项目概览
一套把“用户点击”与“真实到账”连接起来的商业化资金系统:十二个外部渠道的推广链接、用户归因、订单回流、佣金计算、资金发放与逆向扣回,被收敛到同一个模型中。
返利属于 CPS 分销业务。用户从站内点击商品并跳转到外部渠道下单,渠道在数天或数周后回传订单,系统再根据不断变化的订单状态计算返利并发放到用户余额。链路中任何一个环节静默失败,最终都会表现为用户没有收到钱。
我负责佣金计算与资金发放、全渠道归因与转链体系、渠道接入框架,以及建立在返利链路上的营销玩法。
| 维度 | 规模 |
|---|---|
| 覆盖渠道 | 12 个电商与本地生活渠道,包含同渠道下的多套归因体系与多条业务线 |
| 工程结构 | 8 个 Maven 模块 |
| 服务接口 | 48 个客户端接口与 7 个内部 RPC 服务 |
| 异步调度 | 25 个分布式任务,其中 19 个渠道同步任务、6 个结算与风控任务 |
| 数据模型 | 21 张核心表,覆盖订单、报文快照、维权、分佣、转账、归因、收益汇总和营销玩法 |
业务难点
渠道口径无法天然统一
不同渠道对佣金、补贴、服务费、维权扣减和结算状态的定义各不相同。有的同时提供预估与实际佣金,有的补贴拥有独立结算状态,有的需要先扣技术服务费,还有的不能直接根据费率反算。系统必须允许渠道保留差异,又不能让差异渗透到整条资金流程。
订单是长周期流转
一笔订单会沿“已付款 → 确认收货 → 待结算 → 已结算”流转数周,佣金在过程中持续重算,也可能在任意时点退款。系统既要跟上渠道变化,又不能把中间状态误判成不可恢复的终态。
每一个数字都是真实资金
多发需要追回,少发会引发投诉,重复发放会形成资损。因此每次写库、计算和转账都必须回答:如果同一动作再次执行,结果是否仍然正确。
系统架构
整条链路被拆成六个稳定阶段,并由小时级调度串联:
渠道订单同步
→ 渠道报文清洗为统一订单模型
→ 订单落库与原始报文快照
→ 实时佣金重算与正向/逆向分佣单
→ 资金发放、状态更新和到账通知
→ 退款扣回、分次补扣与日结对账
新增渠道只需要在同步、清洗、计算和归因层增加对应实现,不修改既有核心编排逻辑。
核心设计决策
1. 用一个计算模型容纳十二种计价口径
将“渠道口径”和“资金流程”彻底分开。流程层只判断订单当前是否允许返利以及应返金额;每个渠道的策略独立处理字段优先级、补贴门槛、服务费和维权扣减。
所有渠道共用风控、退款状态、分佣状态和比例配置等前置判断。金额全链路使用“分”为整数单位,比例计算统一向下取整。涉及规则切换时按订单下单时间做灰度,而不是根据当前时间判断,保证历史订单重复计算时结果不会漂移。
2. 假设资金链路的每一步都会重复执行
| 风险 | 处理方式 |
|---|---|
| 重复转账 | 先写转账流水取得业务单号,再调用支付;重试复用同一单号 |
| 并发写订单 | 使用版本号乐观锁,金额、状态和完成时间均未变化时直接跳过 |
| 多实例重复发钱 | 计算、发放和扣回阶段分别使用带过期时间的分布式锁 |
| 事务相互影响 | 主资金流程使用独立事务,营销奖励通过新事务隔离 |
| 提前发放 | 渠道结算冷却期和风控检查都通过后才允许转账 |
| 超额扣回 | 必须存在成功的正向发放,扣款不能超过已发金额 |
| 余额不足 | 按递增序号生成分次补扣记录,直到剩余金额收敛 |
| 无限重试 | 设置重试上限并接入全链路异常告警 |
分佣状态统一收敛为八种状态,并在枚举中定义正向、终态和待扣状态集合,避免核心流程散落状态魔法值。
3. 修复“临时失败结论永久冻结返利”
计算层曾为了减少重复工作,发现订单已有失败原因时直接复用;结算层又会把计算失败文案写回订单。两段逻辑组合后,“返利比例尚未配置”这种临时结论一旦入库就会永久生效,运营后来补齐配置,历史订单仍不会重新计算。
我对长周期订单渠道显式取消这条短路,强制根据最新状态重算。同时移除“佣金为零等于已退款”的全平台终态判断,因为未结算订单佣金暂时为零是正常状态。
这个问题形成了一条后续设计原则:只有真正稳定的结果才允许缓存,失败结果默认视为可变化状态。
4. 归因正确性优先于表面可用性
转链需要同时携带渠道和用户归因标识。用户标识生成失败时,如果降级为只带渠道标识,链接仍然可用、订单也能完成,但回传后无法找到站内用户,返利会静默丢失。
因此归因标识生成采用 fail-closed:标识不可用就明确返回转链失败,让用户能够感知并重试。风控异常则采用不同策略:只有风控明确拒绝才阻断返利,风控服务暂时异常不能让全站资金链路停摆。
两种选择看似相反,判断标准相同:哪一种失败方式更容易被发现、重试和补偿。
5. 隔离共享推广位中的第三方订单
接入某渠道时发现推广位并非独占。如果归因后缀与第三方取值空间发生碰撞,其他业务的订单可能被错误归给站内用户并真实发放返利。
我重新设计归因标识格式,使双方取值空间不重叠,并在订单同步侧增加本地归属过滤,避免第三方订单进入落库、消息投递和风控上报链路。隔离前缀保持为代码约束而非普通运行时配置,防止误修改后让存量订单整体失去归因。
工程治理
报文快照作为写库闸门
渠道订单快照采用 append-only 多版本存储。处理前只比较会影响订单状态与佣金的字段,报文没有业务变化就跳过写库和重算。
某渠道商品明细返回顺序不稳定,原签名按遍历顺序拼接,导致内容相同的报文被持续判为变化。我将明细按内容生成条目后排序再签名,使比对结果与返回顺序无关;对于与主状态不同步、只会产生噪声的字段则明确排除并记录原因。
从 OFFSET 分页迁移到游标分页
订单增长后,底层中间件的 OFFSET 上限导致扫单任务失败。迁移到游标分页时额外区分“底层是否读取到原始行”和“内存过滤后是否有结果”:即使过滤结果为空,只要底层读取过数据就继续推进游标,避免任务成功结束但后续订单永远未被扫描。
收益汇总与日结对账
用户累计收益通过数据库原子自增维护,汇总更新失败不阻断主资金流程。为解决短暂不一致,日结任务根据真实分佣流水反算并修正,只扫描近期发生资金变化的用户,在正确性与任务成本之间取得平衡。
风控双向接入
订单、分佣、发放和扣回等关键节点异步上报风控,投递异常不影响主资金事务;入方向在佣金计算和资金发放前各设置一道检查,并提供补偿入口支持风控误判解除后的历史订单重算。
多渠道接入框架
渠道枚举中的稳定标识同时用于实现包、Bean 前缀和渠道路由。核心编排根据约定找到对应实现,新增渠道只需提供实现类并增加一条枚举配置,不修改既有分发逻辑。
归因侧将站内用户与渠道组合生成渠道标识,并统一保存正向和反向映射;具有特殊授权要求的渠道通过独立适配层隔离。商品侧则使用约定键名的中间模型连接转链、展示、商详和导购落库,热榜、高佣等异构物料也能复用同一上游展示逻辑。
我主导完成两个渠道从零接入,覆盖商品列表、商详、商品与任意链接转链、口令解析、活动物料、订单及维权同步。接入结论、生产实测差异和被否决方案均沉淀在交接文档中,减少后续接手与排查成本。
营销玩法
在返利主链路上建设翻倍卡、邀新任务、订单抽奖和新人补贴等玩法。以翻倍卡为例:
- 发放时快照倍数,活动配置后续变化不影响历史奖励;
- 使用乐观锁扣减库存,订单维度分布式锁防止重复绑定;
- 订单退款时根据奖励状态解绑或扣回;
- 余额不足时支持分次补扣,失败重试复用业务单号;
- 营销奖励扣回失败不阻断主佣金扣回,两笔资金按风险优先级隔离。
项目结果
- 十二个渠道共用同一套计算、归因与资金链路,新增渠道不修改既有核心编排;
- 主导两个渠道从零接入并完成生产验证;
- 资金链路具备幂等、乐观锁、分布式锁、分次补扣、余额校验、重试上限和全链路告警;
- 修复配置补齐后历史订单仍永久不返利的缺陷,以及可能造成资损的归因碰撞风险;
- 扫单链路迁移到游标分页,解除分页上限并避免过滤后空结果导致的静默漏单;
- 通过快照比对、收益汇总和日结对账降低无效写入并保证长周期资金一致性。
技术栈
Java · Spring · MyBatis-Plus · MySQL · Redis · ZooKeeper · 消息队列 · 分布式定时调度 · RPC · 动态配置中心
本文为公开版本。渠道名称、第三方接口字段、内部系统与代码结构、佣金率和具体经营数据均已省略或脱敏。