【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统

大家好,我是你们的老朋友腻害兔,今天继续我们的 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+ 个 Controller90+ 个 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 的优势:

  1. 功能完整度最高:在开源 Java 电商方案中,芋道商城的功能覆盖度几乎是最全的,7 种营销玩法 + 二级分销 + DIY 装修 + 内置客服,基本开箱即用。
  2. 架构可扩展:责任链价格引擎 + Handler 策略模式,新增营销玩法的成本很低。
  3. 多租户原生支持:对于 SaaS 电商场景(比如做多商户入驻的商城平台),这是碾压级优势。

RuoYi-Vue-Pro 的劣势:

  1. 模块耦合度偏高:虽然有 trade-api 做解耦,但 trade 模块直接依赖了 product、pay、promotion、member、system 5 个模块,未来拆微服务的成本不低。
  2. 缺少领域驱动设计:代码是典型的"三层架构"(Controller-Service-Mapper),没有用 DDD 的思想来划分限界上下文。对于 50+ 张表的电商系统来说,后期维护成本会越来越高。
  3. 前端体验待打磨:商城有 3 套前端(Vue2/Vue3/UniApp),但 DIY 装修的可视化编辑器功能相对简陋,和 CRMEB 的拖拽式装修还有差距。

五、核心业务流程

5.1 下单全链路流程

从用户点击"立即购买"到订单完成,整个链路涉及 4 个子模块的协同:

5.2 售后全流程

售后流程相对复杂,涉及用户和商家的多轮交互:

  1. 用户发起售后申请 → 状态变为 APPLY
  2. 商家审批
    • 同意(仅退款)→ 状态变为 WAIT_REFUND → 系统发起退款 → 退款成功 → COMPLETE
    • 同意(退货退款)→ 状态变为 SELLER_AGREE
    • 拒绝 → 状态变为 SELLER_DISAGREE(终结)
  3. 退货退款流程
    • 买家发货 → BUYER_DELIVERY
    • 商家确认收货 → WAIT_REFUND → 退款 → COMPLETE
    • 商家拒绝收货 → SELLER_REFUSE(终结)
  4. 用户随时可取消 → 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 这个模块还能做什么?

  1. 直播带货:在商品详情页接入直播间,主播讲解的商品可以自动弹出优惠券和限时折扣。
  2. 智能推荐:基于 product_browse_history 和 product_favorite 数据做协同过滤推荐,"猜你喜欢"、"看了又看"。
  3. 预售模式:在普通订单的基础上增加"预售"类型,支持定金+尾款模式(类似双11)。
  4. 跨境支付:对接 Stripe/PayPal,把商城从国内扩展到海外。

8.2 如果让我重新设计

  1. 引入 DDD 分层:把"商品"、"交易"、"营销"划分为三个限界上下文,每个上下文内部用 DDD 的四层架构(Interfaces → Application → Domain → Infrastructure),上下文之间通过领域事件通信,而不是直接调用 Service。
  2. 用状态机框架替代手写状态流转:售后模块的状态流转目前靠手写 if-else 判断前置状态,如果引入 Spring Statemachine 或者 Cola Statemachine,状态流转会更清晰、更安全。
  3. 订单超时用延迟消息:把定时任务扫描改成 RocketMQ 延迟消息,订单创建时发一条 30 分钟的延迟消息,消费时检查订单是否已支付,未支付则自动取消。
  4. 购物车支持离线同步:目前购物车存在数据库里,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?敬请期待!

相关推荐
GIR1231 小时前
官方出品 | 多通道土壤呼吸测量系统市场现状与十五五规划深度报告:行业分析+趋势预测全收录
大数据·人工智能·机器学习
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
神奇霸王龙3 小时前
GB/T 46886 闭环屠夫:5 旗舰多模态 LLM 工业质检实测
人工智能·计算机视觉·ai·开源·ai编程·本地部署
南讯股份Nascent3 小时前
洽洽全域会员项目启动会圆满召开
大数据·人工智能
大郭鹏宇3 小时前
基于 LangGraph 构建智能分诊系统(一):项目概述与环境搭建
大数据·人工智能·microsoft·langchain
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
三声三视3 小时前
uni-app 鸿蒙端传参变成 [object Object]?顺着源码追到 ArkTS router 底层才搞明白
人工智能·ai·uni-app·aigc·ai编程·harmonyos
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
计算机魔术师5 小时前
Karpathy:用语音与LLM长谈可提升理解效率
人工智能·ai编程