大家好,我是你们的老朋友腻害兔,今天继续我们的 RuoYi-Vue-Pro 源码拆解系列。上一期我们聊了 ERP 模块,有读者留言说想看商城模块的拆解------这不,它来了!
今天我们要啃的是整个项目里最庞大、最复杂的模块:yudao-module-mall。废话不多说,直接上干货。
一、今日模块概览
一句话总结:商城模块是 RuoYi-Vue-Pro 的电商引擎,从商品上架、购物车、下单支付、物流发货到售后退款、分销佣金,覆盖了电商交易全链路。
整个商城大模块由 5 个子模块 组成,内部还通过一个 trade-api 轻量模块来解决循环依赖,架构设计上颇有讲究:
| 子模块 | 职责 | 核心能力 |
|---|---|---|
| yudao-module-product | 商品模块 | SPU/SKU 管理、分类、品牌、规格属性、评论、收藏、浏览记录 |
| yudao-module-promotion | 营销模块 | 优惠券、限时折扣、满减送、秒杀、砍价、拼团、积分商城、DIY 页面、客服 |
| yudao-module-trade | 交易模块 | 购物车、订单、售后、物流、分销佣金、价格计算引擎 |
| yudao-module-trade-api | 交易 API 解耦层 | 枚举、DTO、API 接口,打破 trade ↔ promotion 循环依赖 |
| yudao-module-statistics | 统计模块 | 会员统计、商品统计、交易统计、支付统计 |
划重点!!! 这 5 个子模块加起来涉及 50+ 张数据库表 、70+ 个 Controller 、90+ 个 Service,是整个 RuoYi-Vue-Pro 中体量最大的业务模块群,没有之一。
二、技术选型分析
2.1 为什么是"大模块拆小模块"而不是单体?
RuoYi-Vue-Pro 的商城没有做成一个巨大的 yudao-module-mall,而是拆成了 5 个子模块。这背后有一个很现实的问题------循环依赖。
交易模块需要调用营销模块的优惠券、折扣活动接口来计算价格;而营销模块的拼团、砍价、秒杀又需要调用交易模块来创建订单。这就形成了 trade → promotion → trade 的循环。
解决方案是引入 yudao-module-trade-api 这个"薄包装层",只包含 API 接口定义、DTO 和枚举,不含任何业务逻辑。依赖链变成了:
java
trade-api (仅接口 + DTO + 枚举)
↑ ↑
trade promotion
↑ |
└───────────┘
经验之谈: 这种"API 模块解耦"的思路在微服务架构中很常见(类似 Feign Client 模块),芋道在单体架构里就用了这个思路,说明设计者是有远见------未来如果要拆微服务,这些 API 模块可以直接改成 Feign 接口。
2.2 价格计算引擎:责任链模式
商城里最让我眼前一亮的设计是价格计算引擎 。它没有用一堆 if-else 来处理各种优惠叠加,而是用了责任链模式(Chain of Responsibility):
java
public interface TradePriceCalculator {
// 活动类优惠(秒杀/砍价/拼团/积分商城)
int ORDER_SECKILL_ACTIVITY = 8;
int ORDER_BARGAIN_ACTIVITY = 8;
int ORDER_COMBINATION_ACTIVITY = 8;
int ORDER_POINT_ACTIVITY = 8;
// 限时折扣
int ORDER_DISCOUNT_ACTIVITY = 10;
// 满减送
int ORDER_REWARD_ACTIVITY = 20;
// 优惠券
int ORDER_COUPON = 30;
// 积分抵扣
int ORDER_POINT_USE = 40;
// 运费计算(放在营销活动之后)
int ORDER_DELIVERY = 50;
// 赠送积分(放最后,因为运费也产生积分)
int ORDER_POINT_GIVE = 999;
void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);
}
Spring 会按 @Order 注解的值从小到大依次执行所有 Calculator,每个 Calculator 只负责自己的优惠计算,然后把结果传递给下一个。
| 计算器 | 优先级 | 职责 |
|---|---|---|
| TradeSeckillActivityPriceCalculator | 8 | 秒杀价格覆盖 |
| TradeBargainActivityPriceCalculator | 8 | 砍价价格覆盖 |
| TradeCombinationActivityPriceCalculator | 8 | 拼团价格覆盖 |
| TradePointActivityPriceCalculator | 8 | 积分商城价格覆盖 |
| TradeDiscountActivityPriceCalculator | 10 | 限时折扣 |
| TradeRewardActivityPriceCalculator | 20 | 满减送 |
| TradeCouponPriceCalculator | 30 | 优惠券 |
| TradePointUsePriceCalculator | 40 | 积分抵扣 |
| TradeDeliveryPriceCalculator | 50 | 运费计算 |
| TradePointGivePriceCalculator | 999 | 赠送积分 |
为什么这样设计? 电商的优惠规则变化极快------今天加个秒杀,明天加个拼团,后天老板说要做"满减+优惠券+积分"三重叠加。如果用 if-else,代码很快就变成一锅粥。用责任链模式,新增一种优惠只需要加一个 Calculator 实现类,完全不动已有代码,符合开闭原则(OCP)。
2.3 订单生命周期:Handler 策略模式
订单创建/支付/取消时会触发一系列"副作用":扣库存、核销优惠券、记录分销佣金、更新拼团进度......芋道用了 Handler 策略模式 来解耦:
java
TradeOrderHandler (接口)
├── TradeOrderStockHandler -- 库存扣减/回滚
├── TradeOrderCouponHandler -- 优惠券核销/退回
├── TradeOrderBrokerageHandler -- 分销佣金计算
├── TradeOrderCombinationHandler -- 拼团记录更新
├── TradeOrderBargainHandler -- 砍价记录更新
├── TradeOrderSeckillHandler -- 秒杀库存更新
├── TradeOrderPointHandler -- 积分抵扣/退还
├── TradeOrderMemberPointHandler -- 会员积分变动
└── TradeOrderWechatSyncHandler -- 微信同步
每个 Handler 实现 afterOrderCreate、afterOrderPay、afterOrderCancel 等钩子方法,订单 Service 在关键节点遍历调用所有 Handler。
踩坑提醒: 这种设计虽然优雅,但有个隐患------Handler 之间的执行顺序很重要(比如先扣库存再扣优惠券),如果顺序搞错了会导致数据不一致。好在芋道用了 @Order 注解来控制顺序,但这一点在代码注释里写得不够醒目。
2.4 售后状态机
售后模块用的是经典的状态机模式,8 个状态覆盖了退款/退货退款的全流程:
特别值得注意的是,芋道用了自定义注解 @AfterSaleLog + AOP 来自动记录售后操作日志,每个状态变更方法上标注操作类型,AOP 切面自动写入日志表,既保证了日志完整性,又不侵入业务代码。
三、需求溯源推演
作为一个做过电商产品的产品经理,我来尝试还原一下这个模块最初的需求文档长什么样。
3.1 商品模块的需求溯源
最初的需求大概率是这样的:
"我们需要一个商品管理系统,运营人员能上架/下架商品,支持多规格(比如一件 T 恤有 S/M/L 三个尺码和黑色/白色两种颜色,组合出 6 个 SKU),用户可以按分类浏览商品、搜索商品、查看详情页。"
从代码里可以看出,芋道的 SPU-SKU 模型是标准的电商设计:
- SPU(Standard Product Unit):标准化产品单元,比如"iPhone 15 Pro"
- SKU(Stock Keeping Unit):库存量单位,比如"iPhone 15 Pro / 256GB / 钛金属色"
SPU 表里有个字段设计很有意思------spec_type(规格类型),0 表示单规格,1 表示多规格。单规格商品只有一个默认 SKU,多规格商品则通过属性组合生成多个 SKU。这个设计让前端展示逻辑可以统一处理。
3.2 交易模块的需求溯源
交易模块的需求推演更有意思。从 TradeOrderTypeEnum 可以看出,订单类型从最初的普通订单 ,逐步扩展出了秒杀订单、砍价订单、拼团订单、积分商城订单五种类型。
java
public enum TradeOrderTypeEnum {
NORMAL(0, "普通订单"),
SECKILL(1, "秒杀订单"),
BARGAIN(2, "砍价订单"),
COMBINATION(3, "拼团订单"),
POINT(4, "积分商城"),
}
产品推演: 这基本就是一个电商平台从 0 到 1 的增长路径------先上线基础交易,然后做秒杀拉日活,做砍价做裂变拉新,做拼团做社交传播,最后做积分商城提升留存。芋道的代码演进路径,几乎就是中国电商的标准增长故事。
3.3 分销佣金的需求溯源
trade_brokerage_user、trade_brokerage_record、trade_brokerage_withdraw 三张表构成了一个完整的二级分销体系。从 trade_config 表的字段可以看出:
- 支持配置一级/二级佣金比例
- 支持设置佣金冻结天数
- 支持多种提现方式(微信/支付宝/银行卡)
- 支持设置最低提现金额和手续费比例
老规矩,说句大实话: 分销是社交电商的核心玩法,但也是法律风险最高的功能。二级分销在国内是合法的,但超过三级就涉嫌传销了。芋道只做了二级,这个分寸把握得很到位。
四、竞品对标分析
我们把 RuoYi-Vue-Pro 的商城模块和其他几个主流开源项目做个对比:
| 对比维度 | RuoYi-Vue-Pro | JeecgBoot | Pig | mall4j | CRMEB |
|---|---|---|---|---|---|
| 商品模型 | SPU-SKU,支持多规格 | 基础 SPU-SKU | 无商城 | SPU-SKU,多规格 | SPU-SKU,多规格 |
| 营销玩法 | 秒杀/砍价/拼团/满减/折扣/优惠券/积分商城 7 种 | 无原生商城 | 无原生商城 | 满减/折扣/优惠券 3 种 | 秒杀/拼团/砍价/优惠券/积分 5 种 |
| 交易链路 | 购物车→结算→支付→发货→收货→售后 完整链路 | 无 | 无 | 完整链路 | 完整链路 |
| 分销体系 | 二级分销 + 佣金提现 | 无 | 无 | 无 | 二级分销 |
| DIY 装修 | 支持(DiyPage/DiyTemplate) | 无 | 无 | 基础支持 | 支持 |
| 客服系统 | 内置 IM 客服 | 无 | 无 | 无 | 内置客服 |
| 价格计算 | 责任链模式,10 个 Calculator | 无 | 无 | 硬编码 | 策略模式 |
| 售后状态机 | 8 状态 + AOP 日志 | 无 | 无 | 基础退款 | 基础退款+退货 |
| 数据统计 | 会员/商品/交易/支付 4 维统计 | 无 | 无 | 基础统计 | 数据统计 |
| 多租户支持 | 原生支持 | 原生支持 | 原生支持 | 不支持 | 不支持 |
RuoYi-Vue-Pro 的优势:
- 功能完整度最高:在开源 Java 电商方案中,芋道商城的功能覆盖度几乎是最全的,7 种营销玩法 + 二级分销 + DIY 装修 + 内置客服,基本开箱即用。
- 架构可扩展:责任链价格引擎 + Handler 策略模式,新增营销玩法的成本很低。
- 多租户原生支持:对于 SaaS 电商场景(比如做多商户入驻的商城平台),这是碾压级优势。
RuoYi-Vue-Pro 的劣势:
- 模块耦合度偏高:虽然有 trade-api 做解耦,但 trade 模块直接依赖了 product、pay、promotion、member、system 5 个模块,未来拆微服务的成本不低。
- 缺少领域驱动设计:代码是典型的"三层架构"(Controller-Service-Mapper),没有用 DDD 的思想来划分限界上下文。对于 50+ 张表的电商系统来说,后期维护成本会越来越高。
- 前端体验待打磨:商城有 3 套前端(Vue2/Vue3/UniApp),但 DIY 装修的可视化编辑器功能相对简陋,和 CRMEB 的拖拽式装修还有差距。
五、核心业务流程
5.1 下单全链路流程
从用户点击"立即购买"到订单完成,整个链路涉及 4 个子模块的协同:

5.2 售后全流程
售后流程相对复杂,涉及用户和商家的多轮交互:
- 用户发起售后申请 → 状态变为 APPLY
- 商家审批 :
- 同意(仅退款)→ 状态变为 WAIT_REFUND → 系统发起退款 → 退款成功 → COMPLETE
- 同意(退货退款)→ 状态变为 SELLER_AGREE
- 拒绝 → 状态变为 SELLER_DISAGREE(终结)
- 退货退款流程 :
- 买家发货 → BUYER_DELIVERY
- 商家确认收货 → WAIT_REFUND → 退款 → COMPLETE
- 商家拒绝收货 → SELLER_REFUSE(终结)
- 用户随时可取消 → BUYER_CANCEL(终结)
六、数据模型解读
6.1 商品数据模型
商品模块的核心是 SPU-SKU 二级模型,配合分类、品牌、规格属性构成完整的商品体系:
java
product_category (分类树,最多2级)
└── product_spu (商品SPU)
├── product_brand (品牌)
├── product_sku (SKU,1对多)
│ └── properties (JSON,规格属性组合)
├── product_property → product_property_value (规格键值对)
├── product_comment (评论)
├── product_favorite (收藏)
└── product_browse_history (浏览记录)
设计亮点: SKU 的规格属性用 JSON 存储在 properties 字段里(如 {"propertyId":1,"propertyName":"颜色","valueId":10,"valueName":"黑色"}),而不是用关联表。这是一个性能优先于范式的设计------查询 SKU 详情时不需要 JOIN 规格表,一次查询就能拿到完整的规格信息。
6.2 交易数据模型
交易模块的数据模型围绕订单展开,同时支持分销和物流:
java
trade_order (主订单)
├── trade_order_item (订单明细,1个SPU+SKU对应一条)
├── trade_order_log (订单操作日志)
├── trade_after_sale (售后单)
│ └── trade_after_sale_log (售后日志)
└── trade_cart (购物车)
trade_brokerage_user (分销用户)
├── trade_brokerage_record (佣金记录)
└── trade_brokerage_withdraw (佣金提现)
trade_delivery_express (快递公司)
trade_delivery_express_template (运费模板)
├── trade_delivery_express_template_charge (运费规则)
└── trade_delivery_express_template_free (包邮规则)
trade_delivery_pick_up_store (自提门店)
注意一个细节: trade_order 表里有大量冗余字段------spu_name、sku_properties、pic_url 等本应从 trade_order_item 关联查询的数据,都被冗余存储到了订单表里。这是电商系统的经典设计------订单数据一旦生成就不应该依赖商品表的实时数据(商品可能改名、改图、甚至被删除),同时冗余存储也避免了查询时的 JOIN 开销。
6.3 营销数据模型
营销模块是表最多的子模块,涵盖了 7 种营销玩法:

七、产品设计亮点与槽点
7.1 让我眼前一亮的設計
1. 价格计算引擎的可扩展性
前面已经详细分析过了。10 个 Calculator 各司其职,新增营销玩法只需要加一个 Calculator 类,这种设计在开源项目里真的不多见。对比 mall4j 的硬编码方式,高下立判。
2. 售后日志的 AOP 自动记录
用 @AfterSaleLog 注解 + AOP 切面自动记录售后操作日志,业务代码只需要标注操作类型,日志的记录、上下文信息的收集全部由框架层自动完成。这种设计既保证了日志的完整性(不怕开发者忘记写日志),又保持了业务代码的整洁。
3. 浏览记录的容量控制
每个用户的浏览记录最多保留 100 条,超出后自动淘汰最早的记录。同时,同一商品重复浏览只保留最新一条。这个设计既控制了数据量,又保证了用户体验------"足迹"功能不需要展示几千条历史记录。
4. 订单 Tab 计数的批量返回
get-count 接口一次返回 5 个 Tab 的计数(全部/待付款/待发货/已发货/待评价 + 售后数量),前端一次请求就能渲染完整的订单 Tab 栏,避免了 5 次串行请求。
7.2 可以改进的地方
1. 购物车缺少价格计算能力
源码里有句注释很直白:
java
// TODO 芋艿:未来优化:购物车的价格计算,支持营销信息;
// 目前不支持的原因,前端界面需要前端 pr 支持下;例如说:会员价格;
购物车目前只做数量管理和库存校验,不计算优惠价格。这意味着用户在购物车页面看不到"券后价"、"活动价",必须进入结算页才能看到最终价格。这在体验上是个明显的短板------淘宝、京东的购物车都能实时显示优惠后价格。
2. App 端收藏接口拼写错误
AppFavoriteController 里有个接口路径是 /exits,应该是 /exists(检查是否已收藏)。虽然不影响功能,但这种拼写错误暴露在外,对 API 的专业度有影响。
3. 订单超时取消依赖定时任务
订单超时未支付自动取消是通过定时任务(TradeOrderAutoCancelJob)扫描实现的,而不是延迟消息。在订单量大的场景下,定时任务可能有几秒到几十秒的延迟,而且频繁扫表对数据库有压力。更优的方案是用 RocketMQ 的延迟消息或者 Redis 的 Key 过期事件。
4. SPU 删除流程偏重
删除 SPU 必须先把状态改为"回收站"(RECYCLE),然后再执行删除。虽然逻辑上没问题,但前端操作需要两步,对于批量管理商品的运营人员来说,体验不够好。
八、发散性思考
8.1 这个模块还能做什么?
- 直播带货:在商品详情页接入直播间,主播讲解的商品可以自动弹出优惠券和限时折扣。
- 智能推荐:基于 product_browse_history 和 product_favorite 数据做协同过滤推荐,"猜你喜欢"、"看了又看"。
- 预售模式:在普通订单的基础上增加"预售"类型,支持定金+尾款模式(类似双11)。
- 跨境支付:对接 Stripe/PayPal,把商城从国内扩展到海外。
8.2 如果让我重新设计
- 引入 DDD 分层:把"商品"、"交易"、"营销"划分为三个限界上下文,每个上下文内部用 DDD 的四层架构(Interfaces → Application → Domain → Infrastructure),上下文之间通过领域事件通信,而不是直接调用 Service。
- 用状态机框架替代手写状态流转:售后模块的状态流转目前靠手写 if-else 判断前置状态,如果引入 Spring Statemachine 或者 Cola Statemachine,状态流转会更清晰、更安全。
- 订单超时用延迟消息:把定时任务扫描改成 RocketMQ 延迟消息,订单创建时发一条 30 分钟的延迟消息,消费时检查订单是否已支付,未支付则自动取消。
- 购物车支持离线同步:目前购物车存在数据库里,App 断网时无法操作。可以加一层本地缓存(SQLite),联网后自动同步。
8.3 设计思路迁移
芋道商城的这几个设计思路,可以迁移到很多其他场景:
- 责任链价格计算 → 任何需要"多规则叠加计算"的场景:保险保费计算、税费计算、物流费用计算。
- Handler 策略模式 → 任何需要"一个动作触发多个副作用"的场景:用户注册后触发一系列初始化操作、审批通过后触发一系列通知。
- API 模块解耦 → 任何需要"打破循环依赖"的场景:把接口定义和实现分离到不同模块。
- 售后状态机 + AOP 日志 → 任何需要"状态流转 + 操作审计"的场景:工单系统、审批流、合同管理。
九、关键代码导读
最后,推荐 5 个最值得阅读的代码文件,按优先级排序:
1. TradePriceServiceImpl.java
路径: yudao-module-trade/src/main/java/.../service/price/TradePriceServiceImpl.java
为什么值得读: 这是整个商城的"定价大脑"。短短 160 行代码,展示了如何用责任链模式把 10 个计算器串联起来。读懂了这个文件,你就理解了电商价格计算的核心逻辑。特别注意 priceCalculators.forEach(calculator -> calculator.calculate(...)) 这一行------Spring 自动注入所有实现了 TradePriceCalculator 接口的 Bean,并按 @Order 排序。
2. TradeOrderUpdateServiceImpl.java
路径: yudao-module-trade/src/main/java/.../service/order/TradeOrderUpdateServiceImpl.java
为什么值得读: 1000+ 行的"巨无霸"Service,包含了订单的完整生命周期:创建、结算、支付、发货、收货、取消、评价。读懂了这个文件,你就理解了电商交易的全链路。特别推荐看 createOrder 方法,它展示了从"价格计算 → 生成订单 → 执行 Handler → 返回结果"的完整流程。
3. AfterSaleServiceImpl.java
路径: yudao-module-trade/src/main/java/.../service/aftersale/AfterSaleServiceImpl.java
为什么值得读: 售后状态机的完整实现。8 个状态、6 个操作方法,每个方法都是"校验前置状态 → 更新状态 → 记录日志"的三段式结构。特别推荐关注 @AfterSaleLog 注解的使用方式------这是 AOP 在业务中的经典应用。
4. ProductSkuServiceImpl.java
路径: yudao-module-product/src/main/java/.../service/sku/ProductSkuServiceImpl.java
为什么值得读: SKU 管理是商品模块的核心难点。这个文件展示了多规格商品的 SKU 生成逻辑:N 个属性 × M 个属性值 = N*M 个 SKU。特别推荐看 validateSkuList 方法------它做了 4 层校验(属性存在性、属性不重复、属性数量一致、组合不重复),是防止"脏数据"的典范。
5. CartServiceImpl.java
路径: yudao-module-trade/src/main/java/.../service/cart/CartServiceImpl.java
为什么值得读: 200 行代码,麻雀虽小五脏俱全。购物车的"增删改查"看似简单,但里面藏着几个有意思的设计:同一 SKU 重复添加时合并数量而非新增记录、SPU 被删除时延迟清理购物车、库存校验前置到加购环节。对于想学习电商基础模块的同学,这个文件是最佳入门。
总结
RuoYi-Vue-Pro 的商城模块是我分析至今信息量最大的模块------50+ 张表、7 种营销玩法、责任链价格引擎、Handler 策略模式、售后状态机......几乎每一层都有值得细品的设计。
如果要用一句话评价:这是一个"功能完整度拉满、架构设计及格偏上"的电商方案。 它不是最优雅的(缺少 DDD),但它可能是开源 Java 电商方案中"开箱即用"能力最强的。对于想快速搭建电商系统的团队来说,芋道商城是一个非常好的起点------前提是你得先把它读懂。
好了,今天的拆解就到这里。觉得有用的话,点个赞支持一下呗~ 下一篇我们继续商城系列,拆解营销与统计模块------优惠券怎么设计最灵活?秒杀的库存扣减怎么防超卖?统计数据怎么做到 T+1?敬请期待!