031530-URS达人等级向上传播更新Bug修复技术文档.md 14 KB

URS推广模块达人等级向上传播更新Bug修复技术文档

问题概述

1. 问题描述

当某个用户的上级更新了达人指标时,这个更新没有向上传播到更高层级的上级(上上级、上上上级等),导致达人等级统计不准确。

2. 影响范围

  • 直接影响:上级用户的达人等级不会自动更新
  • 业务影响:影响收益分成计算和权益分配
  • 数据一致性:团队统计数据可能不准确

技术原因分析

1. 当前事件流程

用户A推荐用户B → 触发UrsReferralCreatedEvent → 只更新用户A的达人等级
用户B达人等级提升 → 触发UrsTalentLevelUpEvent → 只记录日志,不更新上级

2. 缺失的逻辑

用户B达人等级提升 → 应该触发 → 更新用户A、用户A的上级、用户A的上上级...

3. 代码分析

3.1 UrsReferralCreatedListener.php

public function handle(UrsReferralCreatedEvent $event): void
{
    // ❌ 问题:只更新直接推荐人,没有向上传播
    UrsTalentService::updateUserTalent($event->referrerId);
}

3.2 UrsTalentLevelUpListener.php

public function handle(UrsTalentLevelUpEvent $event): void
{
    // ❌ 问题:只记录日志,没有更新上级用户
    Log::info('URS达人等级提升', [...]);
}

解决方案设计

1. 核心思路

  • 向上传播机制:当用户达人等级变化时,向上遍历推荐关系链
  • 批量更新:逐级更新所有上级用户的达人等级
  • 异步处理:使用队列避免阻塞主流程
  • 防重复更新:避免同一用户被重复更新

2. 技术实现方案(混合处理模式)

核心策略:分层处理机制

  • 第1级(直接上级):即时同步处理,确保实时性
  • 第2级及以上(上上级等):队列异步处理,避免阻塞

实现方案:修改现有监听器

UrsTalentLevelUpListener中添加分层向上传播逻辑:

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

namespace App\Module\UrsPromotion\Jobs;

use App\Module\UrsPromotion\Services\UrsTalentService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;

/**
 * URS达人等级上级更新队列Job
 *
 * 专门处理第2级及以上上级用户的达人等级异步更新
 */
class UpdateUpstreamTalentLevelJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * 任务最大尝试次数
     */
    public $tries = 3;

    /**
     * 任务超时时间(秒)
     */
    public $timeout = 60;

    /**
     * 要更新的上级用户ID
     */
    private int $referrerId;

    /**
     * 触发更新的原始用户ID
     */
    private int $originalUserId;

    /**
     * 上级层级
     */
    private int $level;

    /**
     * 创建队列Job实例
     */
    public function __construct(int $referrerId, int $originalUserId, int $level)
    {
        $this->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 异步队列处理

// 使用队列避免阻塞主流程
dispatch(new UpdateUpstreamTalentLevelsJob($userId));

3.2 批量更新优化

// 合并相同用户的多次更新请求
$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 防重复更新机制

// 使用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的核心问题是缺少向上传播机制,导致上级用户的达人等级不能及时更新。通过在事件监听器中添加向上传播逻辑,结合异步队列处理和防重复更新机制,可以有效解决这个问题,确保所有上级用户的达人等级都能及时准确地更新。