# URS推广模块达人等级向上传播更新Bug修复技术文档 ## 问题概述 ### 1. 问题描述 当某个用户的上级更新了达人指标时,这个更新没有向上传播到更高层级的上级(上上级、上上上级等),导致达人等级统计不准确。 ### 2. 影响范围 - **直接影响**:上级用户的达人等级不会自动更新 - **业务影响**:影响收益分成计算和权益分配 - **数据一致性**:团队统计数据可能不准确 ## 技术原因分析 ### 1. 当前事件流程 ``` 用户A推荐用户B → 触发UrsReferralCreatedEvent → 只更新用户A的达人等级 用户B达人等级提升 → 触发UrsTalentLevelUpEvent → 只记录日志,不更新上级 ``` ### 2. 缺失的逻辑 ``` 用户B达人等级提升 → 应该触发 → 更新用户A、用户A的上级、用户A的上上级... ``` ### 3. 代码分析 #### 3.1 UrsReferralCreatedListener.php ```php public function handle(UrsReferralCreatedEvent $event): void { // ❌ 问题:只更新直接推荐人,没有向上传播 UrsTalentService::updateUserTalent($event->referrerId); } ``` #### 3.2 UrsTalentLevelUpListener.php ```php public function handle(UrsTalentLevelUpEvent $event): void { // ❌ 问题:只记录日志,没有更新上级用户 Log::info('URS达人等级提升', [...]); } ``` ## 解决方案设计 ### 1. 核心思路 - **向上传播机制**:当用户达人等级变化时,向上遍历推荐关系链 - **批量更新**:逐级更新所有上级用户的达人等级 - **异步处理**:使用队列避免阻塞主流程 - **防重复更新**:避免同一用户被重复更新 ### 2. 技术实现方案(混合处理模式) #### 核心策略:分层处理机制 - **第1级(直接上级)**:即时同步处理,确保实时性 - **第2级及以上(上上级等)**:队列异步处理,避免阻塞 #### 实现方案:修改现有监听器 在`UrsTalentLevelUpListener`中添加分层向上传播逻辑: ```php public function handle(UrsTalentLevelUpEvent $event): void { // 1. 记录日志 Log::info('URS达人等级提升', [ 'user_id' => $event->userId, 'old_level' => $event->oldLevel, 'new_level' => $event->newLevel, 'direct_count' => $event->directCount, 'promotion_count' => $event->promotionCount, ]); // 2. 分层向上传播更新上级用户 $this->updateUpstreamTalentLevels($event->userId); } /** * 分层更新上级用户达人等级 * - 第1级:即时同步处理 * - 第2级及以上:队列异步处理 */ private function updateUpstreamTalentLevels(int $userId): void { try { // 获取URS用户ID $ursUserId = UrsUserMappingService::getMappingUrsUserId($userId); // 获取推荐关系链(向上20级) $referralChain = UrsReferralService::getReferralChain($ursUserId, 20); if (empty($referralChain)) { Log::info('用户无上级推荐关系,无需向上传播', ['user_id' => $userId]); return; } // 分层处理上级用户 foreach ($referralChain as $level => $ursReferrerId) { $referrerId = UrsUserMapping::getFarmUserIdByUrsUserId($ursReferrerId); if (!$referrerId) { Log::warning('上级用户映射不存在,跳过更新', [ 'user_id' => $userId, 'level' => $level, 'urs_referrer_id' => $ursReferrerId ]); continue; } if ($level === 1) { // 第1级(直接上级):即时同步处理 Log::info('即时更新直接上级达人等级', [ 'user_id' => $userId, 'referrer_id' => $referrerId, 'level' => $level ]); UrsTalentService::updateUserTalent($referrerId); } else { // 第2级及以上:队列异步处理 Log::info('队列异步更新上级达人等级', [ 'user_id' => $userId, 'referrer_id' => $referrerId, 'level' => $level ]); dispatch(new UpdateUpstreamTalentLevelJob($referrerId, $userId, $level)) ->onQueue('urs_talent_update'); } } Log::info('URS达人等级向上传播更新完成', [ 'user_id' => $userId, 'total_upstream_count' => count($referralChain), 'immediate_update_count' => 1, 'queued_update_count' => count($referralChain) - 1 ]); } catch (\Exception $e) { Log::error('URS达人等级向上传播更新失败', [ 'user_id' => $userId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); } } ``` #### 队列Job设计:UpdateUpstreamTalentLevelJob 创建专门的队列Job处理异步上级更新: ```php referrerId = $referrerId; $this->originalUserId = $originalUserId; $this->level = $level; // 设置队列名称 $this->onQueue('urs_talent_update'); } /** * 执行队列任务 */ public function handle(): void { try { // 防重复更新检查 $lockKey = "urs_talent_update_lock_{$this->referrerId}"; $lock = Cache::lock($lockKey, 30); if (!$lock->get()) { Log::info('URS达人等级更新被跳过(已有更新在进行)', [ 'referrer_id' => $this->referrerId, 'original_user_id' => $this->originalUserId, 'level' => $this->level ]); return; } Log::info('开始异步更新上级达人等级', [ 'referrer_id' => $this->referrerId, 'original_user_id' => $this->originalUserId, 'level' => $this->level, 'attempt' => $this->attempts() ]); // 执行达人等级更新 $result = UrsTalentService::updateUserTalent($this->referrerId); Log::info('异步更新上级达人等级成功', [ 'referrer_id' => $this->referrerId, 'original_user_id' => $this->originalUserId, 'level' => $this->level, 'new_talent_level' => $result->talentLevel, 'direct_count' => $result->directCount, 'promotion_count' => $result->promotionCount ]); } catch (\Exception $e) { Log::error('异步更新上级达人等级失败', [ 'referrer_id' => $this->referrerId, 'original_user_id' => $this->originalUserId, 'level' => $this->level, 'attempt' => $this->attempts(), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // 重新抛出异常,让队列系统处理重试 throw $e; } } /** * 任务失败处理 */ public function failed(\Throwable $exception): void { Log::error('URS达人等级异步更新最终失败', [ 'referrer_id' => $this->referrerId, 'original_user_id' => $this->originalUserId, 'level' => $this->level, 'final_error' => $exception->getMessage() ]); } } ``` ### 3. 性能优化考虑 #### 3.1 异步队列处理 ```php // 使用队列避免阻塞主流程 dispatch(new UpdateUpstreamTalentLevelsJob($userId)); ``` #### 3.2 批量更新优化 ```php // 合并相同用户的多次更新请求 $pendingUpdates = Cache::get('urs_talent_pending_updates', []); $pendingUpdates[] = $userId; Cache::put('urs_talent_pending_updates', $pendingUpdates, 60); // 定时批量处理 if (count($pendingUpdates) >= 10) { UrsTalentService::batchUpdateTalentLevels($pendingUpdates); Cache::forget('urs_talent_pending_updates'); } ``` #### 3.3 防重复更新机制 ```php // 使用Redis锁防止重复更新 $lockKey = "urs_talent_update_lock_{$userId}"; if (Cache::lock($lockKey, 60)->get()) { UrsTalentService::updateUserTalent($userId); } ``` ## 详细实现计划 ### 第一阶段:核心功能实现(预计2小时) #### 1.1 修改UrsTalentLevelUpListener(30分钟) - **文件位置**:`app/Module/UrsPromotion/Listeners/UrsTalentLevelUpListener.php` - **修改内容**: - 添加`updateUpstreamTalentLevels`私有方法 - 实现分层处理逻辑(即时+队列) - 完善错误处理和日志记录 - **测试验证**:单元测试验证分层逻辑 #### 1.2 创建UpdateUpstreamTalentLevelJob(30分钟) - **文件位置**:`app/Module/UrsPromotion/Jobs/UpdateUpstreamTalentLevelJob.php` - **实现内容**: - 队列Job基础结构 - 防重复更新锁机制 - 重试和失败处理逻辑 - 详细的日志记录 - **配置要求**: - 队列名称:`urs_talent_update` - 最大重试次数:3次 - 超时时间:60秒 #### 1.3 修改UrsReferralCreatedListener(20分钟) - **文件位置**:`app/Module/UrsPromotion/Listeners/UrsReferralCreatedListener.php` - **修改内容**: - 复用UrsTalentLevelUpListener的向上传播逻辑 - 确保新建推荐关系时也能触发上级更新 - **注意事项**:避免代码重复,考虑提取公共方法 #### 1.4 创建基础测试用例(40分钟) - **测试文件**:`tests/Unit/UrsPromotion/UrsTalentUpstreamUpdateTest.php` - **测试场景**: - 单级向上传播(直接上级即时更新) - 多级向上传播(上上级队列更新) - 无上级用户的边界情况 - 映射关系不存在的异常情况 ### 第二阶段:队列配置和优化(预计1小时) #### 2.1 队列配置(20分钟) - **配置文件**:`config/queue.php` - **队列设置**: - 确保`urs_talent_update`队列配置正确 - 设置合适的worker数量和内存限制 - **监控配置**: - 配置队列监控和告警 - 设置失败任务处理策略 #### 2.2 防重复更新机制优化(20分钟) - **Redis锁优化**: - 调整锁超时时间 - 添加锁获取失败的处理逻辑 - **更新状态跟踪**: - 记录更新开始和结束时间 - 统计更新成功率 #### 2.3 性能测试和调优(20分钟) - **压力测试**: - 模拟大量用户同时升级的场景 - 测试队列处理能力 - **性能优化**: - 优化数据库查询 - 调整队列处理参数 ### 第三阶段:完善和部署(预计1小时) #### 3.1 集成测试(30分钟) - **测试文件**:`tests/Feature/UrsPromotion/UrsTalentUpstreamIntegrationTest.php` - **测试场景**: - 完整的事件流程测试 - 多用户并发升级测试 - 队列处理延迟测试 - **数据验证**: - 验证所有上级用户等级正确更新 - 验证更新时间和顺序 #### 3.2 文档更新(20分钟) - **技术文档**:更新达人等级逻辑文档 - **API文档**:如有新增接口,更新API文档 - **运维文档**:添加队列监控和故障排查指南 #### 3.3 部署准备(10分钟) - **代码审查**:确保代码质量和安全性 - **配置检查**:验证生产环境队列配置 - **回滚方案**:准备紧急回滚策略 ### 实现优先级 1. **P0(必须)**:UrsTalentLevelUpListener修改 + UpdateUpstreamTalentLevelJob创建 2. **P1(重要)**:UrsReferralCreatedListener修改 + 基础测试 3. **P2(优化)**:性能优化 + 监控配置 4. **P3(完善)**:集成测试 + 文档更新 ## 风险评估 ### 1. 技术风险 - **性能影响**:向上传播可能导致大量数据库操作 - **循环更新**:可能出现重复更新同一用户的情况 - **事务处理**:需要考虑事务边界和异常处理 ### 2. 业务风险 - **数据一致性**:更新过程中可能出现数据不一致 - **系统负载**:大量用户同时更新可能影响系统性能 ### 3. 风险缓解措施 - 使用队列异步处理 - 添加重试机制和异常处理 - 实现渐进式部署和回滚机制 - 添加监控和告警机制 ## 测试策略 ### 1. 单元测试 - 测试向上传播逻辑 - 测试防重复更新机制 - 测试异常处理 ### 2. 集成测试 - 测试完整的事件流程 - 测试多级推荐关系场景 - 测试高并发场景 ### 3. 性能测试 - 测试大量用户同时更新的性能 - 测试队列处理性能 - 测试数据库查询性能 ## 总结 这个bug的核心问题是缺少向上传播机制,导致上级用户的达人等级不能及时更新。通过在事件监听器中添加向上传播逻辑,结合异步队列处理和防重复更新机制,可以有效解决这个问题,确保所有上级用户的达人等级都能及时准确地更新。