From 0f7cf82276308a6893525775d16243e3c8280c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E9=92=8A?= Date: Wed, 29 Oct 2025 10:54:57 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B2=E7=9B=92=E8=BD=AC=E7=9B=98=E4=BC=98?= =?UTF-8?q?=E5=8C=96-=E9=87=8D=E6=9E=84-=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlindBoxTurntableGiftDrawWorldNew.php | 214 +++++++++++------- 1 file changed, 130 insertions(+), 84 deletions(-) diff --git a/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php b/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php index 66238ae..22fc1df 100644 --- a/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php +++ b/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php @@ -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) { - // 收礼人 - $gift_user_ids = explode(',', $gift_user_ids); - $total_num = $num * count($gift_user_ids); //总数量 - $bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息 - $total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格 - $periods = $bag_data['periods']; //期数 - //1. 验证参数并提前处理错误 - $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price); - if ($validationResult !== true) { - return $validationResult; - } - //2.预计算抽奖结果 - $precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods ); - $precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集 - $availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新 + try { + // 收礼人 + $gift_user_ids = explode(',', $gift_user_ids); + $total_num = $num * count($gift_user_ids); //总数量 + $bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息 + $total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格 + $periods = $bag_data['periods']; //期数 + //1. 验证参数并提前处理错误 + $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price); + if ($validationResult !== true) { + return $validationResult; + } + //2.预计算抽奖结果 + $precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods ); + $precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集 + $availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新 - if (count($precomputedResults) != $total_num) { - // 记录错误到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']){ + if (count($precomputedResults) != $total_num) { + // 记录错误到Redis + // 使用正确的Redis方法存储数据 $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, + 'total_num' => $total_num, + 'actual_num' => count($precomputedResults), + 'room_id' => $room_id, 'user_id' => $user_id, - 'gift_user_ids' => $gift_user_ids, + 'gift_bag_id' => $gift_bag_id, '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]; } - } + $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. 执行抽奖事务(核心操作) - $transactionResult = $this->executeDrawTransaction( - $bag_data, - $user_id, - $room_id, - $num, - $total_price, - $precomputedResults, - $availableGiftss, - $heart_id, - $auction_id - ); - if ($transactionResult['code'] !== 1) { - return $transactionResult; - } - $boxTurntableLog = $transactionResult['data']['log_id']; - $giftCounts = $transactionResult['data']['gift_counts']; + // 4. 执行抽奖事务(核心操作) + $transactionResult = $this->executeDrawTransaction( + $bag_data, + $user_id, + $room_id, + $num, + $total_price, + $precomputedResults, + $availableGiftss, + $heart_id, + $auction_id + ); + if ($transactionResult['code'] !== 1) { + return $transactionResult; + } + $boxTurntableLog = $transactionResult['data']['log_id']; + $giftCounts = $transactionResult['data']['gift_counts']; - // 5. 处理后续操作(非事务性操作) - $this->handlePostDrawOperations( - $precomputedResults, - $boxTurntableLog, - $room_id - ); - // 6. 构建并返回结果 - return $this->buildDrawResult($boxTurntableLog, $giftCounts); + // 5. 处理后续操作(非事务性操作) + $this->handlePostDrawOperations( + $precomputedResults, + $boxTurntableLog, + $room_id + ); + // 6. 构建并返回结果 + 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"); // 更新Alias表 $this->updateAliasTable($aliasTableForNew, $selectedGift['id']); + $this->updateCachedGiftBagDetail($gift_bag_id, $selectedGift['id']); //更新缓存中礼物的数量 } } } @@ -235,8 +253,6 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model { // 查找礼物在Alias表中的位置 $gifts = &$aliasTable['gifts']; - $indexMap = &$aliasTable['index_map']; - foreach ($gifts as &$gift) { if ($gift['id'] == $giftId) { $gift['remaining_number']--; @@ -377,15 +393,26 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model { if (!$aliasTable) return null; - $n = $aliasTable['n']; - $k = mt_rand(0, $n - 1); - - // 随机选择 - if (mt_rand() / mt_getrandmax() < $aliasTable['prob'][$k]) { - return $aliasTable['index_map'][$k]; - } else { - return $aliasTable['index_map'][$aliasTable['alias'][$k]] ?? null; + // 添加重试机制,最多尝试5次 + $maxAttempts = 5; + $attempt = 0; + while ($attempt < $maxAttempts) { + $n = $aliasTable['n']; + $k = mt_rand(0, $n - 1); + // 随机选择 + 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)复杂度,只执行一次) @@ -468,6 +495,21 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model 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('添加盲盒转盘记录失败'); } // 3. 批量更新库存(按ID排序避免死锁) - $this->batchUpdateGiftInventory($availableGiftss); + $this->batchUpdateGiftInventory($bag_data['gift_bag_id'],$availableGiftss); // 4. 批量插入礼包发放记录 $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分组统计需要减少的数量 $inventoryUpdates = []; @@ -655,6 +697,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model $giftBagDetail = Db::name("vs_gift_bag_detail") ->where('id', $giftId) ->find(); + $giftBagDetailCached = $this->getCachedGiftBagDetailItem($gift_bag_id, $giftId); if (!$giftBagDetail) { throw new \Exception("礼物详情不存在,ID: " . $giftId); } @@ -662,14 +705,17 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model if ($giftBagDetail['remaining_number'] < $count) { throw new \Exception("礼物库存不足,ID: " . $giftId); } - $ret = db::name("vs_gift_bag_detail")->where('id',$giftId) - ->setDec('remaining_number', $count); + $upRemainingNumber = $giftBagDetail['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) { throw new \Exception('更新礼物剩余数量失败'); } - - // 同时更新缓存中的库存信息 - $this->updateCachedGiftBagDetail($giftBagDetail['gift_bag_id'], $giftId, $count); } }