新需求-活动需求-盲盒转盘调通盘-调试-优化错误

This commit is contained in:
2025-10-14 15:23:36 +08:00
parent 4440aee163
commit 14f30dd692

View File

@@ -36,85 +36,96 @@ class BlindBoxTurntableGiftDrawWorld extends Model
*/ */
public function draw_gift($gift_bag_id, $user_id, $gift_user_ids, $num = 1, $room_id = 0, $heart_id = 0,$auction_id = 0) public function draw_gift($gift_bag_id, $user_id, $gift_user_ids, $num = 1, $room_id = 0, $heart_id = 0,$auction_id = 0)
{ {
try { // 最大重试次数
// 1. 验证参数并提前处理错误 $maxRetries = 3;
$validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids); for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
if ($validationResult !== true) { try {
return $validationResult; // 1. 验证参数并提前处理错误
} $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids);
// 2. 预加载必要数据 if ($validationResult !== true) {
$loadResult = $this->loadDrawData($gift_bag_id, $user_id, $room_id,$num,$gift_user_ids); return $validationResult;
if ($loadResult['code'] !== 1) { }
return $loadResult; // 2. 预加载必要数据
} $loadResult = $this->loadDrawData($gift_bag_id, $user_id, $room_id, $num, $gift_user_ids);
['bag_data' => $bag_data, 'room' => $room, 'xlh_ext' => $xlh_ext] = $loadResult['data']; if ($loadResult['code'] !== 1) {
return $loadResult;
}
['bag_data' => $bag_data, 'room' => $room, 'xlh_ext' => $xlh_ext] = $loadResult['data'];
// 3. 预计算抽奖结果 // 3. 预计算抽奖结果
$precomputeResult = $this->precomputeDrawResults( $precomputeResult = $this->precomputeDrawResults(
$bag_data, $bag_data,
$gift_user_ids, $gift_user_ids,
$num $num
); );
if ($precomputeResult['code'] !== 1) { if ($precomputeResult['code'] !== 1) {
return $precomputeResult; return $precomputeResult;
} }
$precomputedResults = $precomputeResult['data']['results']; $precomputedResults = $precomputeResult['data']['results'];
$availableGiftss = $precomputeResult['data']['availableGifts']; $availableGiftss = $precomputeResult['data']['availableGifts'];
$currentXlhPeriodsNum = $precomputeResult['data']['current_xlh_periods_num']; $currentXlhPeriodsNum = $precomputeResult['data']['current_xlh_periods_num'];
$xlhIsPiaoPing = $precomputeResult['data']['xlh_is_piao_ping']; $xlhIsPiaoPing = $precomputeResult['data']['xlh_is_piao_ping'];
$expectedCount = count(explode(',', $gift_user_ids)) * $num; $expectedCount = count(explode(',', $gift_user_ids)) * $num;
if(count($precomputedResults) != $expectedCount){ if (count($precomputedResults) != $expectedCount) {
// 记录错误到Redis // 记录错误到Redis
$this->recordDrawErrorToRedis($expectedCount, count($precomputedResults), $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults); $this->recordDrawErrorToRedis($expectedCount, count($precomputedResults), $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults);
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null]; return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
} }
// 4. 执行抽奖事务(核心操作) // 4. 执行抽奖事务(核心操作)
$transactionResult = $this->executeDrawTransaction( $transactionResult = $this->executeDrawTransaction(
$bag_data, $bag_data,
$user_id, $user_id,
$room_id, $room_id,
$num, $num,
$precomputedResults, $precomputedResults,
$availableGiftss, $availableGiftss,
$gift_user_ids, $gift_user_ids,
$heart_id, $heart_id,
$auction_id $auction_id
); );
if ($transactionResult['code'] !== 1) { if ($transactionResult['code'] !== 1) {
return $transactionResult; return $transactionResult;
} }
$boxTurntableLog = $transactionResult['data']['log_id']; $boxTurntableLog = $transactionResult['data']['log_id'];
$giftCounts = $transactionResult['data']['gift_counts']; $giftCounts = $transactionResult['data']['gift_counts'];
// 5. 处理后续操作(非事务性操作) // 5. 处理后续操作(非事务性操作)
$this->handlePostDrawOperations( $this->handlePostDrawOperations(
$precomputedResults, $precomputedResults,
$boxTurntableLog, $boxTurntableLog,
$room_id, $room_id,
$xlh_ext, $xlh_ext,
$xlhIsPiaoPing, $xlhIsPiaoPing,
$currentXlhPeriodsNum, $currentXlhPeriodsNum,
$room $room
); );
// 6. 构建并返回结果 // 6. 构建并返回结果
return $this->buildDrawResult($boxTurntableLog, $giftCounts); return $this->buildDrawResult($boxTurntableLog, $giftCounts);
} catch (\Exception $e) { } catch (\Exception $e) {
$key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'); $key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s');
$errorData = [ $errorData = [
'gift_bag_id' => $gift_bag_id, 'gift_bag_id' => $gift_bag_id,
'user_id' => $user_id, 'user_id' => $user_id,
'gift_user_ids' => $gift_user_ids, 'gift_user_ids' => $gift_user_ids,
'num' => $num, 'num' => $num,
'room_id' => $room_id, 'room_id' => $room_id,
'heart_id' => $heart_id, 'heart_id' => $heart_id,
'auction_id' => $auction_id, 'auction_id' => $auction_id,
]; ];
$this->redis->setex($key, 86400 * 7, $e->getMessage(). ' ' .json_encode($errorData)); if ($this->redis) {
return ['code' => 0, 'msg' => "网络加载失败,请重试!", 'data' => null]; $this->redis->setex($key, 86400 * 7, $e->getMessage() . ' ' . json_encode($errorData));
}
// 如果是死锁且还有重试机会
if (strpos($e->getMessage(), 'Deadlock') !== false && $attempt < $maxRetries - 1) {
// 随机延迟后重试
usleep(rand(50000, 200000)); // 50-200ms
continue;
}
return ['code' => 0, 'msg' => "网络加载失败,请重试!", 'data' => null];
}
} }
} }
/** /**
* 验证抽奖参数 * 验证抽奖参数
@@ -471,54 +482,66 @@ class BlindBoxTurntableGiftDrawWorld extends Model
$gift_user_num = count(explode(',', $gift_user_ids)); //人数 $gift_user_num = count(explode(',', $gift_user_ids)); //人数
$bagGiftPrice = $bag_data['gift_price'] * $num * $gift_user_num; $bagGiftPrice = $bag_data['gift_price'] * $num * $gift_user_num;
db::startTrans(); // 增加重试机制
try { $maxRetries = 3;
// 1. 创建抽奖记录 for ($retry = 0; $retry < $maxRetries; $retry++) {
$boxTurntableLog = db::name('vs_blind_box_turntable_log')->insertGetId([ try {
'user_id' => $user_id, db::startTrans();
'gift_bag_id' => $bag_data['id'], // 按照固定顺序处理事务步骤
'num' => $num, // 1. 扣除用户金币(优先处理)
'room_id' => $room_id, $this->deductUserCoins($user_id, $bagGiftPrice, $room_id);
'bag_price' => $bag_data['gift_price'],
'createtime' => time()
]);
if (!$boxTurntableLog) { // 2. 创建抽奖记录
throw new \Exception('添加盲盒转盘记录失败'); $boxTurntableLog = db::name('vs_blind_box_turntable_log')->insertGetId([
'user_id' => $user_id,
'gift_bag_id' => $bag_data['id'],
'num' => $num,
'room_id' => $room_id,
'bag_price' => $bag_data['gift_price'],
'createtime' => time()
]);
if (!$boxTurntableLog) {
throw new \Exception('添加盲盒转盘记录失败');
}
// 3. 批量更新库存按ID排序避免死锁
$this->batchUpdateGiftInventory($availableGiftss, $room_id);
// 4. 批量插入礼包发放记录
$this->batchInsertGiftBagReceiveLog($user_id, $boxTurntableLog, $bag_data, $room_id, $precomputedResults);
// 5. 发送礼物
$result = $this->sendGiftsToRecipients($precomputedResults, $room_id,$user_id,$heart_id,$auction_id);
if (isset($result['code']) && $result['code'] !== 1) {
throw new \Exception($result['msg']);
}
db::commit();
// 统计礼物数量
$giftCounts = $this->countGifts($precomputedResults);
return [
'code' => 1,
'msg' => '事务执行成功',
'data' => [
'log_id' => $boxTurntableLog,
'gift_counts' => $giftCounts
]
];
} catch (\Exception $e) {
db::rollback();
// 检查是否是死锁错误
if (strpos($e->getMessage(), 'Deadlock') !== false && $retry < $maxRetries - 1) {
// 等待随机时间后重试
usleep(rand(10000, 100000)); // 10-100ms
continue;
}
return ['code' => 0, 'msg' => $e->getMessage(), 'data' => null];
} }
// 2. 批量更新库存
$this->batchUpdateGiftInventory($availableGiftss, $room_id);
// 3. 批量插入礼包发放记录
$this->batchInsertGiftBagReceiveLog($user_id, $boxTurntableLog, $bag_data, $room_id, $precomputedResults);
// 4. 扣除用户金币
$this->deductUserCoins($user_id, $bagGiftPrice, $room_id);
//发送礼物
$result = $this->sendGiftsToRecipients($precomputedResults, $room_id,$user_id,$heart_id,$auction_id);
if (isset($result['code']) && $result['code'] !== 1) {
throw new \Exception($result['msg']);
}
db::commit();
// 5. 统计礼物数量
$giftCounts = $this->countGifts($precomputedResults);
return [
'code' => 1,
'msg' => '事务执行成功',
'data' => [
'log_id' => $boxTurntableLog,
'gift_counts' => $giftCounts
]
];
} catch (\Exception $e) {
db::rollback();
return ['code' => 0, 'msg' => $e->getMessage(), 'data' => null];
} }
return ['code' => 0, 'msg' => '操作超时,请重试', 'data' => null];
} }
/** /**
@@ -533,9 +556,14 @@ class BlindBoxTurntableGiftDrawWorld extends Model
$inventoryUpdates[$giftId] = ($inventoryUpdates[$giftId] ?? 0) + 1; $inventoryUpdates[$giftId] = ($inventoryUpdates[$giftId] ?? 0) + 1;
} }
// 按ID排序避免死锁
ksort($inventoryUpdates);
// 批量更新 // 批量更新
foreach ($inventoryUpdates as $giftId => $count) { foreach ($inventoryUpdates as $giftId => $count) {
$ret = db::name("vs_gift_bag_detail")->where('id',$giftId)->setDec('remaining_number', $count); $ret = db::name("vs_gift_bag_detail")->where('id',$giftId)
->lock(true) // 添加悲观锁
->setDec('remaining_number', $count);
if (!$ret) { if (!$ret) {
Log::record('巡乐会更新礼物剩余数量: ' . $room_id."【数据】".var_export($precomputedResults, true),"info"); Log::record('巡乐会更新礼物剩余数量: ' . $room_id."【数据】".var_export($precomputedResults, true),"info");
throw new \Exception('更新礼物剩余数量失败'); throw new \Exception('更新礼物剩余数量失败');
@@ -578,6 +606,15 @@ class BlindBoxTurntableGiftDrawWorld extends Model
*/ */
private function deductUserCoins($user_id, $bagGiftPrice, $room_id) private function deductUserCoins($user_id, $bagGiftPrice, $room_id)
{ {
// 使用悲观锁查询用户钱包
$userWallet = db::name('user_wallet')
->where(['user_id' => $user_id])
->lock(true)
->find();
if (!$userWallet || $userWallet['coin'] < $bagGiftPrice) {
throw new \Exception('用户金币不足');
}
$walletUpdate = model('GiveGift')->change_user_cion_or_earnings_log( $walletUpdate = model('GiveGift')->change_user_cion_or_earnings_log(
$user_id, $user_id,
$bagGiftPrice, $bagGiftPrice,