
本文只讨论工程实现表结构、结算算法、边界条件。不谈营销、不涉及任何商业推广。一、问题定义差额计酬业内常称级差的核心是下级出单时上级拿到的不是固定比例而是「自己聘阶比例」与「下级已拿比例」之间的差。形式化描述用户u有一个随时变动的聘阶rate(u)例如 0.05 / 0.10 / 0.15 / 0.20订单o的实际成交额amount(o)关系链u1 → u2 → u3 → ...u1是直接推荐人对每个上级ui其佣金 amount(o) × (rate(ui) − max(rate(u1..u(i-1))))且该值必须 ≥ 0关键约束沿链上行时取「已出现的最高聘阶」作为封顶基准否则链上会出现倒挂下级比例高于上级差额变成负数。二、表结构设计1. 用户与聘阶CREATETABLEuser_rank(user_idBIGINTNOTNULLCOMMENT用户ID,rank_idINTNOTNULLCOMMENT聘阶ID,effective_atDATETIMENOTNULLCOMMENT生效时间,expire_atDATETIMEDEFAULTNULLCOMMENT失效时间(归零制用),periodVARCHAR(7)NOTNULLCOMMENT归属周期 YYYY-MM,PRIMARYKEY(user_id,period))COMMENT用户聘阶快照表按月快照不用可变字段;要点**聘阶不要做成user.rank这样的可变单值字段**。归零制意味着聘阶按月重算历史订单必须能还原当时的聘阶否则退款冲正时算不回去。所以用「按周期快照」。 ### 2. 关系链 sqlCREATETABLEuser_relation(user_idBIGINTNOTNULL,parent_idBIGINTNOTNULLCOMMENT直接推荐人,pathVARCHAR(512)NOTNULLCOMMENT物化路径 /1/18/233/,depthINTNOTNULLDEFAULT1,bound_atDATETIMENOTNULL,PRIMARYKEY(user_id),KEYidx_parent(parent_id),KEYidx_path(path(255)))COMMENT推荐关系链物化路径便于一次查出所有上级;关系链是**只增不改**的换绑是极少数场景用新记录失效时间处理。用物化路径path查所有上级是一条LIKE而不是递归查询——在结算高峰期差别很大。 ### 3. 佣金流水核心 sqlCREATETABLEcommission_ledger(idBIGINTNOTNULLAUTO_INCREMENT,order_idBIGINTNOTNULLCOMMENT来源订单,order_item_idBIGINTDEFAULTNULL,beneficiaryBIGINTNOTNULLCOMMENT受益人,biz_typeTINYINTNOTNULLCOMMENT1差额 2返佣 3团队奖,base_amountDECIMAL(12,2)NOTNULLCOMMENT计佣基数,from_rateDECIMAL(6,4)NOTNULLCOMMENT下级已拿比例,to_rateDECIMAL(6,4)NOTNULLCOMMENT本人聘阶比例,rateDECIMAL(6,4)NOTNULLCOMMENT实际差额比例,amountDECIMAL(12,2)NOTNULLCOMMENT佣金金额,statusTINYINTNOTNULLDEFAULT0COMMENT0待结算 1已结算 2已冲正,settle_batchVARCHAR(32)DEFAULTNULL,created_atDATETIMENOTNULL,reversed_byBIGINTDEFAULTNULLCOMMENT冲正流水ID,PRIMARYKEY(id),UNIQUEKEYuk_order_beneficiary_biz(order_id,order_item_id,beneficiary,biz_type),KEYidx_settle(status,settle_batch))COMMENT佣金流水只增不改冲正走反向记录;三个设计决定值得说明 1. **from_rate/to_rate/rate三个字段都落库**。只存金额的话事后对账或客服答疑时无法解释为什么这个人只拿了 3%一线会反复找研发。 2. 2. **唯一索引order_idorder_item_idbeneficiarybiz_type**这是结算幂等的最后一道防线。重复投递的消息MQ 至少一次语义会被数据库直接挡掉而不是靠应用层判断。 3. 3. **冲正不 UPDATE而是插一条反向记录并回填reversed_by**。财务口径要求流水可追溯status只表示聚合态。## 三、结算算法function settle(order):chain query_uplines(order.buyer_id) # 按 depth 升序最多 N 层rates []max_seen 0 # 已出现的最高聘阶for u in chain:r snapshot_rate(u.user_id, order.period)diff r - max_seenif diff 0:emit(u, baseorder.amount, from_ratemax_seen,to_rater, ratediff)max_seen r # 只在真的发钱后才抬高基准# 支付通道 / 平台分成最后从总额里扣不参与差额计算四个容易写错的地方基准抬升时机max_seen必须在「真的产生差额」之后才更新。若用if diff 0或无条件更新倒挂链上会把更高聘阶的上级压成 0。金额取整差额比例相乘几乎必然出现分位以下小数。统一用「向下取整到分」并把舍去部分累计到一个platform_residue账户否则总拨出金额可能超过制度上限哪怕是 0.01 元对账时也是事故。封顶与截断若制度设定「最多往上算 N 层」chain查询就必须带depth N不能靠循环里 break——否则高并发下每单拉取全链成本失控。拨出率校验结算前先聚合本次的拨出总额与订单额超过制度上限要拒绝结算并告警不能先发再说。四、边界条件清单场景处理方式订单退款按order_id查全部status1流水逐条生成反向记录biz_type不变amount取负reversed_by互相回填部分退款按退款金额比例冲正剩余部分重算不要用「只冲最后一条」的偷懒写法聘阶月中变更user_rank按周期快照已规避若必须支持月中生效则变更时对当月已完成订单不做回溯仅对变更后订单生效归零制周期切分归零是「周期快照重建」不是「删数据」。历史commission_ledger不动关系链换绑旧记录置失效时间新记录生效已产生佣金不回滚合同层面约定并发结算靠唯一索引兜底 结算批次号做灰度可回滚跨月结算以订单支付成功时间归属周期不以结算执行时间归属五、性能与实现建议关系链用物化路径后查所有上级是索引扫描单次 O(depth)不需要递归 CTE结算走异步订单支付成功后发消息消费者按批次聚合settle_batch作为可回滚单元佣金流水表按created_at月分区历史查询走归档库金额一律DECIMAL(12,2)不要用浮点比例用DECIMAL(6,4)存避免二进制浮点误差累积对外只暴露「已结算」状态的流水待结算流水不参与提现校验六、小结差额佣金引擎的复杂度不在公式而在三件事聘阶要按周期快照、流水只增不改、结算必须幂等。这三条做对了后面无论是加归零制、加累积制还是加团队奖都只是在同一套骨架上多挂一个biz_type。反之如果把聘阶做成一个可变字段、把冲正写成UPDATE amount 0那这套引擎从第一天起就注定对不上账。