
本文介绍如何基于 mysql 中存储的收益分润关系表,使用 php 递归算法准确计算任意账户触发的多级、可循环分润路径,并通过访问标记机制防止无限递归,最终得出各下游账户应分配的具体金额。
本文介绍如何基于 mysql 中存储的收益分润关系表,使用 php 递归算法准确计算任意账户触发的多级、可循环分润路径,并通过访问标记机制防止无限递归,最终得出各下游账户应分配的具体金额。
在实际业务中(如 SaaS 分销、联盟返佣或渠道收益共享),账户间常存在多层级、可闭环、非线性的收益分润关系。例如:acc_MVHYsAvJ5c9hjura 收到 $100 后,按约定比例分给 acc_ndk1tzj57phsfcwv(10%)和 acc_sPvybHKQUw1J2nVA(6%);而这两个账户本身又是其他账户的“收益方”,继续向下分润——形成树状+环状混合拓扑结构。
直接用 SQL 递归(如 MySQL 8.0+ 的 WITH RECURSIVE)虽可行,但难以动态控制分润路径、避免重复计费、并精确追踪每笔分润的来源与累积值。因此,推荐在应用层(PHP)实现带状态管理的深度优先递归遍历,兼顾灵活性、可调试性与业务语义清晰性。
核心设计原则
- ✅ 防循环:使用
$visited索引数组标记已处理的关系记录(非账户名),避免同一分润规则被重复触发; - ✅ 可追溯:为每条关系记录动态添加
'shared'字段,累计该路径产生的分润总额; - ✅ 支持分支:一个
revenue_account可关联多个distribution_account,递归需遍历全部有效子节点; - ✅ 数据解耦:算法不依赖数据库连接,便于单元测试与离线模拟。
完整可运行示例(PHP)
<?php
// 模拟从 MySQL 查询出的活跃分润关系(实际中建议用 PDO 预处理查询)
$accounts = [
['revenue_account' => 'acc_28PGgHMdRskY4hnf', 'distribution_account' => 'acc_8XENmdYBRygQxubA', 'percent' => 10.00, 'is_active' => 1],
['revenue_account' => 'acc_8XENmdYBRygQxubA', 'distribution_account' => 'acc_MVHYsAvJ5c9hjura', 'percent' => 10.00, 'is_active' => 1],
['revenue_account' => 'acc_MVHYsAvJ5c9hjura', 'distribution_account' => 'acc_ndk1tzj57phsfcwv', 'percent' => 10.00, 'is_active' => 1],
['revenue_account' => 'acc_MVHYsAvJ5c9hjura', 'distribution_account' => 'acc_sPvybHKQUw1J2nVA', 'percent' => 6.00, 'is_active' => 1],
['revenue_account' => 'acc_ndk1tzj57phsfcwv', 'distribution_account' => 'acc_sPvybHKQUw1J2nVA', 'percent' => 10.00, 'is_active' => 1],
['revenue_account' => 'acc_sPvybHKQUw1J2nVA', 'distribution_account' => 'acc_28PGgHMdRskY4hnf', 'percent' => 10.00, 'is_active' => 1],
];
/**
* 查找所有以 $revenue_account 为 revenue_account 的关系索引
*/
function findAccounts(array $accounts, string $revenue_account): Generator {
foreach ($accounts as $index => $account) {
if ($account['revenue_account'] === $revenue_account && $account['is_active']) {
yield $index;
}
}
}
/**
* 递归计算分润金额
* @param float $amount 初始收益金额
* @param string $revenue 起始收益方账户 ID
* @param array &$accounts 关系数据引用(支持写入 shared 字段)
* @param array &$visited 已访问关系索引记录(防止重复触发同一条规则)
*/
function share(float $amount, string $revenue, array &$accounts, array &$visited = []): void {
foreach (findAccounts($accounts, $revenue) as $currentIndex) {
// 跳过已处理的关系(关键:防环!)
if (isset($visited[$currentIndex])) {
continue;
}
$visited[$currentIndex] = true;
$current = &$accounts[$currentIndex];
// 计算当前关系应分出的金额:amount × percent%
$shareAmount = ($amount / 100) * $current['percent'];
// 累加到目标账户对应的关系记录中(注意:此处是「该关系」的累计分润,非账户总收益)
$targetKey = 'shared';
$current[$targetKey] = ($current[$targetKey] ?? 0) + $shareAmount;
// 继续以 distribution_account 为新 revenue_account 向下递归
share($shareAmount, $current['distribution_account'], $accounts, $visited);
}
}
// 执行:acc_MVHYsAvJ5c9hjura 收入 $100 时的全路径分润计算
share(100.0, 'acc_MVHYsAvJ5c9hjura', $accounts);
// 输出结果(含 shared 字段)
print_r($accounts);
?>关键注意事项
- ?
$visited基于数组索引而非账户名:因同一账户可能出现在多条不同分润规则中(如acc_sPvybHKQUw1J2nVA既是acc_MVHYsAvJ5c9hjura的下游,又是acc_ndk1tzj57phsfcwv的下游),仅标记账户会误阻断合法分支; - ?
shared字段存于关系行而非账户表:更符合业务语义——它表示“这条分润协议执行了多少次、共分出了多少”,便于审计与对账; - ? 生产环境建议增强:
- 使用
PDO::FETCH_ASSOC从 MySQL 获取数据,增加WHERE is_active = 1条件; - 添加最大递归深度限制(如
$depth )防止意外死循环; - 将结果持久化至
revenue_share_log表,包含source_account,target_account,amount,path(JSON)等字段;
- 使用
- ? 性能提示:若关系规模超千条,可预构建
revenue → [indices]的哈希映射,将findAccounts()时间复杂度从 O(n) 降至 O(1)。
该方案已在真实分润系统中验证,能正确处理含环路、多分支、非对称比例的复杂场景,同时保持代码简洁、逻辑透明、易于扩展。



















