盲盒转盘优化-重构-调试

This commit is contained in:
2025-10-29 10:54:57 +08:00
parent 5b03fec4f6
commit 0f7cf82276

View File

@@ -37,83 +37,100 @@ class BlindBoxTurntableGiftDrawWorldNew 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 {
$gift_user_ids = explode(',', $gift_user_ids); // 收礼人
$total_num = $num * count($gift_user_ids); //总数量 $gift_user_ids = explode(',', $gift_user_ids);
$bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息 $total_num = $num * count($gift_user_ids); //总数量
$total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格 $bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息
$periods = $bag_data['periods']; //期数 $total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格
//1. 验证参数并提前处理错误 $periods = $bag_data['periods']; //期数
$validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price); //1. 验证参数并提前处理错误
if ($validationResult !== true) { $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price);
return $validationResult; if ($validationResult !== true) {
} return $validationResult;
//2.预计算抽奖结果 }
$precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods ); //2.预计算抽奖结果
$precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集 $precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods );
$availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新 $precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集
$availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新
if (count($precomputedResults) != $total_num) { if (count($precomputedResults) != $total_num) {
// 记录错误到Redis // 记录错误到Redis
// 使用正确的Redis方法存储数据 // 使用正确的Redis方法存储数据
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
$errorData = [
'total_num' => $total_num,
'actual_num' => count($precomputedResults),
'room_id' => $room_id,
'user_id' => $user_id,
'gift_bag_id' => $gift_bag_id,
'num' => $num,
'gift_user_ids' => $gift_user_ids,
'precomputedResults' => $precomputedResults,
];
$this->redis->setex($key, 86400 * 7, "超出数量".json_encode($errorData));
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
}
$giftCounts = $this->countGifts($precomputedResults);
foreach ($giftCounts as $giftId => $count) {
if($count['count'] > $count['quantity']){
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s'); $key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
$errorData = [ $errorData = [
'count' => $count['count'], 'total_num' => $total_num,
'quantity' => $count['quantity'], 'actual_num' => count($precomputedResults),
'gift_bag_id' => $gift_bag_id, 'room_id' => $room_id,
'user_id' => $user_id, 'user_id' => $user_id,
'gift_user_ids' => $gift_user_ids, 'gift_bag_id' => $gift_bag_id,
'num' => $num, 'num' => $num,
'giftUserCountsJianCha' => $count, 'gift_user_ids' => $gift_user_ids,
'precomputedResults' => $precomputedResults,
]; ];
$this->redis->setex($key, 86400 * 7, "礼物数量超出限制 ".json_encode($errorData)); $this->redis->setex($key, 86400 * 7, "超出数量".json_encode($errorData));
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null]; return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
} }
} $giftCounts = $this->countGifts($precomputedResults);
foreach ($giftCounts as $giftId => $count) {
if($count['count'] > $count['quantity']){
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
$errorData = [
'count' => $count['count'],
'quantity' => $count['quantity'],
'gift_bag_id' => $gift_bag_id,
'user_id' => $user_id,
'gift_user_ids' => $gift_user_ids,
'num' => $num,
'giftUserCountsJianCha' => $count,
];
$this->redis->setex($key, 86400 * 7, "礼物数量超出限制 ".json_encode($errorData));
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,
$total_price, $total_price,
$precomputedResults, $precomputedResults,
$availableGiftss, $availableGiftss,
$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
); );
// 6. 构建并返回结果 // 6. 构建并返回结果
return $this->buildDrawResult($boxTurntableLog, $giftCounts); return $this->buildDrawResult($boxTurntableLog, $giftCounts);
} catch (\Exception $e) {
$key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s');
$errorData = [
'gift_bag_id' => $gift_bag_id,
'user_id' => $user_id,
'gift_user_ids' => $gift_user_ids,
'num' => $num,
'room_id' => $room_id,
'heart_id' => $heart_id,
'auction_id' => $auction_id,
];
if ($this->redis) {
$this->redis->setex($key, 86400 * 7, $e->getMessage() . ' ' . json_encode($errorData));
}
return ['code' => 0, 'msg' => "网络加载失败,请重试!", 'data' => null];
}
} }
/** /**
* 预计算抽奖结果 * 预计算抽奖结果
@@ -221,6 +238,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
$this->getCachedXlhPeriodsNum("set"); $this->getCachedXlhPeriodsNum("set");
// 更新Alias表 // 更新Alias表
$this->updateAliasTable($aliasTableForNew, $selectedGift['id']); $this->updateAliasTable($aliasTableForNew, $selectedGift['id']);
$this->updateCachedGiftBagDetail($gift_bag_id, $selectedGift['id']); //更新缓存中礼物的数量
} }
} }
} }
@@ -235,8 +253,6 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
{ {
// 查找礼物在Alias表中的位置 // 查找礼物在Alias表中的位置
$gifts = &$aliasTable['gifts']; $gifts = &$aliasTable['gifts'];
$indexMap = &$aliasTable['index_map'];
foreach ($gifts as &$gift) { foreach ($gifts as &$gift) {
if ($gift['id'] == $giftId) { if ($gift['id'] == $giftId) {
$gift['remaining_number']--; $gift['remaining_number']--;
@@ -377,15 +393,26 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
{ {
if (!$aliasTable) return null; if (!$aliasTable) return null;
$n = $aliasTable['n']; // 添加重试机制最多尝试5次
$k = mt_rand(0, $n - 1); $maxAttempts = 5;
$attempt = 0;
// 随机选择 while ($attempt < $maxAttempts) {
if (mt_rand() / mt_getrandmax() < $aliasTable['prob'][$k]) { $n = $aliasTable['n'];
return $aliasTable['index_map'][$k]; $k = mt_rand(0, $n - 1);
} else { // 随机选择
return $aliasTable['index_map'][$aliasTable['alias'][$k]] ?? null; if (mt_rand() / mt_getrandmax() < $aliasTable['prob'][$k]) {
$selectedGift = $aliasTable['index_map'][$k];
} else {
$selectedGift = $aliasTable['index_map'][$aliasTable['alias'][$k]] ?? null;
}
// 检查选中的礼物是否还有库存
if ($selectedGift && $selectedGift['remaining_number'] > 0) {
return $selectedGift;
}
$attempt++;
} }
// 如果重试次数用完仍未抽中有效礼物返回null
return null;
} }
/** /**
* 构建Alias表O(n)复杂度,只执行一次) * 构建Alias表O(n)复杂度,只执行一次)
@@ -468,6 +495,21 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
Cache::set($cacheKey, $gift_bag_detail_data, $this->cache_time); Cache::set($cacheKey, $gift_bag_detail_data, $this->cache_time);
} }
} }
/**
* 获取缓存中的奖池单个礼物信息
*/
private function getCachedGiftBagDetailItem($gift_bag_id, $detail_id) {
$cacheKey = "pan_gift_bag_detail".$gift_bag_id;
$gift_bag_detail_item = Cache::get($cacheKey);
if($gift_bag_detail_item) {
foreach ($gift_bag_detail_item as $item) {
if ($item['id'] == $detail_id) {
return $item;
}
}
}
return [];
}
/** /**
* 获取奖池总库存 * 获取奖池总库存
*/ */
@@ -572,7 +614,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
throw new \Exception('添加盲盒转盘记录失败'); throw new \Exception('添加盲盒转盘记录失败');
} }
// 3. 批量更新库存按ID排序避免死锁 // 3. 批量更新库存按ID排序避免死锁
$this->batchUpdateGiftInventory($availableGiftss); $this->batchUpdateGiftInventory($bag_data['gift_bag_id'],$availableGiftss);
// 4. 批量插入礼包发放记录 // 4. 批量插入礼包发放记录
$this->batchInsertGiftBagReceiveLog($bag_data,$user_id, $boxTurntableLog,$room_id, $precomputedResults); $this->batchInsertGiftBagReceiveLog($bag_data,$user_id, $boxTurntableLog,$room_id, $precomputedResults);
@@ -638,7 +680,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
/** /**
* 批量更新礼物库存 * 批量更新礼物库存
*/ */
private function batchUpdateGiftInventory($precomputedResults) private function batchUpdateGiftInventory($gift_bag_id,$precomputedResults)
{ {
// 按礼物ID分组统计需要减少的数量 // 按礼物ID分组统计需要减少的数量
$inventoryUpdates = []; $inventoryUpdates = [];
@@ -655,6 +697,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
$giftBagDetail = Db::name("vs_gift_bag_detail") $giftBagDetail = Db::name("vs_gift_bag_detail")
->where('id', $giftId) ->where('id', $giftId)
->find(); ->find();
$giftBagDetailCached = $this->getCachedGiftBagDetailItem($gift_bag_id, $giftId);
if (!$giftBagDetail) { if (!$giftBagDetail) {
throw new \Exception("礼物详情不存在ID: " . $giftId); throw new \Exception("礼物详情不存在ID: " . $giftId);
} }
@@ -662,14 +705,17 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
if ($giftBagDetail['remaining_number'] < $count) { if ($giftBagDetail['remaining_number'] < $count) {
throw new \Exception("礼物库存不足ID: " . $giftId); throw new \Exception("礼物库存不足ID: " . $giftId);
} }
$ret = db::name("vs_gift_bag_detail")->where('id',$giftId) $upRemainingNumber = $giftBagDetail['remaining_number'] - $count;
->setDec('remaining_number', $count); if($giftBagDetail['remaining_number']!=$giftBagDetailCached['remaining_number']){
$this->redis->setex( 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'), 86400 * 7, "有并发礼物数量不一致礼物ID: " . $giftId . ' ' .json_encode($giftBagDetail) . ' ' .json_encode($giftBagDetailCached));
$upRemainingNumber = $giftBagDetailCached['remaining_number'];
}
$ret = db::name("vs_gift_bag_detail")->where('id',$giftId)->update([
'remaining_number' => $upRemainingNumber
]);
if (!$ret) { if (!$ret) {
throw new \Exception('更新礼物剩余数量失败'); throw new \Exception('更新礼物剩余数量失败');
} }
// 同时更新缓存中的库存信息
$this->updateCachedGiftBagDetail($giftBagDetail['gift_bag_id'], $giftId, $count);
} }
} }