
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 邀请关系怎么设计才不会被刷邀请关系是这个系统的地基。我用的表结构大致如下字段类型说明idbigint主键inviter_idbigint邀请人用户IDinvitee_idbigint被邀请人用户IDinvite_codevarchar邀请码channelvarchar渠道标记bind_timedatetime绑定时间valid_statustinyint是否有效0待支付 1有效 2失效source_order_idbigint首单订单IDvalid_timedatetime生效时间这里有一个容易被忽略的字段source_order_id。它用于把邀请关系和首单订单绑定后续判断“有效人数”直接按这个订单的状态走。换句话说B 注册时先插入一条valid_status0的关系记录等 B 支付首单成功后再通过事件把关系更新为valid_status1。这样天然避免了“只注册不交易”的无效人头。为了防止同一个用户被重复绑定我在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 合规注意事项裂变和传销的界限最后一定要说合规。推三免单本身不是法律问题但如果活动规则出现“多级返利、收益来自人头、无实质商品或服务”就会变成法律风险。我在项目开发中只要发现需求涉及“三级分销”“无限级佣金”就会明确拒绝并提示产品调整。合规上我坚持三条利益必须与真实商品交易挂钩不能单纯按人头付费。层级不能超过一级下级消费产生收益不能往上无限传递。用户协议中明确禁止通过外挂、批量账号刷单保留追回奖励的权利。做到这三点至少能把“营销工具”和“风险模式”的边界划清楚。系统开发人员不仅是把需求实现出来还要能从业务合规角度提出建议。以上是我做推三免单裂变模式系统开发时的主要经验。这个项目真正花时间的不是活动逻辑而是分布式环境下如何保证订单、支付、结算三者的一致性。如果你也要做同类系统建议先从核心业务单据设计入手把邀请关系、订单状态、结算流水这三张表打磨到极致再逐步拆分布式别一上来就上全套微服务。另外把风控和合规纳入必须项不要等出了问题再补这点我用真金白银换过教训。