【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解 :商城营销与统计——7 种玩法、T+1 聚合、防超卖,电商最烧脑的部分全在这了
大家好,我是你们的源码拆解老伙伴-腻害兔。上一篇我们啃完了商城的「商品与交易」模块——50+ 张表、责任链价格引擎、Handler 策略模式、售后状态机,信息量已经够大了。
但说实话,那只是电商的"骨架"。今天这篇才是"血肉"——营销玩法和数据分析。一个电商平台能不能让用户掏钱,靠的不是商品列表有多好看,而是优惠券怎么发、秒杀怎么抢、拼团怎么裂变、数据看板怎么指导运营决策。
废话不多说,直接上干货。
一、今日模块概览
一句话概括:营销模块(promotion)负责"让用户花钱",统计模块(statistics)负责"告诉老板赚了多少"。
这两个模块在整个商城体系中的定位非常清晰:
- 营销模块 = 7 种促销玩法 + 内容运营(文章/轮播)+ 页面装修(DIY)。它是商城的"增长引擎"。
- 统计模块 = 交易统计 + 商品统计 + 会员统计 + 支付统计。它是商城的"驾驶舱仪表盘"。
划重点!!! 营销模块有 17+ 张表、7 种独立的促销活动类型,每种类型都有完整的"创建→活动→核销→关闭"生命周期。统计模块虽然只有 2 张聚合表,但背后是 T+1 定时聚合 + 实时查询的混合架构。这两个模块加在一起,基本覆盖了一个中小型电商平台的全部运营需求。
二、技术选型分析
2.1 营销模块的技术决策
| 技术点 | 选型 | 理由 | 替代方案 |
|---|---|---|---|
| 活动库存管理 | 活动级 + 商品级双级库存 | 活动总库存控制整体,商品库存控制 SKU 粒度 | 单级库存(不够灵活) |
| 并发安全 | 乐观锁(where 条件校验)+ 原子更新 | 适合读多写少场景,避免分布式锁的复杂度 | Redis 分布式锁(重)、数据库行锁(性能差) |
| 活动冲突校验 | 同 SPU 不允许同时参与多个同类型活动 | 防止价格叠加导致亏损 | 允许叠加(需要复杂的价格计算引擎) |
| 商品更新模式 | Diff-based(对比新旧列表,增删改) | 只变更差异部分,减少数据库操作 | 全量删除再插入(有闪断风险) |
| 批量操作容错 | 逐条 try-catch + 错误隔离 | 一条失败不影响整批 | 全有或全无(一批错全部回滚) |
2.2 统计模块的技术决策
| 技术点 | 选型 | 理由 | 替代方案 |
|---|---|---|---|
| 数据聚合方式 | T+1 定时任务(Quartz) | 数据量大时避免实时聚合拖垮主库 | 实时聚合(性能差)、ClickHouse(架构重) |
| 聚合表设计 | 按天 + 按 SPU 的物化视图思路 | 查询直接读聚合表,秒级响应 | 每次从订单表 GROUP BY(慢) |
| 对比分析 | 当期 vs 上期双时间段查询 | 运营最关心的"环比"需求 | 只查当期(信息不够) |
| 会员漏斗 | 实时从源表计算 | 会员数据量相对小,实时可接受 | 也做 T+1(数据不够实时) |
踩坑提醒:很多电商项目在做统计时,一开始用实时聚合,订单量上来后查询越来越慢,最后不得不重构。RuoYi 的做法是一开始就用 T+1 聚合表,虽然牺牲了一点实时性,但保证了查询性能。对于绝大多数电商场景,T+1 的统计数据完全够用——老板看的是趋势,不是秒级精度。
三、需求溯源推演
3.1 营销模块的需求还原
想象一下这个场景:你是一个电商平台的运营负责人,老板跟你说"我们要搞促销"。
"搞促销"三个字背后,其实藏着至少 7 种完全不同的需求:
需求一:发优惠券
"新用户注册送 50 元券,满 200 可用。老用户每周可以领一张 9 折券。"
这就是 promotion_coupon_template + promotion_coupon 的模板-实例模型。模板定义规则(面额、门槛、有效期、领取限制),实例是用户手里的那张券。
需求二:限时秒杀
"明天上午 10 点,iPhone 15 原价 5999,秒杀价 3999,限量 100 台。"
这就是 promotion_seckill_activity + promotion_seckill_config(时段配置)+ promotion_seckill_product 的三层结构。时段配置支持一天多个秒杀场次(10 点场、14 点场、20 点场),每个场次有不同的商品和价格。
需求三:拼团
"3 人成团,每人 99 元买原价 199 的商品。24 小时内不成团自动退款。"
这就是 promotion_combination_activity + promotion_combination_record。关键设计是"团长"和"团员"的关系——团长创建记录(headId = 0),团员加入时继承团长的 headId,凑够人数后全部标记成功。
需求四:砍价
"邀请 5 个好友帮忙砍价,每人砍 10-50 元随机,砍到最低价就能买。"
这就是 promotion_bargain_activity + promotion_bargain_record + promotion_bargain_help。最精妙的设计是砍价金额的随机性(randomMinPrice ~ randomMaxPrice)和并发安全——用乐观锁防止多人同时砍价时价格计算出错。
需求五:限时折扣
"本周全场 8 折,部分爆款 5 折。"
这就是 promotion_discount_activity + promotion_discount_product。相对最简单的一种,但要注意和秒杀、拼团的价格冲突。
需求六:满减/满送
"满 300 减 50,满 500 送赠品。"
这就是 promotion_reward_activity,用 JSON 存储阶梯规则。
需求七:积分兑换
"500 积分 + 9.9 元换购商品。"
这就是 promotion_point_product,积分和现金的混合支付模式。
看到这里你可能会有一个疑问:为什么每种活动都要单独一套表,而不是用一张通用的 promotion_activity 表加类型字段?
答案是:每种活动的数据结构差异太大了。秒杀有时段配置,拼团有团长/团员关系,砍价有帮砍记录,优惠券有模板/实例两层……如果硬塞进一张表,会有大量 NULL 字段,查询和关联都会变得极其复杂。分表设计虽然表多,但每种活动的逻辑是独立清晰的。
3.2 统计模块的需求还原
统计模块的需求更直接——老板和运营每天要看数据:
需求一:今天卖了多少?
交易概览:昨日订单数、支付金额、退款金额,对比前天的环比变化。
需求二:哪些商品卖得好?
商品排行:按销量/销售额/浏览量排序,找出爆款和滞销款。
需求三:用户从哪来?
会员漏斗:访客数 → 下单用户数 → 支付用户数 → 客单价,看哪个环节流失最多。
需求四:钱从哪来?
支付统计:微信支付、支付宝、余额支付各占多少比例。
四、竞品对标分析
| 对比维度 | RuoYi-Vue-Pro 商城 | mall4j | CRMEB | JeecgBoot 商城 |
|---|---|---|---|---|
| 营销玩法数量 | 7 种(券/秒杀/拼团/砍价/折扣/满减/积分) | 5 种(券/秒杀/拼团/砍价/积分) | 6 种(券/秒杀/拼团/砍价/折扣/积分) | 3 种(券/秒杀/拼团) |
| 优惠券模型 | 模板→实例两层,支持固定面额/百分比/满减 | 类似两层模型 | 类似两层模型 | 单层模型 |
| 秒杀库存方案 | 双级库存 + 乐观锁 | Redis 预扣 + 异步落库 | Redis 分布式锁 | 数据库行锁 |
| 拼团虚拟成团 | 支持(virtual_group 字段) | 不支持 | 支持 | 不支持 |
| 砍价并发安全 | 乐观锁(whereBargainPrice) | 数据库行锁 | Redis 锁 | 无特殊处理 |
| 统计架构 | T+1 聚合表 + 实时查询混合 | 实时聚合 | T+1 聚合 | 实时聚合 |
| 数据对比分析 | 当期 vs 上期双时间段 | 仅当期 | 当期 vs 上期 | 仅当期 |
| 会员漏斗 | 访客→下单→支付→客单价 | 无 | 有 | 无 |
| 页面装修 | DIY 模板 + 页面 JSON 配置 | 无 | 有 | 无 |
RuoYi 的优势
- 营销玩法最全:7 种玩法覆盖了电商最常见的促销场景,而且每种都有完整的生命周期管理。
- 拼团虚拟成团:这个功能在很多商业系统中都是付费功能。当拼团快到期但人数不够时,自动用虚拟用户补齐,保证成团率。这对运营来说非常实用。
- 统计模块设计成熟:T+1 聚合 + 实时查询的混合架构,既有性能保证又有数据完整性。对比分析功能(昨日 vs 前天、本月 vs 上月)是运营最需要的。
- 页面装修系统:promotion_diy_template + promotion_diy_page 的模板-实例模式,让运营可以自定义首页布局,不需要开发介入。
RuoYi 的劣势
- 缺少营销活动的 A/B 测试:无法对同一商品设置不同的促销策略做效果对比。
- 缺少用户标签/人群定向:优惠券发放是"所有人都能领"或"新用户才能领",缺少基于用户行为标签的精准发放。
- 统计维度有限:缺少渠道来源分析(用户从哪个渠道来的)、复购率分析、RFM 用户分层等高级分析能力。
五、核心业务流程
5.1 优惠券领取与使用流程

5.2 秒杀库存防超卖流程

关键设计:UPDATE ... WHERE stock > 0 这条 SQL 是防超卖的核心。它利用数据库的行锁机制,确保即使 100 个人同时抢 1 件商品,也只有 1 个人能成功。比 Redis 分布式锁更简单、更可靠。
5.3 拼团成团流程

5.4 T+1 统计聚合流程

六、数据模型解读
6.1 营销模块的表结构
营销模块的表可以分为 4 大类:
第一类:活动主表 + 商品关联表(6 组)
每种促销活动都是"活动头 + 活动商品"的两层结构:
| 活动类型 | 活动主表 | 商品关联表 | 核心字段 |
|---|---|---|---|
| 秒杀 | promotion_seckill_activity | promotion_seckill_product | 时段配置 ID 列表、秒杀价、库存 |
| 拼团 | promotion_combination_activity | promotion_combination_product | 成团人数、虚拟团、限制时长 |
| 砍价 | promotion_bargain_activity | —(直接关联 SPU/SKU) | 起始价、最低价、帮砍次数 |
| 限时折扣 | promotion_discount_activity | promotion_discount_product | 折扣类型(百分比/固定价) |
| 满减满送 | promotion_reward_activity | —(规则存 JSON) | 条件类型、阶梯规则 |
| 积分兑换 | promotion_point_activity | promotion_point_product | 积分数量 + 现金价格 |
设计亮点:商品关联表中冗余了活动的 name、status、startTime、endTime 字段。这样在查询商品列表时,不需要 JOIN 活动主表就能知道商品当前参与了什么活动。这是典型的读优化设计——用冗余换查询性能。
第二类:优惠券模板 + 实例(2 张表)
promotion_coupon_template 定义规则:面额、门槛、有效期类型(固定日期/领取后 N 天)、领取限制、总库存。
promotion_coupon 是用户手里的券:关联模板 ID、用户 ID、状态(未使用/已使用/已过期)、使用订单 ID。
这是一个经典的类-对象模型——模板是"类",券实例是"对象"。
第三类:参与记录表(3 张表)
| 表 | 用途 | 关键字段 |
|---|---|---|
| promotion_combination_record | 拼团参与记录 | head_id(团长 ID)、user_size、status、expire_time |
| promotion_bargain_record | 砍价记录 | bargain_price(当前砍到价)、status、order_id |
| promotion_bargain_help | 帮砍记录 | record_id、user_id、reduce_price(砍掉金额) |
第四类:内容运营表(4 张表)
| 表 | 用途 |
|---|---|
| promotion_article_category | 文章分类 |
| promotion_article | 文章(可关联 SPU,支持热门/轮播推荐) |
| promotion_diy_template | 页面装修模板(JSON 存储布局) |
| promotion_diy_page | 装修页面实例 |
6.2 统计模块的表结构
统计模块只有 2 张聚合表,但设计很讲究:
trade_statistics——交易日报表
每天一行,记录当天的核心交易指标:
| 字段 | 含义 |
|---|---|
| time | 统计日期 |
| order_create_count | 创建订单数 |
| order_pay_count | 支付订单数 |
| order_pay_price | 支付总金额 |
| after_sale_count | 售后单数 |
| after_sale_refund_price | 退款总金额 |
| brokerage_settlement_price | 佣金结算金额 |
| wallet_pay_price | 余额支付金额 |
| recharge_pay_count/price | 充值笔数/金额 |
| recharge_refund_count/price | 充值退款笔数/金额 |
product_statistics——商品日报表
每个 SPU 每天一行,记录商品维度的指标:
| 字段 | 含义 |
|---|---|
| time | 统计日期 |
| spu_id | 商品 ID |
| browse_count / browse_user_count | 浏览量 / 浏览人数 |
| favorite_count | 收藏数 |
| cart_count | 加购数 |
| order_count / order_pay_count / order_pay_price | 订单数 / 支付数 / 支付金额 |
| browse_convert_percent | 浏览→购买转化率 |
为什么 product_statistics 要按 SPU 粒度? 因为运营最关心的问题是"哪个商品卖得好"。按 SPU 聚合后,可以直接做商品排行(rank-page 接口),按销量、销售额、浏览量任意排序。
七、产品设计亮点与槽点
7.1 让我眼前一亮的设计
亮点一:拼团虚拟成团
// 过期未成团时的处理逻辑
if (activity.getVirtualGroup()) {
// 虚拟团:自动补齐虚拟用户,标记成团成功
handleVirtualGroupRecord(record);
} else {
// 真实团:标记失败,取消订单退款
handleExpireRecord(record);
}
这个设计太懂运营了。拼团活动最怕的就是"差一个人成不了团",用户体验差,转化率也低。虚拟成团功能让运营可以设置"即使人数不够也自动成团",保证活动效果。这在拼多多等主流电商平台上是标配功能,但在开源项目中很少见。
亮点二:砍价的乐观锁设计
// BargainRecordServiceImpl.updateBargainRecordBargainPrice()
// 使用乐观锁:只有当前价格匹配时才更新成功
baseMapper.updateByIdAndBargainPrice(record.getId(), record.getBargainPrice(), newPrice);
多人同时帮同一个人砍价时,如果不用锁,会出现"价格计算错误"的问题。RuoYi 没有用重量级的分布式锁,而是用数据库的乐观锁(WHERE bargain_price = 期望值)来解决。简单、高效、不会死锁。
亮点三:Diff-based 商品更新
所有活动类型的商品更新都用了同一个模式:
// 对比新旧商品列表,生成三个操作列表
List<XXXProductDO> createList = ...; // 新增的商品
List<XXXProductDO> updateList = ...; // 修改的商品
List<XXXProductDO> deleteList = ...; // 删除的商品
// 批量执行增删改
这个模式避免了"全量删除再插入"导致的短暂数据缺失问题,也减少了不必要的数据库操作。
亮点四:统计模块的幂等设计
// TradeStatisticsServiceImpl.statisticsTrade()
// 先检查该日期是否已有数据
if (existsByTime(date)) {
return; // 幂等保护,重复执行不会重复插入
}
T+1 统计任务如果因为某种原因重复执行(比如 Quartz 集群中的多节点同时触发),幂等设计确保不会产生重复数据。
7.2 可以改进的地方
槽点一:活动冲突只校验了同类型
目前每种活动只校验"同 SPU 不能同时参与多个同类型活动",但没有校验跨类型冲突。比如一个商品同时参与秒杀(5 折)和限时折扣(8 折),用户到底按哪个价格买?虽然价格引擎有优先级处理,但在活动创建时就应该给运营一个明确的提示。
槽点二:优惠券缺少人群定向
目前的优惠券领取规则只有"新用户"和"所有人"两种,缺少基于用户标签(消费金额、购买频次、最后登录时间等)的精准发放。这对于精细化运营来说是个短板。
槽点三:统计缺少渠道分析
目前的统计维度只有时间、商品、会员属性(性别/地区/终端),缺少渠道来源分析。如果平台同时有小程序、H5、APP 多个入口,运营无法知道哪个渠道的转化率最高。
八、发散性思考
8.1 营销模块还能做什么?
方向一:营销活动模板化
目前每种活动都需要运营手动配置。可以做一个"营销活动模板"功能——运营选择"618 大促模板",系统自动创建一组关联活动(满减 + 优惠券 + 秒杀 + 拼团),一键启动。
方向二:智能定价
基于历史销售数据和库存情况,自动建议促销价格。比如"该商品过去 30 天日均销量 10 件,当前库存 500 件,建议设置 7 折促销,预计 15 天清仓"。
方向三:社交裂变增强
砍价和拼团是基础的社交裂变。可以进一步加入"分享得积分"、"邀请好友注册双方得券"、"老带新专属价"等更丰富的裂变玩法。
8.2 统计模块还能做什么?
方向一:实时大屏
目前的 T+1 统计适合日常运营报表。可以加一个"实时交易大屏"模块,用 WebSocket 推送实时订单数据,适合大促期间监控。
方向二:RFM 用户分层
基于用户的最近消费时间(Recency)、消费频率(Frequency)、消费金额(Monetary),自动将用户分为"重要价值用户"、"重要发展用户"、"重要挽留用户"等 8 类,指导差异化营销。
方向三:预测分析
基于历史数据预测未来趋势——下个月预计销售额、哪些商品可能缺货、哪些用户可能流失。这需要引入简单的机器学习模型,但对于有一定数据积累的平台来说非常有价值。
8.3 设计思路的迁移价值
营销模块的"活动生命周期管理"模式(创建→启用→运行→关闭→删除)可以迁移到任何需要"限时活动"的场景,比如:在线教育平台的限时课程、SaaS 产品的限时免费试用、线下门店的限时促销。
统计模块的"T+1 聚合 + 实时查询混合架构"可以迁移到任何需要数据分析的业务系统,比如:内容平台的文章阅读统计、SaaS 平台的功能使用统计、IoT 平台的设备数据采集。
九、关键代码导读
9.1 CouponServiceImpl.java
路径:yudao-module-mall/yudao-module-promotion/src/main/java/.../service/coupon/CouponServiceImpl.java
为什么值得读:优惠券是电商营销的核心。这个类展示了完整的券生命周期管理——领取(takeCoupon)、使用(useCoupon)、退还(returnUsedCoupon)、过期(expireCoupon)、作废(invalidateCoupon)。特别值得关注的是 invalidateCoupon 使用 Propagation.REQUIRES_NEW 确保每条券的作废操作独立事务,不会被外层事务回滚影响。
9.2 CombinationRecordServiceImpl.java
路径:yudao-module-mall/yudao-module-promotion/src/main/java/.../service/combination/CombinationRecordServiceImpl.java
为什么值得读:这是整个营销模块最复杂的 Service。拼团的"团长-团员"关系管理、成团判断、过期处理、虚拟成团、微信订阅消息推送,全部集中在这个类里。理解了这个类,就理解了拼团业务的完整链路。
9.3 BargainRecordServiceImpl.java
路径:yudao-module-mall/yudao-module-promotion/src/main/java/.../service/bargain/BargainRecordServiceImpl.java
为什么值得读:砍价模块的并发安全设计是亮点。updateBargainRecordBargainPrice 方法使用乐观锁(whereBargainPrice 条件)来防止多人同时砍价时的价格计算错误。这是一个非常实用的并发控制案例。
9.4 TradeStatisticsServiceImpl.java
路径:yudao-module-mall/yudao-module-statistics/src/main/java/.../service/trade/TradeStatisticsServiceImpl.java
为什么值得读:展示了 T+1 统计聚合的完整实现——从订单表、售后表、佣金表、充值表中分别聚合数据,合并成一行写入 trade_statistics 表。幂等设计、多租户支持(@TenantJob)都值得学习。
9.5 DataComparisonRespVO.java
路径:yudao-module-mall/yudao-module-statistics/src/main/java/.../controller/admin/vo/DataComparisonRespVO.java
为什么值得读:这个 VO 定义了统计模块的核心响应结构——"当期数据 vs 对比期数据"的双值模式。所有统计接口都返回这个结构,前端可以直接做环比展示。设计简洁、复用性高。
总结
RuoYi-Vue-Pro 的商城营销与统计模块是我分析至今信息量最大的模块之一——17+ 张表、7 种营销玩法、T+1 聚合架构、防超卖设计、虚拟成团……几乎每一层都有值得细品的设计。
如果要用一句话评价:这是一个"功能完整度拉满、架构设计及格偏上"的电商营销方案。 它不是最优雅的(缺少 DDD 分层),但它可能是开源 Java 电商方案中"开箱即用"能力最强的。对于想快速搭建电商系统的团队来说,芋道商城是一个非常好的起点——前提是你得先把它读懂。
好了,今天的拆解就到这里。觉得有用的话,点个赞支持一下呗~ 下一篇我们继续商城系列,拆解会员模块——会员等级怎么设计?积分体系怎么做?余额充值和分销佣金怎么流转?敬请期待!
更多推荐




所有评论(0)