拆解一套开源商城系统的架构设计——以 CRMEB 为例
最近在接一个电商相关的外包项目,需要快速搭一套私域商城。调研了几个开源方案之后,选择了 CRMEB 标准版(PHP),花了几天时间把代码翻了一遍,顺手记录一下几个让我觉得值得学习的设计点。
一、分层架构:Controller → Services → Dao → Model
这是我第一次接触 CRMEB 时觉得最规整的地方。
大多数 ThinkPHP 项目写到后期都变成了"胖 Controller"——一个控制器方法塞下几百行业务逻辑,维护起来非常痛苦。CRMEB 在 TP6 的基础上强制推行了四层分离:
Controller → 只负责接收请求参数、调用 Services、返回响应
Services → 核心业务逻辑层,注入 Dao 实例处理数据
Dao → 封装复杂查询组合,不放业务逻辑
Model → 纯粹的 ORM 映射,对应数据表
举个例子,新增一个自定义模块的大致流程是:
app/model/test/Student.php —— 继承 BaseModel,设定 $pk 和 $name
app/dao/test/StudentDao.php —— 封装对这张表的查询拼装逻辑
app/services/test/StudentServices.php —— 继承 BaseServices,注入 StudentDao,写业务规则
app/adminapi/controller/test/Student.php —— 注入 Services,调用方法,返回 JSON
看起来多了几个文件,但当业务复杂度上来之后,这种结构的维护成本远低于"一锅炖"。DAO 层专门处理"如何查",Services 层专门处理"查完之后干什么",逻辑不会混在一起。
参数获取方面统一用 request()->getMore() 和 request()->postMore() 来声明参数列表,这个设计让每个接口的入参一目了然,不用追着 $_POST 到处找。
二、多模块路由隔离
CRMEB 的 app 目录下有三个业务接口模块:
adminapi —— 后台管理接口
api —— 用户端接口(小程序/H5/APP)
kefuapi —— 客服系统接口
outapi —— 对外开放接口
每个模块拥有独立的路由配置、独立的事件机制、独立的中间件,互不干扰。这意味着你在 api 模块新增一个接口,完全不会影响 adminapi 的路由表,甚至可以对不同模块配置不同的限流、认证策略。
路由配置强制要求 url_route_must => true,也就是说所有接口必须显式声明路由,不允许走默认的"控制器/方法"自动路由。这对于一个对外暴露 API 的系统来说是正确的做法——你清楚地知道每一个可以被访问的端点是什么。
三、事件驱动的监听机制(Listens)
订单支付成功之后要干什么?发短信、更新库存、触发分销佣金计算、记录积分……这些逻辑如果全塞在"支付回调"里,迟早会变成一坨无法维护的代码。
CRMEB 的做法是用 ThinkPHP6 的事件机制 + 自建监听类来解耦这些流程。在 crmeb/listens 目录下,每类后续动作对应一个独立的监听器:
OrderPaySuccessListen.php // 订单支付回调
UserRechargeSuccessListen.php // 用户充值回调
AutoTakeOrderListen.php // 自动确认收货
AutoCancelGroupOrderListen.php // 自动关闭过期拼团
AutoUnLockBrokerageListen.php // 佣金解冻
AutoOrderProfitsharingListen.php // 自动分账
AutoOrderReplyListen.php // 系统默认好评
支付完成只需要 event(‘order.pay.success’, $order) 触发一个事件,后续各个监听器自己处理各自的逻辑。新增一个支付后行为,只需要新建一个监听器并注册事件,不需要改动支付流程的核心代码。
这种设计在业务扩展时非常友好——"不改老代码,只加新代码"这件事在这套架构下是可以真正做到的。
四、存储驱动抽象——以文件上传为例
系统的文件上传封装在 crmeb/services/upload/Upload.php 里,继承 BaseManager,实际驱动支持:
本地存储(Local)
阿里云 OSS(OSS)
腾讯云 COS(COS)
七牛云(Qiniu)
切换存储方式只需要改配置,调用方代码完全不需要动。这个设计思路在支付渠道抽象上同样有体现——微信支付、支付宝、余额支付走统一的接口,各自的实现细节收敛在对应的 driver 里。
这类抽象在系统设计初期多花的那点时间,会在后期扩展接入新渠道时成倍地省回来。
五、Uniapp 前端:一套代码、多端输出
前端采用 Uniapp + Vue2,理论上一套代码可以编译输出:
微信小程序
H5(公众号/浏览器)
App(iOS/Android)
实际体验下来,小程序和 H5 的适配质量比较高,App 打包需要单独处理一些原生权限和支付通道的差异,但整体减少了大量重复开发的工作量。
多端接口设计上,后端对不同平台的登录、支付接口做了统一封装,前端通过 uni.getSystemInfo() 判断当前平台后走不同的支付唤起方式,逻辑集中在一个通用支付函数里处理分支,不会把平台差异散落到各个业务页面中。
六、分销系统的状态机设计
二级分销是这类电商系统的常规功能,但实现好不好,差距很大。CRMEB 的佣金流转经过三个阶段:
生成:订单支付成功后,按商品配置的返佣比例计算一级、二级佣金,写入分销记录,状态为"待解冻"
冻结期:买家确认收货前,佣金处于锁定状态,不可提现(防止退货后的烂账)
解冻:确认收货后,由监听器 AutoUnLockBrokerageListen 触发解冻,佣金进入可提现余额
这个状态机设计解决了一个实际问题:退货退款时,如果佣金已经提现就很难追回。冻结期的设置是一种合理的风控机制。
分销模式支持三种:指定分销(后台手动开通)、人人分销(任意用户可参与)、满额分销(消费达到阈值后自动获得资格)。不同场景选不同模式,后台可配置切换。
七、Redis 的使用姿势
CRMEB 在以下场景用了 Redis:
缓存:系统配置、商品信息等高频读取的数据,避免每次请求都查数据库
队列:异步任务(发短信、消息推送)走队列,不阻塞主流程
秒杀库存:利用 Redis 的原子操作处理高并发下的库存扣减,避免超卖
秒杀商品的库存在活动开始前预热到 Redis,扣减用 DECR 命令,失败则直接返回"已抢完",不落库。扣减成功后,订单创建和库存同步再异步处理。这个思路和很多大厂秒杀方案一致,放到中小规模的电商系统里同样适用。
总结
把这几个点写出来,主要是想说一件事:好的系统设计不一定需要复杂的技术栈,在 PHP + ThinkPHP 这样朴素的组合上,同样可以做出清晰的分层、合理的抽象和可扩展的事件机制。
CRMEB 的源码是 Apache-2.0 协议,代码全量开放,没有加密,可以直接在 Gitee 上拉下来研究。对于想系统学习电商业务逻辑实现的人来说,这份代码是个不错的参考样本——真实业务、真实复杂度、真实的踩坑与解法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)