CRMEB Pro 二开避坑改一个上级推广人为什么可能把分销关系改成死循环做 CRMEB Pro 普通分销二开时很多人看到 spread_uid 字段会觉得很简单用户 A 的上级推广人就是 spread_uid要改上级那就 update 一下这个字段。但普通分销最怕的恰恰就是“只改一个字段”。如果没有校验自己不能绑自己、上级不能是自己的下级、团队分销身份不能混进普通分销、改绑后没有记录推广绑定历史最后很容易出现推广关系闭环、统计异常、订单返佣口径错乱。这篇就围绕 CRMEB Pro 普通分销里的添加分销员、修改上级推广人、清除推广关系几个动作整理一套二开时可以直接参考的安全边界。一、普通分销的核心字段普通分销里最关键的用户字段通常有几个is_promoter 是否是分销员 spread_open 是否有推广资格 spread_uid 当前上级推广人 UID spread_time 绑定上级推广人的时间 agent_level 分销员等级其中 spread_uid 是关系链核心但它不能单独理解。一个用户能不能成为别人的上级还要看状态、推广资格、分销员身份、团队分销身份等条件。否则你可能会遇到这种关系A 的上级是 B B 的上级是 C 结果后台把 C 的上级改成 A这条链就绕回来了。后续计算一级、二级推广人、推广订单、分销排行时都会变得很危险。二、后台普通分销相关接口后台分销管理相关路由里普通分销至少有这些高频入口GET /adminapi/agent/candidate_user 添加分销员候选用户列表 POST /adminapi/agent/add 添加分销员 GET /adminapi/agent/spread_user 修改上级推广员候选用户列表 PUT /adminapi/agent/spread 修改上级推广人 PUT /adminapi/agent/stair/delete_spread/:uid 清除上级推广人 PUT /adminapi/agent/stair/delete_system_spread/:uid 删除系统推广关系 PUT /adminapi/agent/cancel_promoter/:uid 取消分销员身份这几个接口看起来都是“管理分销员”但风险等级不一样添加分销员主要是打开推广员权限。修改上级推广人会改变关系链风险最高。清除上级会影响直属关系和统计展示。取消分销员身份会影响推广权限但不能乱删历史佣金。三、添加分销员不是简单打开 is_promoter添加分销员时不能只做$userServices-update($uid,[is_promoter1],uid);实际至少要校验1. 用户存在 2. 用户状态正常 3. 用户有推广资格 spread_open 1 4. 用户还不是分销员 5. 用户不是团队分销身份 6. 分销等级存在且启用简化后的业务逻辑可以这样理解/** * 添加分销员 * param int $uid 用户UID * param int $agentLevel 分销等级ID * return bool */publicfunctionaddAgent(int$uid,int$agentLevel0):bool{$userInfo$userServices-getUserInfo($uid,uid,status,spread_open,is_promoter,division_type,agent_level);if(!$userInfo){thrownewAdminException(用户不存在);}if((int)$userInfo[status]!1){thrownewAdminException(该用户状态异常不能添加为分销员);}if((int)$userInfo[spread_open]!1){thrownewAdminException(该用户没有推广资格);}if((int)$userInfo[is_promoter]1){thrownewAdminException(该用户已经是分销员);}if((int)$userInfo[division_type]!0){thrownewAdminException(团队分销身份用户不能添加为普通分销员);}returnfalse!$userServices-update($uid,[is_promoter1],uid);}这里最容易漏的是 division_type。普通分销和团队分销如果不隔离后续返佣、团队统计、上下级关系会互相污染。四、候选上级列表只能做第一层过滤后台修改上级推广人时会先提供候选推广员列表。这个列表通常会筛掉禁用用户。没有推广资格的用户。不是分销员的用户。团队分销身份用户。当前用户自己。但是注意候选列表不能替代提交校验。因为前端传参可以被绕过页面上没出现的人也可以被手动传到接口里。所以后端提交时还要再次校验。这就是为什么候选接口里即使排除了当前用户提交接口里仍然要判断if($uid$spreadUid){thrownewAdminException(上级推广人不能为自己);}前端负责体验后端负责规则。这个边界不要反过来。五、改绑上级最关键的是防止闭环修改上级推广人的核心接口可以理解成PUT /adminapi/agent/spread 参数uid, spread_uid业务层关键校验包括1. uid 和 spread_uid 都不能为空 2. 上级不能是自己 3. 当前用户必须存在 4. 新上级必须存在且状态正常 5. 新上级必须有推广资格并且是普通分销员 6. 新上级不能是团队分销身份 7. 新上级不能是当前用户的下级重点是第 7 条if($userServices-isSpreadUserChild($uid,$spreadUid)){thrownewAdminException(上级推广人不能为自己下级);}这一步是在防闭环。如果不做这个校验用户链路可能从树变成环A - B - C - A一旦关系变成环二级推广统计、团队人数、推广订单归属都可能出现重复、死循环或统计异常。六、改绑不能只改 spread_uid改绑上级时更推荐走统一方法比如return$userServices-saveUserSpreadUid($uid,$spreadUid);而不是业务层随手$userServices-update($uid,[spread_uid$spreadUid]);因为统一方法里还会做这些事情校验用户和上级是否存在。防止自己绑定自己。防止绑定自己的下级。生成完整的推广绑定数据。同步团队/分销关系表。分发推广绑定记录任务。分发好友关系记录任务。简化理解如下/** * 保存用户上级推广人 * param int $uid 当前用户UID * param int $spreadUid 上级推广人UID * return bool */publicfunctionsaveUserSpreadUid(int$uid,int$spreadUid){if($uid$spreadUid){thrownewValidateException(上级推广人不能为自己);}if($this-isSpreadUserChild($uid,$spreadUid)){thrownewValidateException(上级推广人不能为自己下级);}$data$this-getSpreadBindData($spreadUid,true);$this-dao-update($uid,$data);app()-make(DivisionRelationServices::class)-syncUserRelation($uid);UserSpreadJob::dispatch([$uid,$spreadUid]);UserFriendsJob::dispatch([$uid,$spreadUid]);returntrue;}这就是统一封装的价值不是为了少写几行代码而是为了不漏业务副作用。七、为什么要记录推广绑定历史用户改绑上级后当前关系只看 eb_user.spread_uid 当然够用但运营排查问题时经常会问这个用户什么时候绑定过上级是注册时绑定还是后台改绑之前的上级是谁为什么这笔邀请奖励发给了某个人所以改绑时要记录推广绑定历史。项目里通常通过队列任务处理UserSpreadJob::dispatch([$uid,$spreadUid]);UserFriendsJob::dispatch([$uid,$spreadUid]);这样当前关系和历史记录是两套口径eb_user.spread_uid 当前上级 eb_user_spread 历史绑定记录 好友/邀请关系任务 用于后续统计和消息触达二开时不要只想着当前字段改对了历史链路也要能追溯。八、清除上级和取消分销员不是一回事普通分销里还有两个容易混的动作清除上级推广人处理 spread_uid 取消分销员身份处理 is_promoter清除上级推广人是让这个用户不再挂某个上级。它影响的是“我属于谁”。取消分销员身份是让这个用户不再具备推广员权限。它影响的是“别人能不能属于我”。这两个动作不要混成一个按钮更不要为了取消分销员身份就把历史订单、历史佣金流水、已提现记录一起清掉。正确边界应该是当前推广权限可以关闭。当前上级关系可以按规则清除。历史佣金流水不应被删除。历史订单返佣快照不应被清空。下级用户怎么处理要有明确规则不要隐式连带。九、后台用户详情里也要防止乱改除了分销管理页后台用户详情也可能编辑上级推广人。这里同样要做防线/** * 校验后台用户详情编辑时是否允许修改上级推广人 * param int $uid 当前用户UID * param array $userInfo 当前用户信息 * param int $spreadUid 新上级推广人UID0表示清除上级 * return void */protectedfunctioncheckUserEditSpreadUid(int$uid,$userInfo,int$spreadUid):void{if(in_array((int)$userInfo[division_type],[1,2,3],true)){thrownewAdminException(团队分销身份用户不能在用户详情中修改上级推广人);}if($uid$spreadUid){thrownewAdminException(上级推广人不能为自己);}if($this-isSpreadUserChild($uid,$spreadUid)){thrownewAdminException(上级推广人不能为自己下级);}}同一条业务规则不能只在一个入口生效。否则 A 页面拦住了B 页面又能绕进去。十、二开测试清单做普通分销改绑类需求时建议至少测这些场景1. 普通用户添加为分销员 2. 禁用用户不能添加为分销员 3. 团队分销身份不能添加为普通分销员 4. 分销员不能把自己设置为上级 5. 分销员不能把自己的下级设置为上级 6. 上级必须是有效分销员 7. 改绑后 spread_uid 和 spread_time 正确变化 8. 改绑后推广绑定历史有记录 9. 改绑后普通分销统计口径正常 10. 清除上级不会删除历史订单和佣金流水如果涉及线上数据改绑前最好先记录用户当前关系、订单快照和佣金流水。尤其是大客户环境不要用临时 SQL 批量改 spread_uid。总结CRMEB Pro 普通分销二开里spread_uid 看起来只是一个上级字段但它背后连接的是推广链路、分销员身份、绑定历史、好友关系、订单返佣和统计报表。真正安全的改绑逻辑至少要做到不能绑自己 不能绑自己的下级 不能把团队分销身份混进普通分销 不能只改 spread_uid 而不记录历史 不能因为取消身份就清空历史佣金和订单一句话普通分销改绑不是一个 update是一次关系链校验和同步。二开时把这些边界守住后面统计和返佣才不会出奇怪问题。
CRMEB Pro 二开避坑:改一个上级推广人,为什么可能把分销关系改成死循环?
CRMEB Pro 二开避坑改一个上级推广人为什么可能把分销关系改成死循环做 CRMEB Pro 普通分销二开时很多人看到 spread_uid 字段会觉得很简单用户 A 的上级推广人就是 spread_uid要改上级那就 update 一下这个字段。但普通分销最怕的恰恰就是“只改一个字段”。如果没有校验自己不能绑自己、上级不能是自己的下级、团队分销身份不能混进普通分销、改绑后没有记录推广绑定历史最后很容易出现推广关系闭环、统计异常、订单返佣口径错乱。这篇就围绕 CRMEB Pro 普通分销里的添加分销员、修改上级推广人、清除推广关系几个动作整理一套二开时可以直接参考的安全边界。一、普通分销的核心字段普通分销里最关键的用户字段通常有几个is_promoter 是否是分销员 spread_open 是否有推广资格 spread_uid 当前上级推广人 UID spread_time 绑定上级推广人的时间 agent_level 分销员等级其中 spread_uid 是关系链核心但它不能单独理解。一个用户能不能成为别人的上级还要看状态、推广资格、分销员身份、团队分销身份等条件。否则你可能会遇到这种关系A 的上级是 B B 的上级是 C 结果后台把 C 的上级改成 A这条链就绕回来了。后续计算一级、二级推广人、推广订单、分销排行时都会变得很危险。二、后台普通分销相关接口后台分销管理相关路由里普通分销至少有这些高频入口GET /adminapi/agent/candidate_user 添加分销员候选用户列表 POST /adminapi/agent/add 添加分销员 GET /adminapi/agent/spread_user 修改上级推广员候选用户列表 PUT /adminapi/agent/spread 修改上级推广人 PUT /adminapi/agent/stair/delete_spread/:uid 清除上级推广人 PUT /adminapi/agent/stair/delete_system_spread/:uid 删除系统推广关系 PUT /adminapi/agent/cancel_promoter/:uid 取消分销员身份这几个接口看起来都是“管理分销员”但风险等级不一样添加分销员主要是打开推广员权限。修改上级推广人会改变关系链风险最高。清除上级会影响直属关系和统计展示。取消分销员身份会影响推广权限但不能乱删历史佣金。三、添加分销员不是简单打开 is_promoter添加分销员时不能只做$userServices-update($uid,[is_promoter1],uid);实际至少要校验1. 用户存在 2. 用户状态正常 3. 用户有推广资格 spread_open 1 4. 用户还不是分销员 5. 用户不是团队分销身份 6. 分销等级存在且启用简化后的业务逻辑可以这样理解/** * 添加分销员 * param int $uid 用户UID * param int $agentLevel 分销等级ID * return bool */publicfunctionaddAgent(int$uid,int$agentLevel0):bool{$userInfo$userServices-getUserInfo($uid,uid,status,spread_open,is_promoter,division_type,agent_level);if(!$userInfo){thrownewAdminException(用户不存在);}if((int)$userInfo[status]!1){thrownewAdminException(该用户状态异常不能添加为分销员);}if((int)$userInfo[spread_open]!1){thrownewAdminException(该用户没有推广资格);}if((int)$userInfo[is_promoter]1){thrownewAdminException(该用户已经是分销员);}if((int)$userInfo[division_type]!0){thrownewAdminException(团队分销身份用户不能添加为普通分销员);}returnfalse!$userServices-update($uid,[is_promoter1],uid);}这里最容易漏的是 division_type。普通分销和团队分销如果不隔离后续返佣、团队统计、上下级关系会互相污染。四、候选上级列表只能做第一层过滤后台修改上级推广人时会先提供候选推广员列表。这个列表通常会筛掉禁用用户。没有推广资格的用户。不是分销员的用户。团队分销身份用户。当前用户自己。但是注意候选列表不能替代提交校验。因为前端传参可以被绕过页面上没出现的人也可以被手动传到接口里。所以后端提交时还要再次校验。这就是为什么候选接口里即使排除了当前用户提交接口里仍然要判断if($uid$spreadUid){thrownewAdminException(上级推广人不能为自己);}前端负责体验后端负责规则。这个边界不要反过来。五、改绑上级最关键的是防止闭环修改上级推广人的核心接口可以理解成PUT /adminapi/agent/spread 参数uid, spread_uid业务层关键校验包括1. uid 和 spread_uid 都不能为空 2. 上级不能是自己 3. 当前用户必须存在 4. 新上级必须存在且状态正常 5. 新上级必须有推广资格并且是普通分销员 6. 新上级不能是团队分销身份 7. 新上级不能是当前用户的下级重点是第 7 条if($userServices-isSpreadUserChild($uid,$spreadUid)){thrownewAdminException(上级推广人不能为自己下级);}这一步是在防闭环。如果不做这个校验用户链路可能从树变成环A - B - C - A一旦关系变成环二级推广统计、团队人数、推广订单归属都可能出现重复、死循环或统计异常。六、改绑不能只改 spread_uid改绑上级时更推荐走统一方法比如return$userServices-saveUserSpreadUid($uid,$spreadUid);而不是业务层随手$userServices-update($uid,[spread_uid$spreadUid]);因为统一方法里还会做这些事情校验用户和上级是否存在。防止自己绑定自己。防止绑定自己的下级。生成完整的推广绑定数据。同步团队/分销关系表。分发推广绑定记录任务。分发好友关系记录任务。简化理解如下/** * 保存用户上级推广人 * param int $uid 当前用户UID * param int $spreadUid 上级推广人UID * return bool */publicfunctionsaveUserSpreadUid(int$uid,int$spreadUid){if($uid$spreadUid){thrownewValidateException(上级推广人不能为自己);}if($this-isSpreadUserChild($uid,$spreadUid)){thrownewValidateException(上级推广人不能为自己下级);}$data$this-getSpreadBindData($spreadUid,true);$this-dao-update($uid,$data);app()-make(DivisionRelationServices::class)-syncUserRelation($uid);UserSpreadJob::dispatch([$uid,$spreadUid]);UserFriendsJob::dispatch([$uid,$spreadUid]);returntrue;}这就是统一封装的价值不是为了少写几行代码而是为了不漏业务副作用。七、为什么要记录推广绑定历史用户改绑上级后当前关系只看 eb_user.spread_uid 当然够用但运营排查问题时经常会问这个用户什么时候绑定过上级是注册时绑定还是后台改绑之前的上级是谁为什么这笔邀请奖励发给了某个人所以改绑时要记录推广绑定历史。项目里通常通过队列任务处理UserSpreadJob::dispatch([$uid,$spreadUid]);UserFriendsJob::dispatch([$uid,$spreadUid]);这样当前关系和历史记录是两套口径eb_user.spread_uid 当前上级 eb_user_spread 历史绑定记录 好友/邀请关系任务 用于后续统计和消息触达二开时不要只想着当前字段改对了历史链路也要能追溯。八、清除上级和取消分销员不是一回事普通分销里还有两个容易混的动作清除上级推广人处理 spread_uid 取消分销员身份处理 is_promoter清除上级推广人是让这个用户不再挂某个上级。它影响的是“我属于谁”。取消分销员身份是让这个用户不再具备推广员权限。它影响的是“别人能不能属于我”。这两个动作不要混成一个按钮更不要为了取消分销员身份就把历史订单、历史佣金流水、已提现记录一起清掉。正确边界应该是当前推广权限可以关闭。当前上级关系可以按规则清除。历史佣金流水不应被删除。历史订单返佣快照不应被清空。下级用户怎么处理要有明确规则不要隐式连带。九、后台用户详情里也要防止乱改除了分销管理页后台用户详情也可能编辑上级推广人。这里同样要做防线/** * 校验后台用户详情编辑时是否允许修改上级推广人 * param int $uid 当前用户UID * param array $userInfo 当前用户信息 * param int $spreadUid 新上级推广人UID0表示清除上级 * return void */protectedfunctioncheckUserEditSpreadUid(int$uid,$userInfo,int$spreadUid):void{if(in_array((int)$userInfo[division_type],[1,2,3],true)){thrownewAdminException(团队分销身份用户不能在用户详情中修改上级推广人);}if($uid$spreadUid){thrownewAdminException(上级推广人不能为自己);}if($this-isSpreadUserChild($uid,$spreadUid)){thrownewAdminException(上级推广人不能为自己下级);}}同一条业务规则不能只在一个入口生效。否则 A 页面拦住了B 页面又能绕进去。十、二开测试清单做普通分销改绑类需求时建议至少测这些场景1. 普通用户添加为分销员 2. 禁用用户不能添加为分销员 3. 团队分销身份不能添加为普通分销员 4. 分销员不能把自己设置为上级 5. 分销员不能把自己的下级设置为上级 6. 上级必须是有效分销员 7. 改绑后 spread_uid 和 spread_time 正确变化 8. 改绑后推广绑定历史有记录 9. 改绑后普通分销统计口径正常 10. 清除上级不会删除历史订单和佣金流水如果涉及线上数据改绑前最好先记录用户当前关系、订单快照和佣金流水。尤其是大客户环境不要用临时 SQL 批量改 spread_uid。总结CRMEB Pro 普通分销二开里spread_uid 看起来只是一个上级字段但它背后连接的是推广链路、分销员身份、绑定历史、好友关系、订单返佣和统计报表。真正安全的改绑逻辑至少要做到不能绑自己 不能绑自己的下级 不能把团队分销身份混进普通分销 不能只改 spread_uid 而不记录历史 不能因为取消身份就清空历史佣金和订单一句话普通分销改绑不是一个 update是一次关系链校验和同步。二开时把这些边界守住后面统计和返佣才不会出奇怪问题。