diff --git a/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php b/application/api/model/BlindBoxTurntableGiftDrawWorldNew.php index 66238ae..3b5e2c6 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]; + } } /** * 预计算抽奖结果 @@ -124,7 +141,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model $pan_total_remaining = $this->getCachedPanTotalRemaining($gift_bag_id); //剩余数量 //获取可用礼物 $availableGifts = $this->getAvailableGifts($gift_bag_id,$pan_total_draw_times+$total_num); - $giftInfoMap = cache::get("pan_gift_info_map".$gift_bag_id); //预加载礼物信息 + $giftInfoMap = $this->getCachedPanGiftInfoMap($gift_bag_id);//预加载礼物信息 $remaining_available_gifts = []; if (empty($availableGifts) ||$pan_total_remaining ==0) { //重置奖池 @@ -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']--; @@ -317,18 +333,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model $total_remaining = array_sum(array_column($pan_gift_bag_detail, 'remaining_number')); $total_draw_times = max(0, $total_quantity - $total_remaining); //总抽奖次数 - $gift_info_map = []; - $gift_ids = array_unique(array_column($pan_gift_bag_detail, 'foreign_id')); - if (!empty($gift_ids)) { - foreach ($gift_ids as $gift_id) { - $gift_info_map[$gift_id] = db::name("vs_gift") - ->field('gid,gift_name,gift_price,base_image') - ->where(['gid' => $gift_id]) - ->find(); - } - } Cache::set($cacheKey, $pan_gift_bag_detail, $this->cache_time); - Cache::set("pan_gift_info_map".$gift_bag_id, $gift_info_map, $this->cache_time); Cache::set("pan_total_quantity".$gift_bag_id, $total_quantity, $this->cache_time); Cache::set("pan_total_remaining".$gift_bag_id, $total_remaining, $this->cache_time); Cache::set("pan_total_draw_times".$gift_bag_id, $total_draw_times, $this->cache_time); @@ -377,15 +382,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 +484,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 []; + } /** * 获取奖池总库存 */ @@ -547,6 +578,31 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model } return $xlh_periods_num; } + /** + * 获取奖池总库存 + */ + private function getCachedPanGiftInfoMap($gift_bag_id) { + $cacheKey = "pan_gift_info_map".$gift_bag_id; + $gift_info_map = Cache::get($cacheKey); + if(!$gift_info_map) { + $gift_bag_detail = Db::name("vs_gift_bag_detail") + ->field('foreign_id') + ->where(['gift_bag_id' => $gift_bag_id]) + ->select(); + $gift_info_map = []; + $gift_ids = array_unique(array_column($gift_bag_detail, 'foreign_id')); + if (!empty($gift_ids)) { + foreach ($gift_ids as $gift_id) { + $gift_info_map[$gift_id] = db::name("vs_gift") + ->field('gid,gift_name,gift_price,base_image') + ->where(['gid' => $gift_id]) + ->find(); + } + } + Cache::set($cacheKey, $gift_info_map, $this->cache_time); + } + return $gift_info_map; + } //开始更新操作: /** @@ -572,7 +628,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 +694,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model /** * 批量更新礼物库存 */ - private function batchUpdateGiftInventory($precomputedResults) + private function batchUpdateGiftInventory($gift_bag_id,$precomputedResults) { // 按礼物ID分组统计需要减少的数量 $inventoryUpdates = []; @@ -655,21 +711,28 @@ 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); } + $upRemainingNumber = $giftBagDetail['remaining_number'] - $count; + if($upRemainingNumber!=$giftBagDetailCached['remaining_number']){ + $this->redis->setex( 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'), 86400 * 7, "有并发:礼物数量不一致,礼物ID: " . $giftId . '数据库数量: '.$upRemainingNumber. '缓存数量: '.$giftBagDetailCached['remaining_number']. ' ' .json_encode($giftBagDetail) . ' ' .json_encode($giftBagDetailCached)); + $upRemainingNumber = $giftBagDetailCached['remaining_number']; + } // 检查库存是否足够 - if ($giftBagDetail['remaining_number'] < $count) { + if ($upRemainingNumber < 0) { throw new \Exception("礼物库存不足,ID: " . $giftId); } - $ret = db::name("vs_gift_bag_detail")->where('id',$giftId) - ->setDec('remaining_number', $count); + if($upRemainingNumber = $giftBagDetail['remaining_number']){ + return; + } + $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); } } @@ -1123,6 +1186,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model // 批量插入礼包发放记录 $gift_records = []; $periods = $this->getCachedXlhPeriods('get') ?? 0; + $giftInfoMap = $this->getCachedPanGiftInfoMap($gift_bag_id); foreach ($drawn_gifts as $gift_id => $count) { $gift_records[] = [ 'user_id' => $user_id, @@ -1132,7 +1196,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model 'periods' => $periods, 'room_id' => $room_id, 'num' => $count, - 'gift_price' => cache::get("pan_gift_info_map".$gift_bag_id)[$gift_id], //预加载礼物信息, + 'gift_price' => $giftInfoMap[$gift_id]['gift_price'], //预加载礼物信息, 'bag_price' => $ext['xlh_box_price'], 'createtime' => time() ];