1. 项目概述:推三免单裂变模式到底要做什么
1.1 拆解“推三免单”的业务模型
老用户A邀请B、C、D三个新用户,B/C/D完成首单支付后,A获得免单权益。系统需要在“邀请关系建立”“订单支付”“权益发放”三个环节分别做校验和记录。核心流程如下:
- A生成邀请链接/海报,系统记录邀请码。
- B通过链接注册并绑定A为推荐人,同步埋点记录来源渠道。
- B下单支付后,订单中心触发“是否满足推三免单条件”的事件。
- 活动引擎统计A名下有效邀请人数,人数达到3且订单满足门槛后,生成免单奖励单。
- 结算中心把奖励单转换成退款/余额/红包,进入发放队列。
这里最容易被误解的地方是“免单”时机。很多人以为用户邀请满三个人就能立刻免单,但实际上如果B下单后又退货,A名下这个“有效人数”就要回滚。所以在设计上,我建议把有效邀请状态与订单生命周期绑定,而不是与用户注册状态绑定。注册只能算“绑定”,订单“已支付且超过售后期”才算是“有效人头”。
1.2 这个模式适合什么场景,不适合什么场景
推三免单非常适合客单价不高、毛利空间尚可、复购属性强的品类,比如零食、日用品、课程、会员卡。对用户来说,邀请三个朋友就能回本,门槛不低也不高;对平台来说,用一次免单成本换来三个新用户的首单和后续复购,获客成本通常比广告投放低。
但如果你做的是高客单价低频产品,比如装修、大件家电,我不建议直接上这个模式。高客单价用户决策周期长,邀请动作很难因为一次免单被激活,反而容易被用户当成“拉人头”;另外,免单成本会吃掉大量毛利,如果平台没有后续复购产品承接,活动只能是一锤子买卖。
我在项目里给产品部提了一条规则:活动开始时只允许“阶梯式免单”,也就是按邀请人数分档发放权益,而不是一次性免全单。比如邀请1人得10元券,邀请2人得30元券,邀请3人免单金额上限100元,超过部分由用户自行补差。这样即使后续订单退款,平台损失也更可控。
1.3 从一句需求到一张系统蓝图
产品经理最开始给的描述只有一句“用户拉三个人,平台给免单”,但落地时牵扯到关系链、订单、支付、结算、风控、活动配置多个子系统。这也是我为什么要把标题里的“系统开发”四个字单独拿出来说一遍:这个项目真正难的不是营销创意,而是把创意变成一套可并发、可对账、可追溯的工程系统。
我的拆解方法是从“三个核心问题”入手:
- 怎么确定“谁邀请的谁”?对应邀请关系模块。
- 怎么确定“达到免单条件”?对应活动引擎和订单状态机。
- 怎么把钱安全地发出去?对应结算服务和聚合支付网关。
把这三个问题拆清楚后,再去做架构和技术选型,就不会被“分布式”“微服务”这些词带偏。很多失败项目不是技术不行,而是业务模型没想清楚就开始写代码,最后返工成本极高。
2. 整体架构设计与技术选型
2.1 从单体到分布式:为什么不用一个项目写完
推三免单虽然看起来是一个活动,但它实际跨越了用户、营销、订单、支付、结算、消息通知六大子系统。如果放在一个单体服务里,初期开发很快,但很容易出现三个问题:
- 订单模块和结算模块互相调用,数据库连接抢占严重。
- 免单活动规则一变,就要动订单主流程代码,回归测试成本高。
- 支付回调高并发时,营销服务被拖垮,连带主站下单失败。
所以这个项目从一开始就按领域拆服务。我使用了 Spring Cloud Alibaba 这套 Java 技术栈,核心服务包括用户服务、活动引擎、订单中心、聚合支付网关、结算服务、通知中心。每个服务独立数据库,服务之间通过 OpenFeign 调用,异步场景走 RocketMQ。
为什么选 Java?这个项目需要处理并发、事务、对账,Java 生态在分布式事务、消息中间件、监控链路方面沉淀比较成熟。我团队主要技术栈也是 Java,选择 Java 更多是“团队认知成本最低”的考虑,而不是 Java 在所有方面都最优。如果团队更擅长 Go,只要能把分布式一致性处理好,同样可以胜任。
2.2 聚合支付网关:一个入口收编微信/支付宝
免单活动离不开支付和退款。为了不把微信、支付宝、银联的差异散落在订单中心,我单独做了一个聚合支付网关。网关对外只暴露统一接口,比如createPay(orderNo, amount, channel)、refund(originalOrderNo, amount, reason),内部再根据渠道参数封装不同的 SDK 调用。
这样做的好处有三个。第一,支付渠道变更不影响业务层。第二,退款、查询、对账可以统一复用一套流程。第三,回调统一收敛到网关,再按订单号分发到业务服务,方便追踪问题。
我当时在设计网关时,第一版只做了微信支付和支付宝,后来接一个银行聚合渠道时,只改了网关内部适配器,订单中心和活动引擎完全没动。所以如果你也要做类似系统,建议哪怕第一版只有微信支付,也把“渠道层”抽象出来,不要直接在订单服务里写微信 SDK。
2.3 基础设施与部署形态
基础设施方面,我用了 MySQL 8.0 + Redis 6 + RocketMQ 4.9 + XXL-Job。MySQL 存业务流水,Redis 存邀请关系缓存、分布式锁、活动计数器,RocketMQ 解耦订单支付成功后的活动触发,XXL-Job 做定时对账和超时关单。
部署上,最开始我是所有服务打成镜像放在一台 8C16G 的测试服务器上,后来压测发现支付回调服务占用 CPU 很严重,就把聚合支付网关单独拆到一台 4C8G 的实例,其余服务共用另外两台。整个系统在高并发时段的表现,跟服务拆分粒度直接相关。但我不建议为了分布式而分布式,如果日活只有几千,单体加 Redis 也能撑住。
技术选型可以这样归纳:
| 场景 | 选型 | 原因 |
|---|---|---|
| 微服务框架 | Spring Cloud Alibaba | 服务发现、配置中心、限流组件齐全 |
| 消息中间件 | RocketMQ | 顺序消息、事务消息、重试机制适合交易场景 |
| 缓存/分布式锁 | Redis | 高性能、原子操作,适合计数器与锁 |
| 任务调度 | XXL-Job | 分布式调度,支持分片与失败重试 |
| 数据库 | MySQL | 关系型数据一致性有保障,运维成本低 |
3. 核心模块设计:邀请关系、订单状态和结算账户
3.1 邀请关系怎么设计才不会被刷
邀请关系是这个系统的地基。我用的表结构大致如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| inviter_id | bigint | 邀请人用户ID |
| invitee_id | bigint | 被邀请人用户ID |
| invite_code | varchar | 邀请码 |
| channel | varchar | 渠道标记 |
| bind_time | datetime | 绑定时间 |
| valid_status | tinyint | 是否有效:0待支付 1有效 2失效 |
| source_order_id | bigint | 首单订单ID |
| valid_time | datetime | 生效时间 |
这里有一个容易被忽略的字段:source_order_id。它用于把邀请关系和首单订单绑定,后续判断“有效人数”直接按这个订单的状态走。换句话说,B 注册时先插入一条valid_status=0的关系记录,等 B 支付首单成功后,再通过事件把关系更新为valid_status=1。这样天然避免了“只注册不交易”的无效人头。
为了防止同一个用户被重复绑定,我在invitee_id上建了唯一索引。同时,校验邀请关系时会加一个条件:inviter_id != invitee_id,避免用户用自己的邀请码邀请自己。黑产常使用同一设备、同一手机号注册多个账号,所以除了唯一索引,还必须校验设备指纹和支付实名信息。这个后面“防刷”部分会细讲。
3.2 订单状态机:支付、售后、免单权益的联动
订单中心的状态不能只有“待支付、已支付、已发货、已完成”。在做推三免单以后,订单状态机和活动状态必须联动,我引入了“活动关联单号”和“是否计入裂变”两个字段。
订单状态流转如下:
- 下单锁定库存:状态为“待支付”,此时不触发裂变。
- 支付成功回调:状态为“已支付”,发送
ORDER_PAY_SUCCESS事件,活动引擎收到后尝试激活邀请关系。 - 发货/收货:状态继续推进,但不改变活动有效性。
- 发生退款:状态变为“退款中/已退款”,活动引擎收到退款事件后,将对应邀请关系置为失效,重新计算邀请人有效人数。
问题在于“重新计算”不是简单的减一。如果 A 邀请了 B、C、D 三个人,B 退款了,A 的有效人数从 3 变成 2,但免单权益可能已经发下去了。所以在发放免单权益时,我建议不要立即发放,而是延迟到“订单确认收货 7 天无售后”之后。这样虽然用户体感会差一点,但能大幅减少退款纠纷。
3.3 结算账户与免单权益的发放
免单权益不是直接改订单金额,而是通过结算中心生成一条“奖励流水”。我在用户账户体系里增加了一个wallet_balance字段,并配套一张account_change_log流水表。
发放逻辑如下:
- 活动引擎判定达到免单条件,生成
reward_no。 - 调用结算服务,先查用户账户流水,判断是否已发放,避免重复。
- 写
account_change_log流水,更新wallet_balance。 - 发送站内信/短信/公众号模板消息,提示用户“免单权益已到账”。
这里有一个很重要的幂等设计:reward_no必须在流水表里建唯一索引。我自己在测试时遇到过不止一次,因为消费者重试导致同一笔免单权益发放两次。虽然每次更新余额前都用 Redis 锁做了保护,但最保险的还是数据库唯一索引兜底。
4. 分布式并发下的关键实现:如何防超发、防重复、防刷
4.1 并发场景拆解:三家用户同时支付
最极端的场景是 B、C、D 三个人在同一秒内完成支付,活动引擎需要把三个支付成功事件同时处理后,判断 A 的有效人数达到 3。如果处理时序不对,可能出现人数只有 2、一直不触发免单的情况。
我的处理方案是:
- 支付成功事件进入 RocketMQ,消费者按
inviter_id维度串行消费。 - 消费时先查 Redis 中的“有效人数”,再更新 MySQL。
- 更新时使用
SELECT ... FOR UPDATE锁住 A 的活动计数行。 - 当计数达到阈值后,调用结算服务生成奖励单。
很多人一听到“按 inviter_id 串行消费”,就担心吞吐量不够。实际项目里,一个用户同时被三个以上朋友邀请的概率很低,而且同一时刻发生支付的数量有限,RocketMQ 支持按 key 做顺序消息,性能完全够用。如果未来规模变大,可以改成“预占名额 + 异步汇总”,但第一版没必要一步到位。
4.2 分布式锁与幂等:避免重复发放免单权益
在结算发放环节,我使用了 Redis 分布式锁,Key 设计为lock:reward:{userId}:{activityId},获取锁后先查流水,如果已存在则直接返回。锁的过期时间设置为 5 秒,防止服务宕机导致死锁。
但 Redis 锁不是银弹。锁过期后如果业务还在执行,另一个线程可能拿到锁进入。所以核心数据表一定要有唯一索引。我这里的兜底是account_change_log表的reward_no唯一索引,数据库抛错后再做补偿。顺序是“先查流水、再写流水、最后更新余额”,不要把写流水和更新余额的顺序搞反,否则发生回滚时很难排查。
4.3 防刷:同一设备、同一用户、同一支付身份
推三免单模式天然会被黑产盯上。我在项目里做了三层风控:
- 注册层:邀请链接携带
invite_code,记录设备 ID、IP、UA;同一设备 24 小时内最多绑定 1 个邀请关系。 - 支付层:首单支付金额必须大于活动门槛,且新用户需通过微信/支付宝实名,支付手机号与注册手机号一致。
- 行为层:同一 IP 下单超过 N 笔、同一收货地址对应多个账号、下单后迅速退款等行为,进入风控名单,不参与裂变计数。
这三层风控在最初版本只做了第一层,上线第二天就出现同一手机号注册多个账号的情况,导致无效奖励增多。后来加了支付实名校验才基本堵住。我建议做裂变系统时,风控不是“有条件再上”的功能,而是在第一版就要有基本的规则,否则后续对账会非常痛苦。
5. 数据库优化与存储过程命名规范
5.1 表结构、索引与分表策略
核心表包括用户表、邀请关系表、活动表、订单表、结算流水表。初期数据量不大时,所有表单表存储,订单表按order_no哈希分表,邀请关系表按inviter_id分表。
索引设计上,有几个关键点:
invite_relation表:(inviter_id, valid_status)联合索引,用于活动引擎快速统计有效人数。account_change_log表:reward_no唯一索引。order表:(buyer_id, create_time)联合索引,用于用户订单列表。- 所有查询都不要在索引列上做函数操作,比如
WHERE DATE(create_time)=...会导致索引失效,建议用范围查询。
5.2 存储过程该不该用?
在“系统开发 存储过程命名规则”这个热搜词里,很多朋友关心存储过程怎么命名。我的态度是:尽量不用存储过程,除非业务有强事务要求,并且团队能维护。
原因很简单,存储过程把业务逻辑放进数据库,版本管理、灰度发布、水平扩展都困难。裂变系统的核心逻辑是“支付事件驱动”,更适合放在应用层用消息队列处理,而不是在数据库里写一大段PROCEDURE。如果确实要用,比如每日结算汇总,我会把它限制在离线统计场景,不允许出现在在线交易链路上。
5.3 一套可落地的存储过程命名规则
如果团队里已经有不少存储过程,我建议统一命名,至少包含以下内容:
| 语义 | 规则 | 示例 |
|---|---|---|
| 查询类 | usp_模块_动作_描述 | usp_order_query_by_no |
| 插入类 | usp_模块_插入_描述 | usp_settle_insert_reward |
| 更新类 | usp_模块_更新_描述 | usp_activity_update_valid_count |
| 统计类 | usp_模块_统计_描述 | usp_order_stat_daily |
| 定时任务 | usp_模块_任务_描述 | usp_settle_task_daily_clear |
命名规则主要解决两个问题:让人一眼看出“它是哪个模块的、用来干什么的”,同时避免团队里同时出现p_getOrder、sp_order_get这种混乱写法。不过我还是建议把存储过程限制在离线批量任务里,在线业务尽量用应用层服务。
6. CMS系统与运营端支撑:让配置化变成现实
6.1 CMS系统在裂变活动里的角色
推三免单活动通常不会只跑一期,产品会频繁调整奖励门槛、活动时间、参与商品。如果每次调整都要改代码发版,运营会疯掉,所以需要一个 CMS 系统来承载活动配置。
我在项目中把 CMS 设计成“活动配置中心”:
- 活动基础信息:活动名称、开始/结束时间、参与人群。
- 邀请规则:需要有效邀请人数、免单金额上限、最低购买金额。
- 奖励配置:权益类型(余额/优惠券/退款)、发放时间(支付后/收货后/7天后)。
- 页面配置:活动 H5 页面引导文案、海报模板、分享标题。
CMS 不只管理内容,更重要的是管理“规则”。我把活动引擎的规则配置做成 JSON 格式存入配置表,引擎读取后解析,这样每次活动调整不用改代码,只改配置。这个思路是从内容管理系统扩展出来的,但价值比单纯管理文章大得多。
6.2 服务机器人环境感知灯光交互系统的启发:事件驱动的反馈
搜索热词里有一个“服务机器人环境感知灯光交互系统开发”,乍看和裂变系统没关系,但我在设计前端活动页反馈时借鉴了它的思路。那个系统的核心是“感知环境事件,触发灯光反馈”,我把这个理念映射成“感知业务事件,触发用户端反馈”。
比如,当用户的邀请人数从 2 变成 3 时,系统通过 WebSocket 推送一个事件,前端活动页立即显示“你已达标,免单权益到账”的动效。用户虽然是在手机上操作,但体验逻辑和环境感知灯光是一样的:事件驱动、即时反馈、状态可视化。
这种把其他领域设计模式迁移过来的思路,我觉得在系统开发里非常有用。不要只盯着自己项目的需求文档,多看看服务机器人、IOT、大屏可视化这些项目的实现,很多交互和事件处理模式都可以复用。
6.3 运营监控与数据看板
运营需要实时看到活动效果,我在 CMS 后台增加了数据看板模块,统计维度包括:
- 参与人数、邀请关系绑定数、有效邀请人数。
- 首单支付转化率、免单触发率、奖励发放金额。
- 各渠道的邀请转化效果(区分微信、朋友圈、短信)。
技术实现上,数据看板不直接查业务库,而是通过 RocketMQ 把业务事件同步到一张统计表,再由定时任务聚合。这样既不影响主流程性能,也能保证看板数据基本实时。
7. 联调测试与问题排查实录
7.1 现象:用户支付成功但免单进度没更新
这个是我在联调时遇到最多的问题。排查思路:先看 RocketMQ 消费者日志,确认ORDER_PAY_SUCCESS事件是否正常消费;再看invite_relation表,确认绑定关系是否存在;最后查活动引擎的规则配置,确认活动时间、门槛是否配置正确。
我遇到过最容易忽略的一个原因:B 用户通过 A 的邀请链接进页面后,又被别人分享的链接覆盖了邀请码。解决方式是,在用户选择“首次绑定”后,不再允许通过新链接更新 inviter_id,只记录 channel 来源。
7.2 现象:支付回调丢失,订单卡在待支付
聚合支付、本地回调都有可能延迟或丢失。我的处理方案是:
- 网关收到回调后,无论验签成功与否都先记录日志。
- 订单中心提供主动查询接口,定时任务每 5 分钟扫描“待支付且超过 10 分钟”的订单,调用渠道查询接口确认状态。
- 对账作业每天凌晨跑一次,比对本地订单和渠道账单,差异单进入人工处理列表。
不要指望回调 100% 可靠,任何支付系统都要有“主动查询 + 定时对账”兜底。
7.3 现象:免单奖励重复发放
重复发放的根因基本都是消费者重试。RocketMQ 默认会重试,如果第一次处理成功但返回异常,重试时就会重复执行。解决方式就是在结算流水表加reward_no唯一索引,再配合 Redis 锁。如果发现重复流水,需要先人工核对订单和结算状态,再进行冲正。
7.4 合规注意事项:裂变和传销的界限
最后一定要说合规。推三免单本身不是法律问题,但如果活动规则出现“多级返利、收益来自人头、无实质商品或服务”,就会变成法律风险。我在项目开发中只要发现需求涉及“三级分销”“无限级佣金”就会明确拒绝并提示产品调整。
合规上我坚持三条:
- 利益必须与真实商品交易挂钩,不能单纯按人头付费。
- 层级不能超过一级,下级消费产生收益不能往上无限传递。
- 用户协议中明确禁止通过外挂、批量账号刷单,保留追回奖励的权利。
做到这三点,至少能把“营销工具”和“风险模式”的边界划清楚。系统开发人员不仅是把需求实现出来,还要能从业务合规角度提出建议。
以上是我做推三免单裂变模式系统开发时的主要经验。这个项目真正花时间的不是活动逻辑,而是分布式环境下如何保证订单、支付、结算三者的一致性。如果你也要做同类系统,建议先从核心业务单据设计入手,把邀请关系、订单状态、结算流水这三张表打磨到极致,再逐步拆分布式,别一上来就上全套微服务。另外,把风控和合规纳入必须项,不要等出了问题再补,这点我用真金白银换过教训。