当某个用户的上级更新了达人指标时,这个更新没有向上传播到更高层级的上级(上上级、上上上级等),导致达人等级统计不准确。
用户A推荐用户B → 触发UrsReferralCreatedEvent → 只更新用户A的达人等级
用户B达人等级提升 → 触发UrsTalentLevelUpEvent → 只记录日志,不更新上级
用户B达人等级提升 → 应该触发 → 更新用户A、用户A的上级、用户A的上上级...
public function handle(UrsReferralCreatedEvent $event): void
{
// ❌ 问题:只更新直接推荐人,没有向上传播
UrsTalentService::updateUserTalent($event->referrerId);
}
public function handle(UrsTalentLevelUpEvent $event): void
{
// ❌ 问题:只记录日志,没有更新上级用户
Log::info('URS达人等级提升', [...]);
}
在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处理异步上级更新:
<?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()
]);
}
}
// 使用队列避免阻塞主流程
dispatch(new UpdateUpstreamTalentLevelsJob($userId));
// 合并相同用户的多次更新请求
$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');
}
// 使用Redis锁防止重复更新
$lockKey = "urs_talent_update_lock_{$userId}";
if (Cache::lock($lockKey, 60)->get()) {
UrsTalentService::updateUserTalent($userId);
}
app/Module/UrsPromotion/Listeners/UrsTalentLevelUpListener.phpupdateUpstreamTalentLevels私有方法app/Module/UrsPromotion/Jobs/UpdateUpstreamTalentLevelJob.phpurs_talent_updateapp/Module/UrsPromotion/Listeners/UrsReferralCreatedListener.phptests/Unit/UrsPromotion/UrsTalentUpstreamUpdateTest.phpconfig/queue.phpurs_talent_update队列配置正确tests/Feature/UrsPromotion/UrsTalentUpstreamIntegrationTest.php这个bug的核心问题是缺少向上传播机制,导致上级用户的达人等级不能及时更新。通过在事件监听器中添加向上传播逻辑,结合异步队列处理和防重复更新机制,可以有效解决这个问题,确保所有上级用户的达人等级都能及时准确地更新。