- 后端
- 电商
- 微服务
- 前端
【免费下载链接】gpmall
【咕泡学院实战项目】-基于SpringBoot+Dubbo构建的电商平台-微服务架构、商城、电商、微服务、高并发、kafka、Elasticsearch
导读:本文以 gpmall 电商微服务项目中
coupon-service/README.md营销服务设计文档为核心,系统讲解营销活动与优惠券的业务场景、功能需求、核心服务接口划分以及tb_sale_acti、tb_sale_rule、tb_sale_coupon、tb_sale_coupon_code、tb_queue_msg五张核心表的完整设计。读者将理解 gpmall 中"满减/满折/满赠"活动规则如何落地、优惠券如何通过"冻结-解冻"状态机配合订单生命周期、优惠码如何批量生成与兑换,并看到该模块在源码中的实际建模情况(含其"草稿"定位的局限)。
模块定位:营销服务在 gpmall 中的角色
在 gpmall 微服务架构中,coupon-service(营销服务)承担的是电商平台最核心的运营工具——营销活动与优惠券的管理和计算职责。与订单、支付、商品等"交易主链路"服务不同,营销服务属于典型的横向支撑型业务:它不直接面对终端用户,而是通过 RPC 接口被OrderService(订单服务)等上游服务调用,在用户下单、结算、支付的关键节点上完成"算优惠"的动作。
从仓库目录结构看,coupon-service沿用了 gpmall 各业务域统一的api / provider 双层拆分模式:
coupon-api:对外发布的 Dubbo 接口定义与 DTO(数据传输对象),供订单等其他服务依赖;coupon-provider:营销服务的实际提供者(Provider),包含 Spring Boot 启动类、Druid 数据源配置、MyBatis 实体与 Mapper、营销策略类等;sketch-sql:四张核心业务表的 MySQL 建表脚本草稿(Navicat 导出格式);README.md:本文所依据的模块设计文档,其中明确标注了关键前提——"优惠券 coupon-api 模块目前仍为草稿,不建议 install 到本地包使用"。也就是说,该模块当前更接近"设计稿 + 数据表雏形"的阶段,业务代码尚未完整实现,这一点在阅读下文接口与实体时会反复印证。
模块的技术底座与其他 provider 保持一致:coupon-provider/pom.xml引入了dubbo、zookeeper/curator-recipes(注册中心)、mybatis-spring-boot-starter、druid、tk.mybatis mapper、pagehelper、redisson等依赖,启动类CouponProviderApplication通过@ComponentScan(basePackages = "com.gpmall.coupon")与@MapperScan("com.gpmall.coupon.dal.persistence")完成包扫描,数据源由 DruidConfig.java 以spring.datasource前缀绑定配置。
业务场景:营销服务要解决什么问题
原设计文档将营销服务的场景划分为"活动"与"优惠券"两大部分,两者相互关联、共同构成完整的营销闭环。
活动场景
活动是营销的"场"。用户在活动期间购买参与活动的商品,即可享受优惠价格(满减、满折等);同时用户可以通过浏览活动页领取优惠券,实现"逛活动页 → 领券 → 下单核销"的转化路径。
优惠券场景
优惠券是营销的"券",其生命周期覆盖"领取 → 使用 → 核销/释放"。文档列举了五种领券方式:
- 新用户注册送券:通过系统自动绑定优惠券,降低新用户首单门槛;
- 消费满额送券:用户订单金额消费满一定数值后自动发放优惠券,促进复购;
- 管理后台绑定:运营人员在管理端手工为用户绑定优惠券(定向营销);
- 活动页点击送券:在活动落地页直接领取;
- 批量生成兑换码,凭码领券:支持线下/异业渠道发放兑换码,用户凭码兑换,适合大规模投放。
在使用侧,用户下单时可以选择一张或多张优惠券;而已使用的优惠券在订单成功支付或取消订单之前,不可再次使用——这条约束直接决定了下文优惠码表status字段的"冻结中"状态设计,是营销服务与订单服务协作的基石。
功能需求:活动与优惠券的能力清单
活动需求
- 允许管理人员创建、修改、删除营销活动;活动可针对单一具体商品,也可以是覆盖全部商品的全场活动;
- 允许查询正在进行中的活动列表以及所有活动列表(对应
IActivityQueryService的queryAll与queryActiveActivityList); - 活动具有起止时间,活动类型包括:满减、满赠、满折、秒杀等。文档对前三类给出了明确定义:
| 类型 | 规则说明 |
|---|---|
| 满减 | 订单金额满足指定数值时,减金额 |
| 满赠 | 订单金额满足指定数值时,赠送产品 |
| 满折 | 订单金额满足指定数值时,按总金额打折计算 |
在源码中,这一枚举被建模为SaleTypeEnum(SaleTypeEnum.java):
public enum SaleTypeEnum { FULL_REDUCE, // 满减 FULL_DISCOUNT, // 满折 FULL_GIFT // 满赠商品 }可以看到当前枚举只落地了满减/满折/满赠三类,文档中提到的"秒杀"尚未进入代码实现,属于预留能力。
优惠券需求
- 允许管理人员创建、修改、删除优惠券(对应
ICouponCoreService中的 admin 系列注释); - 购物车结算时,获取用户可用的优惠券列表(对应
ICouponQueryService.queryActiveByUserId); - 创建订单后,检查优惠券是否满足使用条件,满足则锁定已勾选的优惠券,将其设置为冻结状态;待订单支付完成或取消订单后,再次修改优惠券状态。
第三条需求是整个营销服务与订单服务协作的核心契约,文档给出了明确的服务交互描述:
OrderService 传递活动 id、优惠码 code 给 SaleService,SaleService 冻结优惠券、计算优惠金额,返回优惠总金额、赠送产品 id、营销类型等数据给 OrderService,由 OrderService 处理后续业务。
这条契约表明:优惠金额的计算归属营销服务,而订单后续的落库与支付流程仍由 OrderService 负责,营销服务只做"判定 + 冻结 + 算钱"这三件事。
服务接口划分:API 层设计(含草稿定位说明)
coupon-api模块按"核心操作 / 查询"两个维度拆分了四个 Dubbo 接口,全部位于com.gpmall.coupon包下,且文档与源码均标注了**"此接口暂为草稿"**:
活动服务
IActivityCoreService.java 负责活动的写操作:
public interface IActivityCoreService { CreateActiResponse create(CreateActiRequest request); // 创建营销活动 void update(); // 修改活动 void delete(); // 删除活动 }IActivityQueryService.java 负责活动的读操作:
public interface IActivityQueryService { List queryAll(); // 查询所有活动 List queryActiveActivityList(); // 查询正在进行中的活动 void checkActivityIsValid(Integer id); // 检查创建订单时传递的活动id是否有效 }其中checkActivityIsValid对应订单创建链路中"校验活动是否真实有效"的防伪需求——订单服务在接收用户传来的活动 id 后,必须先经营销服务校验,避免伪造活动参与优惠。
优惠券服务
ICouponCoreService.java 是优惠券生命周期的心脏,集中体现了"冻结/解冻"与"发券"逻辑:
public interface ICouponCoreService { // admin创建/修改/删除优惠券 void freezeCoupon(); // 订单支付过程中 冻结优惠券 void unfreezeCoupon(); // 订单取消后, 解冻优惠券 void generateCouponCode(Integer couponId); // 根据优惠券id生成优惠码 void dispatchCoupon(Integer couponId, Integer... userId); // 派发优惠券 void exchangeCoupon(); // 优惠码兑换优惠券 }ICouponQueryService.java 提供查询与校验:
public interface ICouponQueryService { void queryAll(); // 查询所有优惠券 void queryAllByUserId(); // 根据userId查询所有优惠券 void queryActiveByUserId(); // 根据userId查询所有可用优惠券 void checkCouponIsValid(Integer... ids); // 检查创建订单时传递的优惠券id是否有效 }DTO 与参数校验
草稿阶段已经落地了两个代表性 DTO,可窥见其设计思路:
CreateActiRequest.java 继承自gpmall-commons的AbstractRequest(AbstractRequest.java),通过重写requestCheck()实现创建活动时的参数校验:
public class CreateActiRequest extends AbstractRequest { private String name; private SaleTypeEnum saleType; private String desc; @Override public void requestCheck() { if (StringUtils.isEmpty(name)) { throw new ValidateException("缺少name参数"); } if (saleType == null) { throw new ValidateException("缺少type参数"); } } }UseCouponRequest.java 以订单明细列表作为入参,携带List<OrderItemDto>;OrderItemDto.java 定义了orderId、price(BigDecimal)、num三个字段——这正是SaleStrategy判定"是否满 X"所需的原始输入。策略基类 SaleStrategy.java 已预留抽象方法:
public abstract class SaleStrategy { public abstract boolean match(UseCouponRequest request); // 匹配规则(是否满足满减/满折条件) public abstract void perform(); // 执行优惠计算 }从结构看,设计者意图用策略模式承载"满减/满折/满赠"三类算法的差异(对应SaleTypeEnum的三个枚举值),但具体策略子类尚未实现,属于明确的待办区域。
数据库设计:五张核心表
数据库设计是本模块最完整、最可落地的部分,原文档给出了五张表的字段草图,仓库sketch-sql/目录下同时提供了四张业务表的完整建表 SQL(tb_sale_acti.sql、tb_sale_rule.sql、tb_sale_coupon.sql、tb_sale_coupon_code.sql),provider 侧的 MyBatis 实体类(dal/entitys)也与之一一对应。以下按表逐一展开。
1. 营销活动表 tb_sale_acti
活动主表,记录一次营销活动的基本信息与生命周期。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| name | varchar(255) | 活动名称,非空 |
| type | tinyint | 活动类型 |
| status | tinyint | 状态:0 未开始、1 进行中、2 已结束 |
| start_time / end_time | datetime | 活动起止时间 |
| desc | varchar(255) | 活动描述 |
| created / updated | datetime | 创建/更新时间 |
对应实体 SaleActivity.java,其中status注释为"0 未开始 1 进行中 2 已结束"。完整的建表语句见 tb_sale_acti.sql。status的三态设计配合start_time/end_time,天然支撑IActivityQueryService.queryActiveActivityList()的"进行中活动"查询。
2. 活动规则关联表 tb_sale_rule
这是营销服务最关键的一张表,承载"满 X 减 Y / 满 X 折 / 满 X 赠 Z"的具体规则数值,通过acti_id关联活动,实现"一个活动、多条阶梯规则"的模型。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| acti_id | bigint | 活动 id;若是优惠券规则,此字段为 null |
| item_id | bigint | 满足规则条件的商品 id |
| full_price | decimal(10,2) | 满金额阈值 |
| discount_price | decimal(10,2) | 抵扣金额 |
| discount_rate | decimal(10,2) | 折扣比例 |
| gift_item_id | bigint | 赠送产品 id |
| overlap | bit(1) | 是否可叠加规则,默认 b'0' |
| desc | varchar(255) | 规则描述 |
| created / updated | datetime | 创建/更新时间 |
对应实体 SaleRule.java,建表脚本见 tb_sale_rule.sql。几个关键设计点值得展开:
- acti_id 可空:注释明确"活动规则需要绑定 acti_id,如果是优惠券规则,此字段为 null"。这意味着优惠券同样挂在规则表上——优惠券通过
rule_id指向一条tb_sale_rule,从而实现"满减券"(对应 discount_price)、"折扣券"(对应 discount_rate)等券种复用同一套规则数值结构; - item_id 支持单品级活动:字段可空,为空时即"全场活动",非空时限定具体商品,恰好支撑文档中"单一具体商品 / 全场活动"两种活动形态;
- overlap 控制叠加:bit(1) 类型决定该规则能否与其他优惠叠加,是多券并行计算的前提开关;
- 三值字段互斥使用:
full_price是门槛(满 X),discount_price、discount_rate、gift_item_id分别对应满减、满折、满赠三类产出,由活动的type(即SaleTypeEnum)决定取用哪个字段。
3. 优惠券表 tb_sale_coupon
优惠券主表,描述"券"本身(名称、类型、有效期、数量),并通过rule_id关联具体优惠规则。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| name | varchar(100) | 优惠券名称,非空 |
| desc | varchar(255) | 优惠券描述 |
| type | tinyint | 券类型:1 满减券、2 通用 0 元减价券 |
| rule_id | bigint | 关联的规则 id |
| is_visible | bit(1) | 是否可用,默认 b'1' |
| count | int | 可生成的优惠码数量 |
| grab_start_time / grab_end_time | datetime | 抢券起止时间(领券中心备用) |
| start_time / end_time | datetime | 优惠券有效期 |
| created / updated | datetime | 创建/更新时间 |
对应实体 SaleCoupon.java,建表脚本见 tb_sale_coupon.sql。注意两点:
rule_id在注释中直接写为"优惠金额",实际语义是"关联的规则 id"(对应tb_sale_rule),表结构体现的是"券 → 规则 → 数值"的二级关联;grab_start_time/grab_end_time注释明确为"抢券开始时间,领券中心备用",即预留了"定点抢券"这种高并发营销玩法,但当前并未使用;is_visible是运营侧"上下架"开关,控制券是否对用户可见可领。
4. 优惠码表 tb_sale_coupon_code
优惠码是"批量发券"的载体,这张表记录了码的完整生命周期状态机,是整个营销模块中状态设计最精细的一张表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| code | varchar(255) | 优惠码(按码领券) |
| coupon_id | bigint | 关联的优惠券 id |
| status | tinyint | 0 未领取、1 已领取未使用、2 冻结中、3 已使用、4 已过期 |
| user_id | bigint | 关联用户 |
| order_id | bigint | 消费的订单明细 id |
| consume_time | datetime | 使用时间 |
| created / updated | datetime | 创建/更新时间 |
对应实体 SaleCouponCode.java,建表脚本见 tb_sale_coupon_code.sql。
状态机与订单生命周期的对应关系是这张表的设计精髓,也是前文"优惠券需求第 3 条"的落地载体:
| 状态值 | 含义 | 触发时机 |
|---|---|---|
| 0 | 未领取 | 批量生成兑换码后、用户凭码兑换前 |
| 1 | 已领取未使用 | 用户凭码领取 / 系统派发后 |
| 2 | 冻结中 | 用户下单选中该券,OrderService 调 SaleService 冻结 |
| 3 | 已使用 | 订单支付成功,优惠正式核销 |
| 4 | 已过期 | 超过有效期未使用 |
其中"2 冻结中"正是文档强调的"已使用的优惠券在订单成功支付或取消订单之前,不可再次使用":下单时从 1 → 2(冻结),支付成功后 2 → 3(已使用),订单取消后 2 → 1(解冻),对应ICouponCoreService的freezeCoupon()/unfreezeCoupon()。order_id记录了冻结它的订单,保证一张券在同一时刻只可能被一个订单占用。
5. 消息队列消息表 tb_queue_msg(异常消息补偿)
这是营销模块针对分布式事务可靠性的补充设计。字段草图如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| queue | varchar | 队列名称 |
| topic | varchar | 队列主题 |
| method | varchar | 执行的方法 |
| args | varchar | 方法参数 |
| finish_time | datetime | handle 执行完成时间 |
| created | datetime | 创建时间 |
对应实体 QueueMsg.java。其作用正如文档注释所言:"消息队列消息表(异常消息补偿)"。在微服务环境下,"冻结优惠券→生成订单→支付成功→核销"横跨多个服务,任何一环失败都可能导致券状态与订单状态不一致。这张表以"队列名 + topic + 方法名 + 参数"记录待补偿的 MQ 消息,finish_time标记处理完成时间,供补偿程序(如定时任务)扫描未完成记录、重新投递执行。coupon-provider/service/MQMessageHandler.java的类注释"处理订单支付成功的 MQ 信息"印证了营销服务确实计划通过 MQ 消费订单支付成功事件来驱动券状态流转。
模块现状与落地局限
基于以上源码梳理,需要对该模块给出客观的现状评估:
- API 层为草稿:README 明确声明"coupon-api 模块目前仍为草稿,不建议 install 到本地包使用",四个服务接口中的多数方法只有签名与注释,尚无 provider 侧的
@Service实现类(coupon-provider下目前仅有MQMessageHandler空类与SaleStrategy抽象基类); - 数据层最完整:四张业务表的实体、Mapper 接口与 XML(位于 dal/persistence)以及 sketch-sql 建表脚本均已就绪,是全模块中可直接复用/运行的部分;
- 策略模式已定型:
SaleTypeEnum+SaleStrategy的"枚举定类型、策略做算法"骨架已确立,后续只需为满减/满折/满赠各实现一个策略子类并注册即可; - 订单协作契约已定义:OrderService 传活动 id 与优惠码 code,SaleService 冻结券、算优惠、回传优惠金额/赠品 id/营销类型,这一接口契约与
tb_sale_coupon_code的状态机完全吻合,可作为后续双端联调的基准。
从全仓视角看,前端 coupon.vue("我的优惠"页)目前仅展示"您目前还没有优惠券"的空态占位,路由与入口已预留;这进一步说明营销服务的端到端打通仍处于"设计先行、实现待续"的阶段。若要在本仓库基础上继续推进该模块,合理路径是:先以sketch-sql完成建库,再按IActivityCoreService→ICouponCoreService的顺序补齐 provider 实现,并以SaleStrategy策略子类作为优惠计算的扩展点,最后通过 MQ 消费订单支付事件完成券状态机闭环。
- 后端
- 电商
- 微服务
- 前端
【免费下载链接】gpmall
【咕泡学院实战项目】-基于SpringBoot+Dubbo构建的电商平台-微服务架构、商城、电商、微服务、高并发、kafka、Elasticsearch
相关推荐
10分钟上手card.io-iOS-SDK:iOS开发者必备的信用卡OCR解决方案
10分钟上手card.io iOS SDK:iOS开发者必备的信用卡OCR解决方案 card.io iOS SDK是一款开源的信用卡OCR扫描工具,能帮助iOS
移动开发计算机视觉eShop促销系统:优惠券、折扣码与促销活动
eShop促销系统:优惠券、折扣码与促销活动 引言:电商促销的挑战与机遇 在当今竞争激烈的电商环境中,一个强大的促销系统是企业成功的关键因素。eShop作为基于
后端电商前端微服务Amazing-QR生成优惠券二维码:营销活动工具的终极指南
Amazing QR生成优惠券二维码:营销活动工具的终极指南 想要为你的营销活动制作吸引人的优惠券二维码吗?🎯 Amazing QR 是 Python 生态中
图像处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考