redis = new Redis(); // 连接到Redis服务器 $this->redis->connect(config('redis.host'), config('redis.port')); // 根据实际配置调整主机和端口 // 选择数据库1 $this->redis->select(1); $this->cache_time = 60 *24; } catch (\Exception $e) { Log::record('Redis连接失败: ' . $e->getMessage(), 'error'); $this->redis = null; } } /** * 重构后的抽奖方法 - 优化响应速度 */ 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']; //可用礼物/需更新的礼物/需更新 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']){ $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']; // 5. 处理后续操作(非事务性操作) $this->handlePostDrawOperations( $precomputedResults, $boxTurntableLog, $room_id ); // 6. 构建并返回结果 return $this->buildDrawResult($boxTurntableLog, $giftCounts); } /** * 预计算抽奖结果 */ private function precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods) { $pan_total_draw_times = $this->getCachedPanDrawTimes($gift_bag_id); //总抽奖次数 $pan_total_remaining = $this->getCachedPanTotalRemaining($gift_bag_id); //剩余数量 //获取可用礼物 $availableGifts = $this->getAvailableGifts($gift_bag_id,$pan_total_draw_times); $giftInfoMap = cache::get("pan_gift_info_map".$gift_bag_id); //预加载礼物信息 $remaining_available_gifts = []; if (empty($availableGifts) ||$pan_total_remaining ==0) { //重置奖池 $availableGifts = $this->resetPoolAndReload($gift_bag_id); if (empty($availableGifts)) { throw new \Exception('重置奖池后仍无可用礼物'); } $pan_total_draw_times = $this->getCachedPanDrawTimes($gift_bag_id,"clear");//总抽奖次数重置 }else{ if ($pan_total_remaining < $total_num) { $remaining_available_gifts = $availableGifts; // 保存当前剩余礼物作为上期剩余 //重置奖池 $availableGifts = $this->resetPoolAndReload($gift_bag_id); if (empty($availableGifts)) { throw new \Exception('重置奖池后仍无可用礼物'); } $pan_total_draw_times = $this->getCachedPanDrawTimes($gift_bag_id,"clear");;//总抽奖次数重置 } } // 5. 使用Alias Method预计算抽奖结果(O(1)复杂度) $precomputedResults = []; $precomputedResultss = []; // 计算上期剩余礼物总数 $remainingGiftCount = array_sum(array_column($remaining_available_gifts, 'remaining_number')); $newGiftsNeeded = max(0, $total_num - $remainingGiftCount); // 计算还需要多少礼物从新奖池中抽取 // 先从上期剩余礼物中分配 if (!empty($remaining_available_gifts)) { $aliasTableForRemaining = $this->buildAliasTable($remaining_available_gifts); // 计算上期剩余礼物总数 foreach ($gift_user_ids as $giftUserId) { // 为每个用户先分配上期剩余礼物 $userRemainingAllocation = floor($remainingGiftCount / $total_num); if (count($gift_user_ids) > 0) { // 防止除零错误 $extraGifts = $remainingGiftCount % count($gift_user_ids); if (array_search($giftUserId, $gift_user_ids) < $extraGifts) { $userRemainingAllocation++; } } for ($i = 0; $i < $userRemainingAllocation; $i++) { $selectedGift = $this->selectGiftWithAliasMethod($aliasTableForRemaining); if ($selectedGift) { $gift = $giftInfoMap[$selectedGift['foreign_id']]; $precomputedResults[] = [ 'gift_user_id' => $giftUserId, 'gift_bag_detail' => $selectedGift, 'gift' => $gift, 'draw_times' => $pan_total_draw_times, 'periods' => $periods, ]; $this->getCachedXlhPeriodsNum("set");//添加寻乐会条件次数 $pan_total_draw_times = $this->getCachedPanDrawTimes($gift_bag_id,"set"); // 总抽奖次数 } } } Cache::set("pan_total_draw".$gift_bag_id, 0, $this->cache_time); } // 再从新奖池中分配剩余所需礼物 if ($newGiftsNeeded > 0 && !empty($availableGifts)) { $aliasTableForNew = $this->buildAliasTable($availableGifts); foreach ($gift_user_ids as $giftUserId) { // 计算每个用户需要从新奖池获得的礼物数量 $userNewAllocation = floor($newGiftsNeeded / count($gift_user_ids)); if (count($gift_user_ids) > 0) { $extraGifts = $newGiftsNeeded % count($gift_user_ids); if (array_search($giftUserId, $gift_user_ids) < $extraGifts) { $userNewAllocation++; } } for ($i = 0; $i < $userNewAllocation; $i++) { $selectedGift = $this->selectGiftWithAliasMethod($aliasTableForNew); if ($selectedGift) { $gift = $giftInfoMap[$selectedGift['foreign_id']]??[]; $precomputedResults[] = [ 'gift_user_id' => $giftUserId, 'gift_bag_detail' => $selectedGift, 'gift' => $gift, 'draw_times' => $pan_total_draw_times, 'periods' => $periods, ]; $precomputedResultss[] = [ 'gift_user_id' => $giftUserId, 'gift_bag_detail' => $selectedGift, 'gift' => $gift, 'draw_times' => $pan_total_draw_times, 'periods' => $periods, ]; //更新相关缓存数据 $pan_total_draw_times = $this->getCachedPanDrawTimes($gift_bag_id,"set"); //总抽奖次数+1 $pan_total_remaining = $this->getCachedPanTotalRemaining($gift_bag_id,"set"); //剩余数量-1 // 更新房间巡乐会次数 $this->getCachedXlhPeriodsNum("set"); // 更新Alias表 $this->updateAliasTable($aliasTableForNew, $selectedGift['id']); } } } } return ['precomputedResults' => $precomputedResults, 'precomputedResultss' => $precomputedResultss]; } /** * 更新Alias表(模拟库存减少) */ private function updateAliasTable(&$aliasTable, $giftId) { // 查找礼物在Alias表中的位置 $gifts = &$aliasTable['gifts']; $indexMap = &$aliasTable['index_map']; foreach ($gifts as &$gift) { if ($gift['id'] == $giftId) { $gift['remaining_number']--; break; } } // 重新构建Alias表(当剩余数量变化较大时) // 这里可以根据实际情况调整重建频率 $totalRemaining = array_sum(array_column($gifts, 'remaining_number')); if ($totalRemaining <= 0) { $aliasTable = null; } } /** * 验证抽奖参数 */ private function validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids, $total_price) { // 提前验证收礼人 if (in_array($user_id, $gift_user_ids)) { return ['code' => 0, 'msg' => "收礼人不能包含自己", 'data' => null]; } // 验证用户ID if (empty($user_id)) { return ['code' => 0, 'msg' => '用户ID不能为空', 'data' => null]; } // 验证盲盒ID if (empty($gift_bag_id)) { return ['code' => 0, 'msg' => '盲盒ID不能为空', 'data' => null]; } // 检查用户金币 $user_waller = db::name('user_wallet') ->where(['user_id' => $user_id]) ->find(); if (!$user_waller) { return ['code' => 0, 'msg' => '用户钱包不存在', 'data' => null]; } if ($user_waller['coin'] < $total_price) { return ['code' => 0, 'msg' => '用户金币不足', 'data' => null]; } return true; } //获取转盘礼包详细信息【缓存】 private function getCachedGiftBag($gift_bag_id,$is_cache = true) { $cacheKey = "pan_gift_bag".$gift_bag_id; $gift_bag_data = Cache::get($cacheKey); if (!$gift_bag_data || !$is_cache) { $bag_data = db::name("vs_gift_bag") ->field('id,name,ext,periods') ->where('id', $gift_bag_id) ->find(); if (!$bag_data) { return []; } $gift_bag_data = json_decode($bag_data['ext'], true); $gift_bag_data['gift_bag_id'] = $gift_bag_id; $gift_bag_data['gift_bag_name'] = $bag_data['name']; $gift_bag_data['periods'] = $bag_data['periods']; $gift_bag_data['gift_price'] = DB::name("vs_gift") ->where(['gid' => $gift_bag_data['gift_id']])->value('gift_price'); Cache::set($cacheKey, $gift_bag_data, $this->cache_time); } return $gift_bag_data; } //获取奖池详细信息【缓存】 private function getCachedGiftBagDetail($gift_bag_id,$is_cache = true) { $cacheKey = "pan_gift_bag_detail".$gift_bag_id; $pan_gift_bag_detail = Cache::get($cacheKey); if (empty($pan_gift_bag_detail) || !$is_cache) { $pan_gift_bag_detail = db::name("vs_gift_bag_detail") ->field('id,quantity,remaining_number,weight,foreign_id,gift_bag_id') ->where(['gift_bag_id' => $gift_bag_id]) ->select(); if (!$pan_gift_bag_detail) { return []; } $total_quantity = array_sum(array_column( $pan_gift_bag_detail, 'quantity')); $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); } return $pan_gift_bag_detail; } /** * 获取可用礼物 * @param $gift_bag_id * @param $total_draw_times 总抽奖次数 */ private function getAvailableGifts($gift_bag_id, $pan_total_draw_times = 0) { $gift_bag_detail_data = $this->getCachedGiftBagDetail($gift_bag_id); $gift_detail_data = []; foreach ($gift_bag_detail_data as $item) { if($item['remaining_number'] > 0 && $item['weight']<=$pan_total_draw_times && $item['quantity'] > 0){ $gift_detail_data[] = $item; } } return $gift_detail_data; } /** * 触发奖池重新生成 */ // private function triggerPoolRegeneration($gift_bag_id) // { // // 检查是否已经在重新生成奖池 // $regenLockKey = "blind_box_pool_regen_lock:{$gift_bag_id}"; // $lockAcquired = $this->redis->setnx($regenLockKey, 1); // // if ($lockAcquired) { // // 设置锁的过期时间 // $this->redis->expire($regenLockKey, 300); // 5分钟过期 // // // 触发异步奖池生成 // // 这里可以使用队列系统来处理 // $this->regenerateResultPool($gift_bag_id); // } // } /** * 生成奖池并存入Redis */ // public function generateGiftPool($gift_bag_id) // { // try { // // 获取奖池基本信息 // $poolKey = "blind_box_pool:{$gift_bag_id}"; // $poolInfoKey = "blind_box_pool_info:{$gift_bag_id}"; // // 检查Redis连接 // if (!$this->redis) { // throw new \Exception('Redis连接不可用'); // } // // 获取数据库中的期数信息 // $dbPeriods = db::name("vs_gift_bag") // ->where(['id' => $gift_bag_id]) // ->value('periods'); // // 获取奖池详细信息 // $gift_bag_detail_data = $this->getCachedGiftBagDetail($gift_bag_id); // // // 获取可用礼物列表 // $availableGifts = []; // foreach ($gift_bag_detail_data as $gift) { // if ($gift['remaining_number'] > 0) { // // 根据权重添加礼物到可选列表 // for ($i = 0; $i < $gift['weight']; $i++) { // $availableGifts[] = $gift; // } // } // } // if (empty($availableGifts)) { // throw new \Exception('没有可用的礼物'); // } // // 生成大量抽奖结果(例如1000个) // $results = []; // for ($i = 0; $i < 1000; $i++) { // $randomIndex = array_rand($availableGifts); // $selectedGift = $availableGifts[$randomIndex]; // // // 获取礼物详细信息 // $giftInfo = db::name("vs_gift") // ->where(['gid' => $selectedGift['foreign_id']]) // ->find(); // // $results[] = [ // 'gift_bag_detail' => $selectedGift, // 'gift' => $giftInfo ?? [] // ]; // } // // // 使用事务确保原子操作 // $this->redis->watch($poolKey, $poolInfoKey); // $this->redis->multi(); // // // 清空现有奖池 // $this->redis->del($poolKey); // // // 添加新结果到奖池 // foreach ($results as $result) { // $this->redis->rPush($poolKey, json_encode($result)); // } // // // 更新奖池信息 // $this->redis->hMSet($poolInfoKey, [ // 'periods' => $dbPeriods, // 'draw_times' => 0, // 'total_count' => count($results), // 'last_update' => time() // ]); // // $this->redis->exec(); // // Log::record("奖池生成成功,gift_bag_id: {$gift_bag_id}", 'info'); // return true; // } catch (\Exception $e) { // Log::record('生成奖池失败: ' . $e->getMessage(), 'error'); // return false; // } // } /** * 从奖池中获取一个礼物 */ // public function getGiftFromPool($gift_bag_id) // { // try { // $poolKey = "blind_box_pool:{$gift_bag_id}"; // // // 从Redis奖池中取出一个结果 // $result = $this->redis->lPop($poolKey); // // if (!$result) { // // 奖池为空,重新生成 // $this->generateGiftPool($gift_bag_id); // $result = $this->redis->lPop($poolKey); // } // return $result ? json_decode($result, true) : null; // } catch (\Exception $e) { // Log::record('从奖池获取礼物失败: ' . $e->getMessage(), 'error'); // return null; // } // } /** * 重置奖池 */ // private function resetGiftPool($gift_bag_id) // { // try { // // 更新数据库中各礼物的剩余数量为初始数量 // Db::name("vs_gift_bag_detail") // ->where(['gift_bag_id' => $gift_bag_id]) // ->update(['remaining_number' => Db::raw('quantity')]); // // // 清除相关缓存 // Cache::rm("pan_gift_bag_detail".$gift_bag_id); // // // 删除Redis中的奖池数据 // $poolKey = "blind_box_pool:{$gift_bag_id}"; // $poolInfoKey = "blind_box_pool_info:{$gift_bag_id}"; // $this->redis->del($poolKey); // $this->redis->del($poolInfoKey); // // 重新生成奖池 // $this->regenerateResultPool($gift_bag_id); // return true; // } catch (\Exception $e) { // Log::record('重置奖池失败: ' . $e->getMessage(), 'error'); // return false; // } // } /** * 重新生成结果奖池 */ // private function regenerateResultPool($gift_bag_id) // { // try { // // 计算奖池信息 // $poolInfo = $this->calculatePoolInfo($gift_bag_id); // if ($poolInfo['code'] !== 1) { // throw new \Exception('计算奖池信息失败'); // } // // $totalDrawTimes = $poolInfo['data']['total_draw_times']; // $periods = $poolInfo['data']['periods']; // // // 获取可用礼物 // $availableGifts = $this->getAvailableGifts($gift_bag_id); // if (empty($availableGifts)) { // $availableGifts = $this->resetPoolAndReload($gift_bag_id); // if (empty($availableGifts)) { // throw new \Exception('重置奖池后仍无可用礼物'); // } // $totalDrawTimes = 0; // } // // // 预加载礼物信息 // $giftInfoMap = $this->preloadGiftInfo($availableGifts); // // // 构建Alias表(别名表算法用于权重随机) // $aliasTable = $this->buildAliasTable($availableGifts); // if (!$aliasTable) { // throw new \Exception('构建Alias表失败'); // } // // // 生成大量抽奖结果(例如1000个) // $results = []; // $drawTimes = $totalDrawTimes; // // for ($i = 0; $i < 1000; $i++) { // $selectedGift = $this->selectGiftWithAliasMethod($aliasTable, $giftInfoMap); // if ($selectedGift) { // $results[] = [ // 'gift_bag_detail' => $selectedGift, // 'draw_times' => $drawTimes, // 'gift' => $giftInfoMap[$selectedGift['foreign_id']] ?? [] // ]; // // // 更新剩余数量 // $this->updateGiftRemainingNumber($selectedGift['id']); // $drawTimes++; // } // } // // // 将结果存储到Redis奖池中 // $poolKey = "blind_box_pool:{$gift_bag_id}"; // $poolInfoKey = "blind_box_pool_info:{$gift_bag_id}"; // // // 使用事务确保原子操作 // $this->redis->watch($poolKey, $poolInfoKey); // $this->redis->multi(); // // // 清空现有奖池 // $this->redis->del($poolKey); // // // 添加新结果到奖池 // foreach ($results as $result) { // $this->redis->rPush($poolKey, json_encode($result)); // } // // // 更新奖池信息 // $this->redis->hMSet($poolInfoKey, [ // 'periods' => $periods, // 'draw_times' => $drawTimes, // 'total_count' => count($results), // 'last_update' => time() // ]); // // $this->redis->exec(); // // // 释放重新生成锁 // $regenLockKey = "blind_box_pool_regen_lock:{$gift_bag_id}"; // $this->redis->del($regenLockKey); // // Log::record("奖池重新生成成功,gift_bag_id: {$gift_bag_id}", 'info'); // // } catch (\Exception $e) { // Log::record('重新生成奖池失败: ' . $e->getMessage(), 'error'); // // // 释放重新生成锁 // $regenLockKey = "blind_box_pool_regen_lock:{$gift_bag_id}"; // $this->redis->del($regenLockKey); // } // } /** * 重置奖池并重新加载 */ private function resetPoolAndReload($gift_bag_id) { // 重置剩余数量 Db::name("vs_gift_bag_detail") ->where(['gift_bag_id' => $gift_bag_id]) ->update(['remaining_number' => Db::raw('quantity')]); // 清除缓存 Cache::rm("pan_gift_bag_detail".$gift_bag_id); // 重新获取可用礼物 return $this->getAvailableGifts($gift_bag_id); } /** * 使用Alias Method选择礼物(O(1)复杂度) */ private function selectGiftWithAliasMethod($aliasTable) { 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; } } /** * 构建Alias表(O(n)复杂度,只执行一次) */ private function buildAliasTable($gifts) { $n = count($gifts); if ($n === 0) return null; $totalRemaining = array_sum(array_column($gifts, 'remaining_number')); if ($totalRemaining <= 0) return null; // 初始化Alias表 $small = []; $large = []; $prob = []; $alias = []; $indexMap = []; // 归一化概率并填充索引映射 foreach ($gifts as $i => $gift) { $indexMap[$i] = $gift; $prob[$i] = $gift['remaining_number'] * $n / $totalRemaining; if ($prob[$i] < 1.0) { $small[] = $i; } else { $large[] = $i; } } // 构建Alias表 while (!empty($small) && !empty($large)) { $s = array_pop($small); $l = array_pop($large); $alias[$s] = $l; $prob[$l] = ($prob[$l] + $prob[$s]) - 1.0; if ($prob[$l] < 1.0) { $small[] = $l; } else { $large[] = $l; } } return [ 'n' => $n, 'prob' => $prob, 'alias' => $alias, 'index_map' => $indexMap, 'gifts' => $gifts ]; } /** * 更新礼物剩余数量 */ private function updateGiftRemainingNumber($gift_bag_detail_id) { Db::name("vs_gift_bag_detail") ->where(['id' => $gift_bag_detail_id]) ->setDec('remaining_number', 1); } /** * 更新缓存中的奖池信息 */ private function updateCachedGiftBagDetail($gift_bag_id, $detail_id, $decrement = 1) { $cacheKey = "pan_gift_bag_detail".$gift_bag_id; $gift_bag_detail_data = Cache::get($cacheKey); if ($gift_bag_detail_data) { foreach ($gift_bag_detail_data as &$item) { if ($item['id'] == $detail_id) { $item['remaining_number'] -= $decrement; break; } } Cache::set($cacheKey, $gift_bag_detail_data, $this->cache_time); } } /** * 获取奖池总库存 */ private function getCachedPanTotalQuantity($gift_bag_id) { $cacheKey = "pan_total_quantity".$gift_bag_id; $pan_total_quantity = Cache::get($cacheKey); if(!$pan_total_quantity) { $pan_total_quantity = Db::name("vs_gift_bag_detail") ->where(['gift_bag_id' => $gift_bag_id]) ->sum('quantity'); Cache::set($cacheKey, $pan_total_quantity, $this->cache_time); } return $pan_total_quantity; } /** * 获取奖池剩余库存 */ private function getCachedPanTotalRemaining($gift_bag_id, $type="get", $check_num=1) { $cacheKey = "pan_total_remaining".$gift_bag_id; $pan_total_quantity = Cache::get($cacheKey); if($type == "set"){ $pan_total_quantity = $pan_total_quantity-$check_num; if($pan_total_quantity < 0){ $pan_total_quantity = 0; } Cache::set($cacheKey, $pan_total_quantity, $this->cache_time); }elseif($type == "clear"){ $pan_total_quantity = 0; Cache::set($cacheKey, $pan_total_quantity, $this->cache_time); } else{ if(!$pan_total_quantity) { $pan_total_quantity = Db::name("vs_gift_bag_detail") ->where(['gift_bag_id' => $gift_bag_id]) ->sum('remaining_number'); Cache::set($cacheKey, $pan_total_quantity, $this->cache_time); } } return $pan_total_quantity; } /** * 获取缓存的抽奖次数 */ private function getCachedPanDrawTimes($gift_bag_id,$type="get") { $cacheKey = "pan_total_draw".$gift_bag_id; $total_draw_times = Cache::get($cacheKey) ?? 0; if($type == "set"){ $total_draw_times = $total_draw_times+1; Cache::set($cacheKey, $total_draw_times, $this->cache_time); }elseif($type == "clear"){ $total_draw_times = 0; Cache::set($cacheKey, $total_draw_times, $this->cache_time); } return $total_draw_times; } /** * 获取缓存的巡乐会开启次数 */ private function getCachedXlhPeriodsNum($type="get",$num=1) { $cacheKey = "pan_xlh_periods_num"; $xlh_periods_num = Cache::get($cacheKey) ?? 0; if($type=="set"){ $xlh_periods_num = $xlh_periods_num + $num; Cache::set($cacheKey, $xlh_periods_num, 0); } return $xlh_periods_num; } /** * 获取缓存的巡乐会期数 */ private function getCachedXlhPeriods($type="get",$periods=1) { $cacheKey = "pan_this_xlh_periods"; $xlh_periods_num = Cache::get($cacheKey) ?? 0; if($type=="set"){ $xlh_periods_num = $periods; Cache::set($cacheKey, $xlh_periods_num, 0); } return $xlh_periods_num; } //开始更新操作: /** * 执行抽奖事务(核心操作) */ private function executeDrawTransaction($bag_data, $user_id, $room_id, $num,$total_price, $precomputedResults,$availableGiftss,$heart_id,$auction_id) { try { db::startTrans(); // 按照固定顺序处理事务步骤 // 1. 扣除用户金币(优先处理) $this->deductUserCoins($user_id, $total_price, $room_id); // 2. 创建抽奖记录 $boxTurntableLog = db::name('vs_blind_box_turntable_log')->insertGetId([ 'user_id' => $user_id, 'gift_bag_id' => $bag_data['gift_bag_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); // 4. 批量插入礼包发放记录 $this->batchInsertGiftBagReceiveLog($bag_data,$user_id, $boxTurntableLog,$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(); throw new \Exception($e->getMessage()); } } /** * 扣除用户金币 */ private function deductUserCoins($user_id, $bagGiftPrice, $room_id) { // 使用悲观锁查询用户钱包 $userWallet = db::name('user_wallet') ->where(['user_id' => $user_id]) ->find(); if (!$userWallet || $userWallet['coin'] < $bagGiftPrice) { throw new \Exception('用户金币不足'); } $walletUpdate = model('GiveGift')->change_user_cion_or_earnings_log( $user_id, $bagGiftPrice, $room_id, 1, 10, '盲盒转盘抽奖消耗' ); if (!$walletUpdate) { throw new \Exception('扣除用户金币失败'); } //更新用户等级 $userLevel = model('Level')->user_level_data_update( $user_id, $bagGiftPrice, 1, $room_id ); if (!$userLevel) { throw new \Exception('用户等级更新失败'); } } /** * 批量更新礼物库存 */ private function batchUpdateGiftInventory($precomputedResults) { // 按礼物ID分组统计需要减少的数量 $inventoryUpdates = []; foreach ($precomputedResults as $result) { $giftId = $result['gift_bag_detail']['id']??0; $inventoryUpdates[$giftId] = ($inventoryUpdates[$giftId] ?? 0) + 1; } // 按ID排序避免死锁 ksort($inventoryUpdates); // 批量更新 foreach ($inventoryUpdates as $giftId => $count) { $giftBagDetail = Db::name("vs_gift_bag_detail") ->where('id', $giftId) ->find(); if (!$giftBagDetail) { throw new \Exception("礼物详情不存在,ID: " . $giftId); } // 检查库存是否足够 if ($giftBagDetail['remaining_number'] < $count) { throw new \Exception("礼物库存不足,ID: " . $giftId); } $ret = db::name("vs_gift_bag_detail")->where('id',$giftId) ->setDec('remaining_number', $count); if (!$ret) { throw new \Exception('更新礼物剩余数量失败'); } // 同时更新缓存中的库存信息 $this->updateCachedGiftBagDetail($giftBagDetail['gift_bag_id'], $giftId, $count); } } /** * 批量插入礼包发放记录 */ private function batchInsertGiftBagReceiveLog($bag_data,$user_id, $boxTurntableLog,$room_id, $precomputedResults) { $batchInsertData = []; foreach ($precomputedResults as $result) { if(!isset($result['gift']['gift_price'])){ Log::record('数据报错:'.json_encode($result),"info"); } $batchInsertData[] = [ 'user_id' => $user_id, 'gift_user_id' => $result['gift_user_id'], 'parent_id' => $boxTurntableLog, 'gift_bag_id' => $bag_data['gift_bag_id'], 'gift_id' => $result['gift_bag_detail']['foreign_id'], 'periods' => $result['periods'], 'room_id' => $room_id, 'gift_price' => $result['gift']['gift_price'], 'bag_price' => $bag_data['gift_price'], 'createtime' => time() ]; } if (!empty($batchInsertData)) { $insertResult = db::name("vs_gift_bag_receive_pan_log")->insertAll($batchInsertData); if (!$insertResult) { throw new \Exception('插入礼包发放记录失败'); } } } /** * 发送礼物给接收者 */ private function sendGiftsToRecipients($precomputedResults, $room_id,$user_id,$heart_id,$auction_id) { // 统计每个用户每个礼物的数量 $giftUserCounts = []; foreach ($precomputedResults as $result) { $key = $result['gift_user_id'] . '_' . $result['gift_bag_detail']['foreign_id']; if (!isset($giftUserCounts[$key])) { $giftUserCounts[$key] = [ 'gift_user_id' => $result['gift_user_id'], 'gift_id' => $result['gift_bag_detail']['foreign_id'], 'count' => 0, 'gift_price' => $result['gift']['gift_price'] ]; } $giftUserCounts[$key]['count']++; } // 批量发送礼物 foreach ($giftUserCounts as $userGift) { if($userGift['count'] > 9){ //防止礼物超发,礼物超10个则不发送 continue; } $giveGiftExt = [ 'gift_id' => $userGift['gift_id'], 'count' => $userGift['count'], 'gift_price' => $userGift['gift_price'], 'all_gift_price' => $userGift['gift_price'] * $userGift['count'], 'is_draw_gift' => 1 ]; if(!empty($auction_id)){ //竞拍 model('RoomAuction')->room_auction_join($auction_id,$user_id,$userGift['gift_id'],$userGift['count'],2,$giveGiftExt); }else{ $res = model('Room')->room_gift( $user_id, $userGift['gift_user_id'], $userGift['gift_id'], $userGift['count'], 1, $room_id, 0, $heart_id, $giveGiftExt ); } if (isset($res) && $res['code'] != 1) { Log::record('发送礼物失败: ' . $res['msg'] . $userGift['gift_user_id'], "info"); return ['code' => 0, 'msg' => $res['msg'], 'data' => null]; } } return ['code' => 1, 'msg' => '发送礼物成功', 'data' => null]; } /** * 统计礼物数量 */ private function countGifts($precomputedResults) { $giftCounts = []; foreach ($precomputedResults as $result) { $giftId = $result['gift_bag_detail']['foreign_id']; if (!isset($giftCounts[$giftId])) { $giftCounts[$giftId] = [ 'gift_user_id' => $result['gift_user_id'], 'gift_id' => $giftId, 'count' => 0, 'gift_price' => $result['gift']['gift_price'], 'quantity' => $result['gift_bag_detail']['quantity'] ]; } $giftCounts[$giftId]['count']++; } return $giftCounts; } /** * 处理抽奖后的后续操作(非事务性) */ private function handlePostDrawOperations( $precomputedResults, $boxTurntableLog, $room_id ) { // 获取巡乐会配置(使用缓存) $xlh_ext = $this->getCachedGiftBag(13); // 1. 批量插入盲盒转盘结果记录 $this->batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog); // 2. 处理巡乐会相关操作 if (!empty($xlh_ext) && $xlh_ext['inlet_bag_id'] == $precomputedResults[0]['gift_bag_detail']['gift_bag_id']) { $this->handleXlhOperations($room_id, $xlh_ext); } } /** * 批量插入盲盒转盘结果记录 */ private function batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog) { // 统计每个用户每个礼物的数量 $giftUserCounts = $this->countGifts($precomputedResults); // 批量插入 $batchInsertData = []; foreach ($giftUserCounts as $userGift) { $batchInsertData[] = [ 'tid' => $boxTurntableLog, 'gift_user_id' => $userGift['gift_user_id'], 'gift_id' => $userGift['gift_id'], 'count' => $userGift['count'], 'gift_price' => $userGift['gift_price'], 'all_gift_price' => $userGift['gift_price'] * $userGift['count'], 'createtime' => time(), 'heart_id' => 0 ]; } if (!empty($batchInsertData)) { db::name('vs_blind_box_turntable_results_log')->insertAll($batchInsertData); } } /** * 处理巡乐会相关操作 */ private function handleXlhOperations($room_id, $xlh_ext) { $xlhIsPiaoPing = 0; $xlhPeriodsNum = $this->getCachedXlhPeriodsNum("get"); if($xlhPeriodsNum == $xlh_ext['open_condition']['waiting_start_num']){ $xlhIsPiaoPing = 1; } if($xlhPeriodsNum == $xlh_ext['open_condition']['start_num']){ $xlhIsPiaoPing = 2; } // 处理飘屏 if ($xlhIsPiaoPing == 1 || $xlhIsPiaoPing == 2) { $this->handleXlhPiaoPing($room_id, $xlh_ext, $xlhIsPiaoPing); } $this->updateAndPushXlhStatus($room_id, $xlh_ext); } private function handleXlhPiaoPing($room_id, $xlh_ext, $xlhIsPiaoPing){ if($xlhIsPiaoPing == 1){ // 即将开始推送飘屏 $text = "巡乐会即将开始..."; // 推送礼物横幅 $push = new Push(UID, $room_id); $text_list_new = [ 'text' => $text, 'room_id' => $room_id, 'from_type' => 101 ]; $push->xunlehui($text_list_new); } if($xlhIsPiaoPing == 2){ // 正式开始推送飘屏 $text = "巡乐会游戏已正式开启..."; // 推送礼物横幅 $push = new Push(UID, $room_id); $text_list_new = [ 'text' => $text, 'room_id' => $room_id, 'from_type' => 102 ]; $push->xunlehui($text_list_new); // 巡乐会正式开始 $this_xlh_periods = $this->getCachedXlhPeriods('get'); $pan_xlh_id = db::name('vs_room_pan_xlh')->insertGetId([ 'room_id' => $room_id, 'gift_id' => $xlh_ext['locking_condition']['locking_gift_id'], 'homeowner_gift_id' => $xlh_ext['locking_condition']['give_homeowner_gift_id'], 'periods' => $this_xlh_periods+1, 'num' => 0, 'end_time' => time() + $xlh_ext['locking_time']['end_time'] * 60, 'createtime' => time() ]); if(!$pan_xlh_id){ return ['code' => 0, 'msg' => '创建巡乐会失败!', 'data' => []]; } $this->getCachedXlhPeriods('set', $this_xlh_periods+1);//修改巡乐会期数 } } private function updateAndPushXlhStatus($room_id, $xlh_ext){ $xlh['waiting_start_num'] = $xlh_ext['open_condition']['waiting_start_num'];//等待开奖次数 $xlh['start_num'] = $xlh_ext['open_condition']['start_num'];//开始开奖次数 // 当前抽奖次数 $xlh['current_num'] = $this->getCachedXlhPeriodsNum("get"); $xlh['end_time'] = 0; // 状态 if($xlh['current_num'] >= $xlh_ext['open_condition']['start_num']){ $xlh['status'] = 1;//状态 1:巡乐会开始 2:即将开始开始 0:等待开始 //查询巡乐会信息 $pan_xlh = db::name('vs_room_pan_xlh')->where('send_time',0)->order('id desc')->find(); if(empty($pan_xlh)){ $this->handleXlhPiaoPing($room_id, $xlh_ext, 2); $pan_xlh = db::name('vs_room_pan_xlh')->where('send_time',0)->order('id desc')->find(); } $xlh['end_time'] = $pan_xlh['end_time'] ?? 0; } elseif($xlh['current_num'] >= $xlh_ext['open_condition']['waiting_start_num'] && $xlh['current_num'] < $xlh_ext['open_condition']['start_num']){ $xlh['status'] = 2;//状态 1:巡乐会开始 2:即将开始开始 0:等待开始 }else{ $xlh['status'] = 0; } // 推送进度条 $push = new Push(UID, $room_id); $text_list_new = [ 'xlh_data' => $xlh, 'text' => "", 'room_id' => $room_id, 'from_type' => 100 ]; $push->xunlehui($text_list_new); } /** * 构建抽奖结果 */ private function buildDrawResult($boxTurntableLog, $giftCounts) { $resultList = []; foreach ($giftCounts as $gift) { $resultList[] = [ 'gift_id' => $gift['gift_id'], 'count' => $gift['count'] ]; } return [ 'code' => 1, 'msg' => '成功', 'data' => [ 'blind_box_turntable_id' => $boxTurntableLog, 'reslut_list' => $resultList ] ]; } }