- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
本篇文章围绕《大营销平台系统设计实现》营销服务第 22 节「用户行为返利入账」展开,讲解用户行为返利(rebate)领域从需求设计、库表创建、聚合事务入账到 MQ 异步消息发送与失败任务兜底的完整落地过程。读完你可以掌握:如何用 DDD 方式划分返利领域、如何用一个聚合对象实现返利订单与任务记录的原子入库、以及 MQ 发送失败时如何通过任务表补偿,这同时也是面试中高频追问的分布式一致性问题。
一、章节定位与本章诉求
用户行为返利是《大营销平台系统》第二阶段(营销服务)中的一个独立领域模块,位于整个抽奖/返利/积分流程的入口侧:
- 第 21 节完成了活动信息 API 的迭代与功能完善;
- 第 22 节(本节)负责把用户的行为动作转化为返利订单并异步通知下游;
- 第 23 节接收 MQ 消息完成返利结算,给用户的活动账户充值额度。
原文档(第22节:用户行为返利入账)给出的本章诉求非常明确:
按照用户行为返利的需求设计,创建相应的库表,开发 rebate 返利领域,提供返利订单创建接口。并在写入订单后发送 MQ 消息。后续则处理奖励入账。
也就是说,这一节要做四件事:
- 依据 用户行为奖励需求设计 创建返利相关库表;
- 开发独立的
rebate返利领域; - 对外提供返利订单创建接口(接收用户行为触达信息);
- 在订单写入后发送 MQ 消息,交由下游(第 23 节)完成奖励入账。
本章难度评定为 ★★★☆☆,难点不在于单个功能点的实现,而在于聚合事务 + MQ 异步 + 任务兜底这套组合设计的理解与落地。
二、业务场景:什么是用户行为返利
用户行为返利(User Behavior Rebate)是一种非常日常的营销活动类型。文档中给出的例子很直观:
你在某个平台创建了新账号,就会给你发一堆的开户优惠券,这些都是日常的返利活动。
这类返利的共同特点是:由用户完成特定行为动作来触达奖励。原文档列举了常见的返利行为类型:
| 行为类型 | 说明 |
|---|---|
| 打卡 / 签到 | 每日一次的行为动作,本节主场景 |
| 连签 | 连续多日签到,通常有额外奖励档位 |
| 支付 | 完成一笔支付后获得返利 |
| 开户 | 注册新账号后发放开户优惠 |
| 交易 / 信贷 / 拉新 | 交易达标、信贷行为、邀请新用户等 |
本节在功能实现上主要落地的是日常日历签到行为,但从领域设计上抽象了"用户行为类型"这个维度,把打卡、签到、支付、开户、交易、信贷、拉新等各类任务都作为可扩展的配置项,方便后续扩展。这一点与需求文档的设计意图一致——用户行为奖励需求设计 中明确提到:
用户的 2 个行为动作,打卡/签到(每天可完成一次),另外一个动作是后续对接 openai 项目的时候,来接收一个支付完成的消息,触达发奖资格。
发奖可以是抽奖资格也可以是给用户积分。积分部分后续实现。那么这里 openai 支付的对接和赠送积分的场景,虽然要后续实现,但在我们本次做的需求中,要预留出设计,否则后续就不好扩展了。
也就是说,一个用户行为动作对应奖励一种"东西":可以是我们前面定义出来的 sku(一个 sku 配置了用户可使用的抽奖次数额度),也可以是积分。本节以 sku 返利为主,但库表与领域设计为积分返利预留了扩展点。
三、业务流程:聚合对象 + 事务入库 + MQ 兜底
原文档用一张业务流程图概括了本节的核心流程,并给出了三个关键设计点:
1. 一个行为可能触发多种奖励,按配置组装聚合对象
一个用户行为可能会给多种奖励,所以在接收到用户信息后,会根据配置组装聚合对象。【聚合的目的就是为了做一个统一的事务】
用户的某一个行为动作(例如签到)可能同时命中多条返利配置,系统需要把这一行为产生的全部返利结果组装成一个聚合对象,目的就是保证它们作为一个整体做一次事务性入库,避免一条条写入导致的数据不一致。
2. 聚合对象包含返利订单实体与 task 实体,一个事务入库
一个聚合对象中包含了返利的订单实体对象,写入 task 的实体对象。它们是一个事务入库。
这是整个设计最关键的一环:返利订单记录和**MQ 发送任务记录(task)**在同一个数据库事务中落库。为什么 task 记录必须和业务订单同事务写入?因为在分布式环境下,数据库操作和 MQ 消息发送本身无法处于同一个事务中(MQ 中间件不参与本地数据库事务),一旦订单写库成功但消息发送失败,就会造成"订单存在、下游不知道"的数据丢失。把 task 记录与订单同事务写入,就为后续的消息补偿留下了依据。
3. 发送 MQ 消息,失败有任务兜底
另外是发送 MQ 消息,在完成入口动作后,会直接发送 MQ 消息,并且如果发送失败,会有任务兜底。【这样是面试中经常问到的点,如果 MQ 消息发送失败了,你是怎么处理的。】
完整流程可以概括为:
用户行为动作(签到/支付等) │ ▼ 根据返利配置组装聚合对象(返利订单实体 + task 实体) │ ▼ 同一数据库事务入库(返利订单 + 任务记录) │ ▼ 发送 MQ 消息(返利入账消息) │ ├── 成功 → 更新 task 状态为已发送 │ └── 失败 → 保留 task 未发送状态,由任务扫描补偿兜底四、返利领域建模与库表设计
4.1 DDD 领域边界
从 DDD 建模的视角看,返利是一个独立的领域。在 《架构:DDD 领域驱动设计》 的四色建模规范中:
- 蓝色 - 决策命令:用户发起的行为动作,如"开始签到"、"开始抽奖";
- 黄色 - 领域事件:过去时态描述,如"签到完成"、"抽奖完成"。
签到返利的建模路径就是:用户发起"签到"决策命令 → 触发"签到完成"领域事件 → 返利领域根据配置组装返利订单 → 写入任务记录。从系统建模可以细分出返利、活动、策略、奖品等领域,其中"兑换可以是单独的领域也可以合并到返利实现"(见 system-design-diagram.md)。
4.2 库表设计思路
原文档明确要求"创建相应的库表"。结合 用户行为奖励需求设计 可以推断,返利领域至少需要两类核心表:
- 返利配置表:描述"行为类型 → 奖励内容"的映射关系,奖励内容关联 sku(sku 上配置了可用的抽奖次数额度),并预留积分等奖励类型的扩展字段;
- 返利订单表:记录每次用户行为触达产生的返利订单(即"返利记录"),作为后续结算和幂等判断的依据。
需要说明的是,返利订单表承担了幂等校验的职责。面试问题汇总(notes.md)中专门提到过这个设计:
RabbitMQ 判断重复消费的逻辑,是直接在数据库中查询返利记录表是否有相同的订单 ID 的记录,如果发现重复就不消费。
这也印证了返利订单表需要包含具备业务唯一性的订单/行为 ID,用于支撑"签到每天只返利一次"这类幂等约束。
五、返利订单创建接口与入账实现
本节对外提供的是返利订单创建接口,核心职责是:
- 接收用户行为触达信息:包括用户 ID、行为类型(打卡/签到/支付等)以及必要的业务标识;
- 按配置组装聚合对象:查询该行为对应的返利配置,组装返利订单实体与 task 实体;
- 一个事务入库:返利订单 + task 记录原子写入;
- 发送 MQ 消息:完成入口动作后立即发送返利入账消息。
接口实现遵循 DDD 的分层思想:领域层(domain)只负责业务逻辑(配置组装、聚合构建、事务编排),数据持久化由基础设施层提供;这样返利领域保持独立,后续扩展新行为类型(开户、拉新、信贷)时只需增加配置,而不需要改动领域核心逻辑。
入账的"账"在这里有两层含义:
- 返利订单入账:用户的行为产生了返利订单记录,这是本节的完成标志;
- 奖励额度入账:真正把抽奖次数/积分充入用户账户,属于第 23 节"结算"环节的工作(接收 MQ 消息后调用活动账户额度入账接口)。
本节与下一节的分工,正是"入口入账 + 异步结算"的经典拆分:入口接口快速响应、写库落单,重活(账户额度更新)通过 MQ 异步消化,避免用户在签到接口上等待完整的返利链路。
六、MQ 消息发送与任务兜底设计(面试高频点)
6.1 为什么数据库操作和 MQ 不能在一个事务里
数据库事务只能覆盖本地数据库的读写,而 MQ 消息的发送是跨中间件的操作,两者天然无法合并成一个原子事务。如果先发消息再写库,可能出现消息发出去了但订单没写成功,下游收到消息却查不到订单;如果先写库再发消息,则可能订单写好了但消息发送失败,下游永远收不到。无论哪种顺序,都存在数据不一致的窗口。
6.2 任务表 + 状态标记 + 扫描补偿
本节的解法是"任务记录兜底",这也是面试问题汇总中明确提到的设计(notes.md 第 18 问):
本身发送 MQ 是可能存在万分之一或者十万分之的失败的,而数据库操作和 MQ 操作,本身不能做数据库事务。但又要保证失败后的补偿处理。所以要结合中奖记录在写一条发送 MQ 的任务记录,任务记录上有一个状态,标记是否发送完成,这样就可以通过任务扫描的方式完成 MQ 的补偿发送。
落到返利场景就是:
- 返利订单 + task 记录同事务写库后,顺序执行一次 MQ 发送,成功后更新 task 状态为"已发送";
- 如果发送失败或状态更新失败,task 记录保持"未发送/待补偿"状态;
- 由定时任务(或分布式任务调度,如 XXL-JOB)扫描未完成的任务,进行 MQ 补偿发送。
关于补偿量级,文档特别强调:
这里是为了业务流程最快的推进,如果是更新失败也没关系,还有兜底的任务补偿。【任务补偿的数量并不多,但非常需要这个手段】
也就是说:正常路径追求快(入库后立即发消息),异常路径依赖任务扫描兜底,两者结合保证"只要订单存在,消息最终一定会被送出"。
6.3 幂等设计:唯一业务 ID
既然存在补偿发送,就必然存在重复消息的可能。为此 MQ 消息必须携带具备业务唯一性的标识:
MQ 的消息是必须含带具有唯一标识的业务 ID 的。比如订单 ID、奖品 ID、支付单 ID、交易单 ID、贷款单 ID 等等。接收 MQ 的系统,通过唯一 ID 业务,更新或者写库的时候可以保证幂等性。(notes.md 第 19 问)
返利场景中,消息体携带返利订单 ID(或用户行为业务 ID),第 23 节消费端通过查询返利记录表判断是否已处理,重复消息直接丢弃,从而保证签到返利"每天只入账一次"。
6.4 多机部署下的任务抢占
如果补偿任务部署在多台机器上,每台机器都可能捞到同一条失败消息,导致重复发送。notes.md 第 25 问给出了标准答案:
一个任务就是要有多机备份,避免一个挂了,就没有人执行了。之后这里的方案是加锁:设计一个抢占锁,多个任务抢占同一个锁,谁抢占到了,谁可以执行;如果抢占的执行失败了,删掉锁,重新执行;如果删锁失败,对于是谁抢占的,谁可以做重入锁,继续执行;锁有失效时间,如果抢占到的自己挂了,等待锁失效后,重新轮候抢占。
这套"抢占锁 + 失效时间 + 重入"机制,确保任务补偿在任何单点故障场景下都不会中断,也不会被多机重复执行(配合消息幂等,双保险)。
七、衔接结算:第 23 节返利结算
本节"入账"完成后,链路进入 第23节:用户行为返利结算,原文档对该节的设计要点如下:
上一节对用户的行为根据返利配置进行入账发送 MQ 消息,这一节将接收 MQ 消息开始结算返利。这里也就是给用户的活动账户充值。并提供一个日历签到返利的接口,用于后续对接到前端 UI 使用。
结算环节的三个关键点:
- 接收 MQ 消息:消费返利入账消息,调用活动账户额度入账接口,增加用户的可抽奖次数;
- 额度分维度更新:包括总、月、日账户额度更新(总额度、本月额度、当日额度),对应签到"每天一次"的约束可以在日额度层面做校验;
- MQ 消息过滤:消费端只处理返利类型为sku 类型的返利,其他类型(如积分)直接忽略,留给后续扩展;
- 日历签到接口:对外提供一个日历签到返利接口,用于后续前端 UI 对接(对应 web 第5节:对接联调额度签到权重接口)。
由此,"用户行为返利"的完整闭环是:入口接口入账(第 22 节)→ MQ 异步结算(第 23 节)→ 前端日历签到展示。
八、扩展:通用返利 RPC 接口对接支付返利
签到只是返利的行为之一。为了支撑外部业务系统接入,大营销平台还提供了通用返利 RPC 接口,详见 第3节:RPC接口对接支付返利:
通过在大营销 big-market 新增提供的通用返利 RPC 接口,由 OpenAI 服务 chatgpt-data 系统在支付完成接收到回调消息后进行对接完成返利动作。
用户下单完成支付后,会接收到支付消息,之后调用大营销提供的返利接口进行返利。返利为;积分和抽奖次数。
这一扩展正好验证了本节领域设计的"预留扩展点"价值:返利领域抽象的是"行为类型"而非具体行为,签到用 HTTP/应用接口触达,支付返利用 RPC 接口触达,二者的核心逻辑(配置组装 → 聚合事务入库 → MQ 发送 → 任务兜底)完全复用。积分类型的返利则进一步由 积分领域调额服务 对接返利异步消息完成积分额度增加。
九、面试要点速览
围绕本节内容,最值得沉淀的面试问答(均可在 notes.md 找到完整讨论):
MQ 消息发送失败怎么处理?数据库操作与 MQ 无法同事务,采用"业务记录 + 任务记录同事务入库,先发一次 MQ,失败由任务扫描补偿发送"的方案,保证最终一致。
生产者可能多次发送同一个 MQ,怎么保证不重复入账?消息携带唯一业务 ID(如返利订单 ID),消费端查返利记录表判断幂等,重复即丢弃。
多机部署下定时任务重复捞消息怎么办?抢占锁机制:谁抢到锁谁执行,失败删锁重试,支持重入,锁带失效时间防止持锁者宕机。
签到返利为什么要用 MQ 解耦?除了削峰填谷,更核心的是接口快速响应 + 下游异步结算 + 失败可补偿的一致性设计;签到动作本身低并发,但"行为 → 入账 → 结算"的链路需要可靠解耦。
签到每天只返利一次,幂等怎么实现?每天的行为记录生成唯一订单 ID,返利订单表按唯一 ID 去重,同时日额度账户更新也会拦截超限。
小结
本节「用户行为返利入账」是大营销平台返利链路的入口,其技术骨架可以浓缩为一句话:一个聚合(返利订单 + 任务记录同事务入库)、一条消息(MQ 异步通知)、一层兜底(任务扫描补偿)。理解并落地这套设计,你就掌握了分布式环境下"本地事务 + 消息异步 + 任务补偿"这一组最核心的最终一致性手段——这也是它成为面试高频考点的根本原因。
- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
相关推荐
gearmand客户端开发指南:从零开始构建高效任务提交应用
gearmand客户端开发指南:从零开始构建高效任务提交应用 gearmand是一款功能强大的分布式任务队列系统,能够帮助开发者轻松构建高效的任务处理应用。本文
文档教程后端大营销平台微服务对接:基于 Nacos+Dubbo 的 RPC 支付返利接口设计与实现
大营销平台微服务对接:基于 Nacos+Dubbo 的 RPC 支付返利接口设计与实现 导读 本文聚焦《大营销平台系统》外部对接阶段的第 3 节核心内容:在 b
文档教程后端《大营销平台系统》积分领域调额服务:调额接口抽象与行为发奖异步链路设计
《大营销平台系统》积分领域调额服务:调额接口抽象与行为发奖异步链路设计 本文围绕《大营销平台系统设计实现》营销服务第 26 节「积分领域调额服务」展开,讲解积分
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考