diff --git a/application/api/model/BlindBoxTurntableGift.php b/application/api/model/BlindBoxTurntableGift.php index a7821776..d719285c 100644 --- a/application/api/model/BlindBoxTurntableGift.php +++ b/application/api/model/BlindBoxTurntableGift.php @@ -76,6 +76,9 @@ class BlindBoxTurntableGift extends Model public function gift_send($send_id){ try{ $blind_box_turntable = Db::name('vs_blind_box_turntable_log')->where(['id'=>$send_id,'is_sued'=>0])->find(); + if(in_array($blind_box_turntable['gift_bag_id'],[11,12])){ + return ['code' => 1, 'msg' => '成功', 'data' => null]; + } if(!$blind_box_turntable){ return ['code' => 1, 'msg' => '成功', 'data' => null]; } diff --git a/application/api/model/BlindBoxTurntableGiftDraw.php b/application/api/model/BlindBoxTurntableGiftDraw.php deleted file mode 100644 index 07bb322b..00000000 --- a/application/api/model/BlindBoxTurntableGiftDraw.php +++ /dev/null @@ -1,1603 +0,0 @@ -redis = new Redis(); - // 连接到Redis服务器 - $this->redis->connect(config('redis.host'), config('redis.port')); // 根据实际配置调整主机和端口 - // 选择数据库1 - $this->redis->select(1); - } 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) - { - try { - // 1. 验证参数并提前处理错误 - $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids, $room_id); - if ($validationResult !== true) { - return $validationResult; - } - - // 2. 预加载必要数据 - $loadResult = $this->loadDrawData($gift_bag_id, $user_id, $room_id,$num,$gift_user_ids); - if ($loadResult['code'] !== 1) { - return $loadResult; - } - ['bag_data' => $bag_data, 'room' => $room, 'xlh_ext' => $xlh_ext] = $loadResult['data']; - - // 3. 预计算抽奖结果 - $precomputeResult = $this->precomputeDrawResults( - $bag_data, - $room, - $gift_user_ids, - $num, - $room_id - ); - if ($precomputeResult['code'] !== 1) { - return $precomputeResult; - } - $precomputedResults = $precomputeResult['data']['results']; - $availableGiftss = $precomputeResult['data']['availableGifts']; - $currentXlhPeriodsNum = $precomputeResult['data']['current_xlh_periods_num']; - $xlhIsPiaoPing = $precomputeResult['data']['xlh_is_piao_ping']; - $expectedCount = count(explode(',', $gift_user_ids)) * $num; - if(count($precomputedResults) != $expectedCount){ - // 记录错误到Redis - $this->recordDrawErrorToRedis($expectedCount, count($precomputedResults), $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults); - return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null]; - } - // 4. 执行抽奖事务(核心操作) - $transactionResult = $this->executeDrawTransaction( - $bag_data, - $user_id, - $room_id, - $num, - $precomputedResults, - $availableGiftss, - $gift_user_ids, - $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, - $xlh_ext, - $xlhIsPiaoPing, - $currentXlhPeriodsNum, - $room, - $user_id - ); - - // 6. 构建并返回结果 - return $this->buildDrawResult($boxTurntableLog, $giftCounts); - - } catch (\Exception $e) { - $key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'); - $this->redis->setex($key, 86400 * 7, $e->getMessage()); - return ['code' => 0, 'msg' => "网络加载失败,请重试!", 'data' => null]; - } - - } - /** - * 验证抽奖参数 - */ - private function validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids, $room_id) - { - // 提前验证收礼人 - $toarray = explode(',', $gift_user_ids); - if (in_array($user_id, $toarray)) { - return ['code' => 0, 'msg' => "收礼人不能包含自己", 'data' => null]; - } - - // 验证房间ID - if (empty($room_id)) { - return ['code' => 0, 'msg' => '房间ID不能为空', '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]; - } - - return true; - } - /** - * 预加载必要数据 - */ - private function loadDrawData($gift_bag_id, $user_id, $room_id,$num,$gift_user_ids) - { - // 1. 合并查询盲盒配置和礼物信息 - $bag_data = db::name("vs_gift_bag") - ->alias('bag') - ->join('vs_gift gift', 'gift.gid = JSON_UNQUOTE(JSON_EXTRACT(bag.ext, "$.gift_id"))', 'LEFT') - ->field('bag.id,bag.name,bag.ext,gift.gid as gift_id,gift.gift_price') - ->where('bag.id', $gift_bag_id) - ->find(); - - if (!$bag_data || !$bag_data['gift_price']) { - return ['code' => 0, 'msg' => '盲盒配置不存在或盲盒礼物不存在', 'data' => null]; - } - - // 2. 获取房间信息 - $room = db::name('vs_room') - ->field('id,room_name,xlh_periods,xlh_periods_num,is_open_blind_box_turntable') - ->where(['id' => $room_id]) - ->find(); - - if (!$room) { - return ['code' => 0, 'msg' => '房间不存在', 'data' => null]; - } - - if ($room['is_open_blind_box_turntable'] != 1) { - return ['code' => 0, 'msg' => '该房间未开启盲盒转盘', 'data' => null]; - } - - // 3. 检查用户金币 - $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'] < $bag_data['gift_price'] * $num * count(explode(',', $gift_user_ids))) { - return ['code' => 0, 'msg' => '用户金币不足', 'data' => null]; - } - // 4. 获取巡乐会配置(使用缓存) - $xlh_ext = $this->getCachedXlhConfig(); - - return [ - 'code' => 1, - 'msg' => '数据加载成功', - 'data' => [ - 'bag_data' => $bag_data, - 'room' => $room, - 'xlh_ext' => $xlh_ext, - 'user_waller' => $user_waller - ] - ]; - } - /** - * 预计算抽奖结果 - */ - private function precomputeDrawResults($bag_data, $room, $gift_user_ids, $num, $room_id) - { - $toarray = explode(',', $gift_user_ids); - $ext = json_decode($bag_data['ext'], true); - $xlh_ext = $this->getCachedXlhConfig(); - $xlh_is_piao_ping = 0; - $current_xlh_periods_num = $room['xlh_periods_num']; - - // 1. 计算奖池信息 - $poolInfo = $this->calculatePoolInfo($bag_data['id'], $room_id); - if ($poolInfo['code'] !== 1) { - return $poolInfo; - } - $totalQuantity = $poolInfo['data']['total_quantity']; - $totalRemaining = $poolInfo['data']['total_remaining']; - $periods = $poolInfo['data']['periods']; - $totalDrawTimes = $poolInfo['data']['total_draw_times']; - - // 2. 获取可用礼物 - $availableGifts = $this->getAvailableGifts($bag_data['id'], $room_id, $totalDrawTimes); - if (empty($availableGifts)) { - $availableGifts = $this->resetPoolAndReload($bag_data['id'], $room_id, $periods + 1, 0); - if (empty($availableGifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - } - - // 3. 预加载礼物信息(减少后续查询) - $giftInfoMap = $this->preloadGiftInfo($availableGifts); - - // 4. 处理奖池重置逻辑 - $needGiftNum = count($toarray) * $num; - $remaining_available_gifts=[]; - if ($totalRemaining - $needGiftNum <= 0) { - $remaining_available_gifts = $availableGifts; - $availableGifts = $this->resetPoolAndReload($bag_data['id'], $room_id, $periods + 1, 0); - if (empty($availableGifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - $totalDrawTimes = 0; - $num = abs($totalRemaining - $num); - } - - // 5. 使用Alias Method预计算抽奖结果(O(1)复杂度) - $precomputedResults = $this->precomputeResultsWithAliasMethod( - $toarray, - $num, - $availableGifts, - $giftInfoMap, - $totalDrawTimes, - $periods, - $current_xlh_periods_num, - $xlh_ext, - $bag_data['id'], - $room_id, - $remaining_available_gifts - ); - if (empty($precomputedResults['precomputedResults'])) { - throw new \Exception('预计算抽奖结果失败'); - } - - return [ - 'code' => 1, - 'msg' => '预计算成功', - 'data' => [ - 'results' => $precomputedResults['precomputedResults'], - 'current_xlh_periods_num' => $current_xlh_periods_num, - 'xlh_is_piao_ping' => $xlh_is_piao_ping, - 'availableGifts' => $precomputedResults['precomputedResultss'], - ] - ]; - } - - /** - * 使用Alias Method预计算抽奖结果(O(1)复杂度) - */ - private function precomputeResultsWithAliasMethod( - $toarray, - $num, - $availableGifts, - $giftInfoMap, - &$totalDrawTimes, - &$periods, - &$currentXlhPeriodsNum, - $xlhExt, - $giftBagId, - $roomId, - $remaining_available_gifts - ) { - //计算$remaining_available_gifts 里面 remaining_number 的累加 - $remaining_num = 0; - foreach ($remaining_available_gifts as $key=>$value) { - $remaining_num += $value['remaining_number']; - } - if($remaining_num > $num){ - $availableGifts = $remaining_available_gifts; - $remaining_available_gifts = []; - } - $precomputedResults = []; - $precomputedResultss = []; - $giftBagIdToGift = []; - - // 构建Alias表 - $aliasTable = $this->buildAliasTable($availableGifts); - $remaining_num = 0; - foreach ($toarray as $giftUserId) { - if (!empty($remaining_available_gifts)) { - foreach ($remaining_available_gifts as $key=>$value) { - $remaining_num += $value['remaining_number']; - $gift = $giftInfoMap[$value['foreign_id']] ?? null; - for ($j = 0; $j < $value['remaining_number']; $j++) { - $precomputedResults[] = [ - 'gift_user_id' => $giftUserId, - 'gift_bag_detail' => $value, - 'gift' => $gift, - 'draw_times' => $totalDrawTimes, - 'periods' => $periods, - 'j' => $j, - ]; - $totalDrawTimes++; - $currentXlhPeriodsNum++; - - } - unset($remaining_available_gifts[$key]); - } - $numm = $num; - }else{ - $numm = $num+$remaining_num; - } - for ($i = 0; $i < $numm; $i++) { - // 使用Alias Method选择礼物(O(1)复杂度) - $selectedGift = $this->selectGiftWithAliasMethod($aliasTable); - if (!$selectedGift) { - return []; - } - - // 获取礼物信息(从预加载的map中获取,避免查询) - $giftId = $selectedGift['foreign_id']; - $gift = $giftInfoMap[$giftId] ?? null; -// if (!$gift) { -// continue; -// } - - $precomputedResults[] = [ - 'gift_user_id' => $giftUserId, - 'gift_bag_detail' => $selectedGift, - 'gift' => $gift, - 'draw_times' => $totalDrawTimes, - 'periods' => $periods, - 'i' => $i, - ]; - $precomputedResultss[] = [ - 'gift_user_id' => $giftUserId, - 'gift_bag_detail' => $selectedGift, - 'gift' => $gift, - 'draw_times' => $totalDrawTimes, - 'periods' => $periods, - 'i' => $i, - ]; - - $totalDrawTimes++; - $currentXlhPeriodsNum++; - - // 更新Alias表(模拟库存减少) - $this->updateAliasTable($aliasTable, $selectedGift['id']); - - // 检查巡乐会状态 - if (!empty($xlhExt) && $xlhExt['inlet_bag_id'] == $giftBagId) { - if ($currentXlhPeriodsNum == $xlhExt['open_condition']['waiting_start_num']) { - $xlh_is_piao_ping = 1; - } - if ($currentXlhPeriodsNum == $xlhExt['open_condition']['start_num']) { - $xlh_is_piao_ping = 2; - } - } - } - } - - return ['precomputedResults' => $precomputedResults, 'precomputedResultss' => $precomputedResultss]; - } - - /** - * 构建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 - ]; - } - - /** - * 使用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表(模拟库存减少) - */ - 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 executeDrawTransaction($bag_data, $user_id, $room_id, $num, $precomputedResults,$availableGiftss,$gift_user_ids,$heart_id,$auction_id) - { - $gift_user_num = count(explode(',', $gift_user_ids)); //人数 - $bagGiftPrice = $bag_data['gift_price'] * $num * $gift_user_num; - - db::startTrans(); - try { - // 1. 创建抽奖记录 - $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('添加盲盒转盘记录失败'); - } - - // 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]; - } - } - - /** - * 批量更新礼物库存 - */ - private function batchUpdateGiftInventory($precomputedResults, $room_id) - { - // 按礼物ID分组统计需要减少的数量 - $inventoryUpdates = []; - foreach ($precomputedResults as $result) { - $giftId = $result['gift_bag_detail']['gift_bag_detail_id']; - $inventoryUpdates[$giftId] = ($inventoryUpdates[$giftId] ?? 0) + 1; - } - - // 批量更新 - foreach ($inventoryUpdates as $giftId => $count) { - $ret = db::name("vs_room_pan") - ->where([ - 'room_id' => $room_id, - 'gift_bag_detail_id' => $giftId, -// 'remaining_number' => ['>=', $count] - ]) - ->setDec('remaining_number', $count); - - if (!$ret) { - Log::record('巡乐会更新礼物剩余数量: ' . $room_id."【数据】".var_export($precomputedResults, true),"info"); - throw new \Exception('更新礼物剩余数量失败'); - } - } - } - - /** - * 批量插入礼包发放记录 - */ - private function batchInsertGiftBagReceiveLog($user_id, $boxTurntableLog, $bag_data, $room_id, $precomputedResults) - { - $batchInsertData = []; - - foreach ($precomputedResults as $result) { - $batchInsertData[] = [ - 'user_id' => $user_id, - 'gift_user_id' => $result['gift_user_id'], - 'parent_id' => $boxTurntableLog, - 'gift_bag_id' => $bag_data['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_log")->insertAll($batchInsertData); - if (!$insertResult) { - throw new \Exception('插入礼包发放记录失败'); - } - } - } - - /** - * 扣除用户金币 - */ - private function deductUserCoins($user_id, $bagGiftPrice, $room_id) - { - $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 handlePostDrawOperations( - $precomputedResults, - $boxTurntableLog, - $room_id, - $xlh_ext, - $xlhIsPiaoPing, - $currentXlhPeriodsNum, - $room, - $user_id - ) { - // 1. 批量插入盲盒转盘结果记录 - $this->batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog, $room_id); - - // 2. 发送礼物给接收者 (已前置) -// $this->sendGiftsToRecipients($precomputedResults, $room_id,$user_id); - - // 3. 处理巡乐会相关操作 - if (!empty($xlh_ext) && $xlh_ext['inlet_bag_id'] == $precomputedResults[0]['gift_bag_detail']['gift_bag_id']) { - $this->handleXlhOperations($room_id, $xlh_ext, $xlhIsPiaoPing, $currentXlhPeriodsNum,$room); - } - } - - /** - * 批量插入盲盒转盘结果记录 - */ - private function batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog, $room_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']++; - } - - // 批量插入 - $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 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 handleXlhOperations($room_id, $xlh_ext, $xlhIsPiaoPing, $currentXlhPeriodsNum,$room) - { - if($room['xlh_periods_num'] < $xlh_ext['open_condition']['waiting_start_num'] && $currentXlhPeriodsNum >= $xlh_ext['open_condition']['waiting_start_num']){ - $xlhIsPiaoPing = 1; - } - if($room['xlh_periods_num'] < $xlh_ext['open_condition']['start_num'] && $currentXlhPeriodsNum >= $xlh_ext['open_condition']['start_num']){ - $xlhIsPiaoPing = 2; - } - - // 更新房间巡乐会次数 - db::name("vs_room")->where('id', $room_id)->update([ - 'xlh_periods_num' => $currentXlhPeriodsNum - ]); - - // 处理飘屏 - if ($xlhIsPiaoPing == 1 || $xlhIsPiaoPing == 2) { - $this->handleXlhPiaoPing($room_id, $xlh_ext, $xlhIsPiaoPing,$room); - } - - // 更新巡乐会状态并推送 - $this->updateAndPushXlhStatus($room_id, $xlh_ext, $currentXlhPeriodsNum); - } - private function handleXlhPiaoPing($room_id, $xlh_ext, $xlhIsPiaoPing,$room){ - if($xlhIsPiaoPing == 1){ - // 即将开始推送飘屏 - $text = $room['room_name']."的巡乐会即将开始..."; - // 推送礼物横幅 - $push = new Push(UID, $room_id); - $text_list_new = [ - 'text' => $text, - 'room_id' => $room_id, - 'from_type' => 1 - ]; - $push->xunlehui($text_list_new); - } - if($xlhIsPiaoPing == 2){ - // 正式开始推送飘屏 - $text = $room['room_name']."已正式开启巡乐会游戏..."; - // 推送礼物横幅 - $push = new Push(UID, $room_id); - $text_list_new = [ - 'text' => $text, - 'room_id' => $room_id, - 'from_type' => 2 - ]; - $push->xunlehui($text_list_new); - // 巡乐会正式开始 - $this_xlh_periods = db::name('vs_room')->where('id',$room_id)->value('xlh_periods'); - $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' => []]; - } - db::name("vs_room")->where('id',$room_id)->setInc('xlh_periods');//修改巡乐会期数 - } - } - private function updateAndPushXlhStatus($room_id, $xlh_ext, $currentXlhPeriodsNum){ - $xlh['waiting_start_num'] = $xlh_ext['open_condition']['waiting_start_num'];//等待开奖次数 - $xlh['start_num'] = $xlh_ext['open_condition']['start_num'];//开始开奖次数 - // 当前抽奖次数 - $xlh['current_num'] = $currentXlhPeriodsNum; - $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('room_id',$room_id)->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; - } - // 推送 - $text = [ - 'xlh_data' => $xlh, - 'text' => "" - ]; - // 聊天室推送系统消息 - model('Chat')->sendMsg(1056,$room_id,$text); - } - /** - * 统计礼物数量 - */ - private function countGifts($precomputedResults) - { - $giftCounts = []; - foreach ($precomputedResults as $result) { - $giftId = $result['gift_bag_detail']['foreign_id']; - if (!isset($giftCounts[$giftId])) { - $giftCounts[$giftId] = [ - 'gift_id' => $giftId, - 'count' => 0, - 'gift_price' => $result['gift']['gift_price'] - ]; - } - $giftCounts[$giftId]['count']++; - } - return $giftCounts; - } - - /** - * 构建抽奖结果 - */ - 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 - ] - ]; - } - /** - * 计算奖池信息 - */ - private function calculatePoolInfo($gift_bag_id, $room_id) - { - $roomPanInfo = db::name("vs_gift_bag_detail") - ->alias('a') - ->join('vs_room_pan b', 'b.gift_bag_detail_id = a.id AND b.room_id = ' . $room_id) - ->where(['a.gift_bag_id' => $gift_bag_id]) - ->field('SUM(a.quantity) as total_quantity, SUM(b.remaining_number) as total_remaining, MAX(b.periods) as periods') - ->find(); - - $totalQuantity = $roomPanInfo['total_quantity'] ?: 0; - $totalRemaining = $roomPanInfo['total_remaining'] ?: 0; - $periods = $roomPanInfo['periods'] ?: 0; - $totalDrawTimes = max(0, $totalQuantity - $totalRemaining); - - return [ - 'code' => 1, - 'msg' => '计算成功', - 'data' => [ - 'total_quantity' => $totalQuantity, - 'total_remaining' => $totalRemaining, - 'periods' => $periods, - 'total_draw_times' => $totalDrawTimes - ] - ]; - } - - /** - * 获取可用礼物 - */ - private function getAvailableGifts($gift_bag_id, $room_id, $total_draw_times) - { - $where = [ - 'a.gift_bag_id' => $gift_bag_id, - 'a.quantity' => ['>', 0], - 'b.remaining_number' => ['>', 0], - 'b.room_id' => $room_id, - 'a.weight' => ['<=', $total_draw_times], - ]; - - return db::name("vs_gift_bag_detail") - ->field('b.id,a.quantity,b.remaining_number,a.weight,a.foreign_id,a.gift_bag_id,b.gift_bag_detail_id') - ->alias('a') - ->join('vs_room_pan b', 'b.gift_bag_detail_id = a.id', 'left') - ->where($where) - ->select(); - } - - /** - * 预加载礼物信息 - */ - private function preloadGiftInfo($available_gifts) - { - $giftIds = array_unique(array_column($available_gifts, 'foreign_id')); - if (empty($giftIds)) return []; - - $gifts = db::name('vs_gift') - ->where('gid', 'in', $giftIds) - ->select(); - - return array_column($gifts, null, 'gid'); - } - - /** - * 重置奖池并重新加载 - */ - private function resetPoolAndReload($gift_bag_id, $room_id, $periods, $total_draw_times) - { - $this->reset_gift_pool($room_id, $gift_bag_id, $periods); - return $this->getAvailableGifts($gift_bag_id, $room_id, $total_draw_times); - } - - /** - * 获取缓存的巡乐会配置 - */ - private function getCachedXlhConfig() { - $cacheKey = "xlh_config_13"; - $ext = Cache::get($cacheKey); - if (!$ext) { - $bag_data = db::name("vs_gift_bag") - ->field('id,name,ext,periods') - ->where('id', 13) - ->find(); - if (!$bag_data) { - return []; - } - $ext = json_decode($bag_data['ext'], true); - $ext['gift_bag_name'] = $bag_data['name']; - Cache::set($cacheKey, $ext, 3600); // 缓存1小时 - } - return $ext; - } - - /** - * 重置奖池 - * @param int $room_id 房间ID - * @param int $gift_bag_id 礼物包ID - * @param int $periods 期数 - */ - private function reset_gift_pool($room_id, $gift_bag_id, $periods,$remaining_available_gifts=[]) { - $room_pan = db::name("vs_room_pan") - ->where(['room_id'=>$room_id,'gift_bag_id'=>$gift_bag_id]) - ->select(); - // 重置奖池中所有礼物数量 - db::name("vs_room_pan") - ->where(['room_id'=>$room_id,'gift_bag_id'=>$gift_bag_id]) - ->update([ - 'remaining_number' => db::raw('(SELECT quantity FROM fa_vs_gift_bag_detail WHERE id = fa_vs_room_pan.gift_bag_detail_id)'), - 'periods' => $periods - ]); - //防止并发,上把如果件数小于0,则加上 - foreach ($room_pan as $pan) { - if($pan['remaining_number']<0){ - db::name("vs_room_pan")->where('id', $pan['id'])->setInc('remaining_number', $pan['remaining_number']); - } - } - //补充上把礼物有剩余 - if(!empty($remaining_available_gifts)){ - foreach ($remaining_available_gifts as $gift) { - db::name("vs_room_pan")->where('id', $gift['id'])->setInc('remaining_number',$gift['remaining_number']); - } - } - - // 更新总期数 - db::name("vs_gift_bag") - ->where('id', $gift_bag_id) - ->setInc('periods'); - } - - /* - * 获取可用礼物用于预计算 - */ - private function get_available_gifts_for_precompute($gift_bag_id, $room_id, $total_draw_times) { - $where = [ - 'a.gift_bag_id' => $gift_bag_id, - 'a.quantity' => ['>',0], - 'b.remaining_number' => ['>',0], - 'b.room_id' => $room_id, - 'a.weight' => ['<=', $total_draw_times], - ]; - - return db::name("vs_gift_bag_detail") - ->field('a.id,a.quantity,b.remaining_number,a.weight,a.foreign_id') - ->alias('a') - ->join('vs_room_pan b','b.gift_bag_detail_id = a.id','left') - ->where($where) - ->select(); - } - - /* - * 预计算单次抽奖结果 - */ - private function precompute_single_draw($gift_bag_id, $room_id, $available_gifts, $last_periods_remaining = []) { - // 生成缓存键 - $available_cache_key = 'blindbox_available_gifts_' . $gift_bag_id . '_' . $room_id; - $last_remaining_cache_key = 'blindbox_last_remaining_' . $gift_bag_id . '_' . $room_id; - - // 保证有可用礼物 - if (empty($available_gifts)) { - return false; - } - - $last_remaining_all = array_sum(array_column($last_periods_remaining, 'remaining_number')); - if ($last_remaining_all == 0) { - $last_periods_remaining = []; - cache($last_remaining_cache_key, null); - } - - $last_periods_remaining_flag = 0; - if (!empty($last_periods_remaining)) { - $available_gifts = $last_periods_remaining; - $last_periods_remaining_flag = 1; - } - - // 循环尝试直到抽中有效礼物 - $max_attempts = 5; // 最大尝试次数,防止无限循环 - $attempt = 0; - $selected_gift = null; - - while ($attempt < $max_attempts && !$selected_gift) { - // 实现加权随机算法:剩余数量越多,被抽中的概率越大 - $remaining = 0; - foreach ($available_gifts as $gift) { - $remaining += $gift['remaining_number']; - } - - if ($remaining <= 0) { - break; // 如果没有剩余数量,跳出循环 - } - - $rand_value = mt_rand(1, $remaining); - $current_sum = 0; - - foreach ($available_gifts as $gift) { - if ($gift['remaining_number'] <= 0) { - continue; - } - $current_sum += $gift['remaining_number']; - if ($rand_value <= $current_sum) { - $selected_gift = $gift; - break; - } - } - $attempt++; - } - if (!$selected_gift) { - return false; - } - - // 获取开出礼物的信息 - $gift = db::name("vs_gift") - ->where(['gid' => $selected_gift['foreign_id']]) - ->find(); - - if (!$gift) { - return false; - } - - // 操作缓存,减去缓存中对应数据数量 - foreach ($available_gifts as &$available_gifts_gift) { - if ($selected_gift['id'] == $available_gifts_gift['id']) { - $available_gifts_gift['remaining_number'] -= 1; - } - } - if ($available_gifts_gift['remaining_number'] == 0) { - unset($available_gifts_gift); - } - - if ($last_periods_remaining_flag == 1) { - // 操作上一轮奖池 - cache($last_remaining_cache_key, $available_gifts); - } else { - cache($available_cache_key, $available_gifts); - } - - return [ - 'gift_bag_detail' => $selected_gift, - 'gift' => $gift - ]; - } - - /* - * 寻乐会抽奖----------------------------------------------------------------------------------------- - */ - /* - * 巡乐会抽奖 - */ - /* - * 巡乐会抽奖(优化版) - */ - public function xlh_draw_gift($user_id, $num, $room_id) - { - $gift_bag_id = 13; - - // 1. 获取并缓存盲盒配置 - $cacheKey = "xlh_config_{$gift_bag_id}"; - $ext = $this->getCachedXlhConfig(); - - // 2. 检查用户金币和房间状态 - $bag_gift_price = $ext['xlh_box_price'] * $num; - $user_waller = db::name('user_wallet')->where(['user_id' => $user_id])->find(); - if (!$user_waller || $user_waller['coin'] < $bag_gift_price) { - return ['code' => 0, 'msg' => '用户金币不足', 'data' => null]; - } - - $room = db::name('vs_room') - ->field('id,room_name,is_open_blind_box_turntable,xlh_periods') - ->where(['id' => $room_id]) - ->find(); - if (!$room || $room['is_open_blind_box_turntable'] != 1) { - return ['code' => 0, 'msg' => '该房间未开启盲盒转盘', 'data' => null]; - } - - // 3. 检查巡乐会状态 - $pan_xlh = db::name('vs_room_pan_xlh') - ->where(['room_id' => $room_id, 'periods' => $room['xlh_periods']]) - ->find(); - if (empty($pan_xlh)) { - return ['code' => 0, 'msg' => '未开始', 'data' => null]; - } - if ($pan_xlh['end_time'] <= time()) { - return ['code' => 0, 'msg' => '本轮已结束', 'data' => null]; - } - if ($pan_xlh['send_time'] != 0) { - return ['code' => 0, 'msg' => '本轮已结束,礼物已发放', 'data' => null]; - } - - // 4. 预加载必要数据 - $room_pan_data = db::name("vs_room_pan")->alias('a') - ->join('vs_gift_bag_detail b', 'a.gift_bag_detail_id = b.id') - ->where(['a.room_id' => $room_id, 'a.gift_bag_id' => $gift_bag_id]) - ->field('a.id, a.gift_bag_detail_id, a.remaining_number, a.periods,b.weight,b.foreign_id') - ->select(); - - if (empty($room_pan_data)) { - return ['code' => 0, 'msg' => '当前房间未配置抽奖礼物,请联系管理员', 'data' => []]; - } - - // 计算总数量和剩余数量 - $total_quantity = db::name("vs_gift_bag_detail") - ->where(['gift_bag_id' => $gift_bag_id]) - ->sum('quantity'); - - $total_remaining = array_sum(array_column($room_pan_data, 'remaining_number')); - $total_draw_times = $total_quantity - $total_remaining; - - if ($total_draw_times < 0) $total_draw_times = 0; - - // 5. 获取可用礼物(提前过滤无效礼物) - $available_gifts = []; - foreach ($room_pan_data as $pan) { - if ($pan['remaining_number'] > 0 && $pan['weight'] <= $total_draw_times) { - $available_gifts[] = $pan; - } - } - if (empty($available_gifts)) { - // 移除已无剩余数量的礼物 - $remaining_available_gifts = array_filter($room_pan_data, function($gift) { - return $gift['remaining_number'] > 0; - }); - $this->reset_gift_pool($room_id, $gift_bag_id, $room_pan_data[0]['periods'] + 1,$remaining_available_gifts); -// return ['code' => 0, 'msg' => '当前盲盒无可用礼物', 'data' => []]; - } - - // 6. 预计算抽奖结果 - $drawn_gifts = []; // 用于统计抽中结果 - $main_prize_updates = []; // 用于记录主奖品更新 - $is_zhong_jiang = 0; - $pan_xlh_num = $pan_xlh['num']; - - // 批量处理配置 - $batch_size = min(10, $num); // 每批次处理10次 - $total_processed = 0; - $all_results = []; // 存储所有抽奖结果 - - while ($total_processed < $num) { - $current_batch = min($batch_size, $num - $total_processed); - db::startTrans(); - try { - // 批量扣除金币(只在第一次事务中处理) - if ($total_processed == 0) { - $wallet_update = model('GiveGift')->change_user_cion_or_earnings_log( - $user_id, - $bag_gift_price, - $room_id, - 1, - 10, - $ext['gift_bag_name'].'抽奖消耗' - ); - if (!$wallet_update) { - throw new \Exception('扣除用户金币失败'); - } - - $user_level = model('Level')->user_level_data_update( - $user_id, - $bag_gift_price, - 1, - $room_id - ); - if (!$user_level) { - throw new \Exception('用户等级更新失败'); - } - } - - // 处理当前批次的抽奖 - $inventory_updates = []; // 用于记录库存变化 - $gift_details_map = []; // 礼物详情映射 - $remaining_available_gifts = $available_gifts; - for ($i = 0; $i < $current_batch; $i++) { - // 从可用礼物中选择 - $selected_gift = $this->selectGiftFromAvailable($available_gifts); - if (!$selected_gift) { - throw new \Exception('预计算抽奖失败'); - } - - // 记录库存变化 - $inventory_updates[$selected_gift['id']] = - ($inventory_updates[$selected_gift['id']] ?? 0) + 1; - - // 记录抽中结果 - $gift_id = $selected_gift['foreign_id']; - $drawn_gifts[$gift_id] = ($drawn_gifts[$gift_id] ?? 0) + 1; - - // 处理主奖品 - if ($gift_id == $ext['locking_condition']['selected_gift_id']) { - $pan_xlh_num++; - $main_prize_updates[] = [ - 'num' => $pan_xlh_num, - 'user_id' => $user_id, - 'gift_id' => $gift_id - ]; - - // 计算延长时间 - $add_end_time = $this->calculateEndTime($pan_xlh_num, $ext, $room_id); - $end_time = time() + $add_end_time; - - // 记录主奖品更新 - $main_prize_updates[count($main_prize_updates) - 1]['end_time'] = $end_time; - } - - // 记录完整结果 - $all_results[] = [ - 'gift_id' => $gift_id, - 'gift_detail_id' => $selected_gift['id'], - 'gift_bag_detail' => $selected_gift - ]; - - // 更新可用礼物缓存 - foreach ($available_gifts as &$gift) { - if ($gift['id'] == $selected_gift['id']) { - $gift['remaining_number']--; - break; - } - } - unset($gift); - - // 移除已无剩余数量的礼物 - $available_gifts = array_filter($available_gifts, function($gift) { - return $gift['remaining_number'] > 0; - }); - - // 检查是否需要重置奖池 - $total_remaining = array_sum(array_column($available_gifts, 'remaining_number')); - if ($total_remaining <= 0 && $total_processed + $i + 1 < $num) { - $this->reset_gift_pool($room_id, $gift_bag_id, $room_pan_data[0]['periods'] + 1,$remaining_available_gifts); - $available_gifts = $this->reloadGiftPool($room_id, $gift_bag_id); - if (empty($available_gifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - } - } - - // 批量更新库存 - foreach ($inventory_updates as $detail_id => $count) { - db::name("vs_room_pan") - ->where(['id' => $detail_id]) - ->setDec('remaining_number', $count); - } - - // 处理主奖品更新 - if (!empty($main_prize_updates)) { - $last_update = end($main_prize_updates); - db::name('vs_room_pan_xlh')->where('id', $pan_xlh['id'])->update([ - 'user_id' => $last_update['user_id'], - 'pay_price' => $ext['xlh_box_price'], - 'locking_gift_id' => $last_update['gift_id'], - 'num' => $last_update['num'], - 'end_time' => $last_update['end_time'], - 'updatetime' => time() - ]); - - db::name('vs_room_pan_xlh_log')->insert([ - 'xlh_id' => $pan_xlh['id'], - 'user_id' => $last_update['user_id'], - 'num' => $last_update['num'], - 'locking_end_time' => $last_update['end_time'], - 'createtime' => time() - ]); - $is_zhong_jiang = 1; - } - - db::commit(); - $total_processed += $current_batch; - } catch (\Exception $e) { - db::rollback(); - Log::record('巡乐会抽奖失败: ' . $e->getMessage(),"infos"); - return ['code' => 0, 'msg' => "抽奖中,请稍等...", 'data' => null]; - } - } - - // 7. 批量处理结果记录 - try { - db::startTrans(); - if(count($drawn_gifts) > $num){ - Log::record('巡乐会抽奖失败-数量超限: ' . count($drawn_gifts),"infos"); - return ['code' => 0, 'msg' => "抽奖中,请稍等...", 'data' => null]; - } - // 批量插入礼包发放记录 - $gift_records = []; - $periods = $room_pan_data[0]['periods'] ?? 0; - foreach ($drawn_gifts as $gift_id => $count) { - $gift_records[] = [ - 'user_id' => $user_id, - 'parent_id' => $pan_xlh['id'], - 'gift_bag_id' => $gift_bag_id, - 'gift_id' => $gift_id, - 'periods' => $periods, - 'room_id' => $room_id, - 'num' => $count, - 'gift_price' => $this->getGiftPrice($gift_id), - 'bag_price' => $ext['xlh_box_price'], - 'createtime' => time() - ]; - } - - if (!empty($gift_records)) { - db::name("vs_gift_bag_receive_log")->insertAll($gift_records); - } - - // 批量处理用户礼物包 - foreach ($drawn_gifts as $gift_id => $count) { - $res = model('UserGiftPack')->change_user_gift_pack( - $user_id, - $gift_id, - $count, - model('UserGiftPack')::XLH_DRAW_GIFT_GET, - "巡乐会抽奖所得" - ); - if ($res['code'] != 1) { - throw new \Exception($res['msg']); - } - } - - // 添加活动记录 - Db::name('vs_activities_receive')->insert([ - 'user_id' => $user_id, - 'activities_id' => 6, - 'room_id' => $room_id, - 'createtime' => time(), - 'updatetime' => time() - ]); - - db::commit(); - } catch (\Exception $e) { - db::rollback(); - return ['code' => 0, 'msg' => $e->getMessage(), 'data' => null]; - } - - // 8. 处理推送消息 - if ($is_zhong_jiang == 1) { - $this->handlePrizeNotification($user_id,$gift_id, $room_id, $pan_xlh_num, $end_time, $room['room_name'],$ext['locking_condition']['locking_gift_id']); - } - - // 9. 构建返回结果 - return $this->buildResult($drawn_gifts); - } - - /** - * 巡乐会抽奖-从可用礼物中选择一个 - */ - private function selectGiftFromAvailable(array &$available_gifts) - { - $remaining = array_sum(array_column($available_gifts, 'remaining_number')); - if ($remaining <= 0) { - return null; - } - // 循环尝试直到抽中有效礼物 - $max_attempts = 5; // 最大尝试次数,防止无限循环 - $attempt = 0; - $selected_gift = null; - while ($attempt < $max_attempts && !$selected_gift) { - $rand_value = mt_rand(1, $remaining); - $current_sum = 0; - - foreach ($available_gifts as $gift) { - if ($gift['remaining_number'] <= 0) { - continue; - } - $current_sum += $gift['remaining_number']; - if ($rand_value <= $current_sum) { - $selected_gift = $gift; - break; - } - } - $attempt++; - } - - return $selected_gift; - } - - /** - * 巡乐会抽奖-计算结束时间 - */ - private function calculateEndTime($pan_xlh_num, $ext, $room_id) - { - $cache_key = 'selected_gift_id_' . $room_id . $ext['locking_condition']['selected_gift_id']; - - if ($pan_xlh_num <= 1) { - $add_end_time = $ext['locking_time']['tow_no_locking_time'] * 60; - Cache::set($cache_key, $add_end_time, $add_end_time); - return $add_end_time; - } - - if (Cache::get($cache_key)) { - $erci_xlh_num = Cache::get($cache_key); - $add_end_time = $erci_xlh_num - $ext['locking_time']['next_time'] * 60; - Cache::set($cache_key, $add_end_time, $add_end_time); - } else { - $add_end_time = ($ext['locking_time']['tow_no_locking_time'] - $ext['locking_time']['next_time']) * 60; - } - - if ($add_end_time <= 30) { - Cache::set($cache_key, 30, 30); - return 30; - } - - return $add_end_time; - } - - /** - * 巡乐会抽奖-重新加载奖池 - */ - private function reloadGiftPool($room_id, $gift_bag_id) - { - return db::name("vs_room_pan")->alias('a') - ->join('vs_gift_bag_detail b', 'a.gift_bag_detail_id = b.id') - ->where(['a.room_id' => $room_id, 'a.gift_bag_id' => $gift_bag_id]) - ->field('a.id, a.gift_bag_detail_id, a.remaining_number, a.periods,b.weight,b.foreign_id') - ->select(); - } - - /** - * 巡乐会抽奖-获取礼物价格 - */ - private function getGiftPrice($gift_id) - { - static $gift_prices = []; - - if (!isset($gift_prices[$gift_id])) { - $gift_prices[$gift_id] = db::name("vs_gift") - ->where(['gid' => $gift_id]) - ->value('gift_price'); - } - - return $gift_prices[$gift_id]; - } - - /** - * 巡乐会抽奖-处理中奖通知 - */ - private function handlePrizeNotification($user_id,$gift_id, $room_id, $pan_xlh_num, $end_time, $room_name,$locking_gift_id) - { - $FromUserInfo = db::name('user')->field('nickname,avatar')->where(['id' => $user_id])->find(); - $gift_info = db::name('vs_gift')->field('gift_name')->where(['gid' => $gift_id])->find(); - $locking_gift = db::name('vs_gift')->field('gift_name')->where(['gid' => $locking_gift_id])->find(); - //锁定礼物 - - $text = [ - 'gift_num' => $pan_xlh_num, - 'FromUserInfo' => $FromUserInfo, - 'end_time' => $end_time, - 'text' => $FromUserInfo['nickname'] . ' 在 ' . $room_name . ' 房间巡乐会中 获得' . $gift_info['gift_name'] . '礼物 x 1' - ]; - - model('Chat')->sendMsg(1057, $room_id, $text); - - //推送礼物横幅 - $text = $FromUserInfo['nickname'] . ' 在 ' . $room_name.' 房间巡乐会锁定礼物'.$locking_gift['gift_name'].' x ' .$pan_xlh_num ; - $push = new Push(0, $room_id); - $text_list_new = [ - 'text' => $text, - 'room_id' => $room_id, - 'from_type' => 1 - ]; - $push->xunlehui($text_list_new); - } - - /** - * 巡乐会抽奖-构建返回结果 - */ - private function buildResult($drawn_gifts) - { - $result_list = []; - $gift_info_cache = []; - - foreach ($drawn_gifts as $gift_id => $count) { - if (!isset($gift_info_cache[$gift_id])) { - $gift_info = db::name('vs_gift') - ->where(['gid' => $gift_id]) - ->find(); - $gift_info_cache[$gift_id] = $gift_info; - } else { - $gift_info = $gift_info_cache[$gift_id]; - } - - $result_list[] = [ - 'gift_id' => $gift_id, - 'gift_name' => $gift_info['gift_name'], - 'base_image' => $gift_info['base_image'], - 'gift_price' => $gift_info['gift_price'], - 'count' => $count, - ]; - } - - // 按价格降序排序 - usort($result_list, function($a, $b) { - return $b['gift_price'] <=> $a['gift_price']; - }); - - return ['code' => 1, 'msg' => '成功', 'data' => $result_list]; - } - //错误日志记录------------------------------------------------------- - /** - * 记录抽奖错误日志到Redis - */ - private function recordDrawErrorToRedis($expectedCount, $actualCount, $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults) - { - // 检查Redis连接是否可用 - if (!$this->redis) { - Log::record('Redis连接不可用,无法记录错误日志', 'error'); - return; - } - - try { - $errorData = [ - 'timestamp' => time(), - 'expected_count' => $expectedCount, - 'actual_count' => $actualCount, - 'room_id' => $room_id, - 'user_id' => $user_id, - 'gift_bag_id' => $gift_bag_id, - 'num' => $num, - 'gift_user_ids' => $gift_user_ids, - 'precomputed_results_count' => count($precomputedResults), - 'precomputed_results' => $precomputedResults, - 'error_type' => 'draw_count_mismatch' - ]; - - // 使用正确的Redis方法存储数据 - $key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'); - $this->redis->setex($key, 86400 * 7, json_encode($errorData)); - } catch (\Exception $e) { - Log::record('Redis操作失败: ' . $e->getMessage(), 'error'); - } - } -} diff --git a/application/api/model/BlindBoxTurntableGiftDrawWorld.php b/application/api/model/BlindBoxTurntableGiftDrawWorld.php deleted file mode 100644 index 1e5c6116..00000000 --- a/application/api/model/BlindBoxTurntableGiftDrawWorld.php +++ /dev/null @@ -1,1547 +0,0 @@ -redis = new Redis(); - // 连接到Redis服务器 - $this->redis->connect(config('redis.host'), config('redis.port')); // 根据实际配置调整主机和端口 - // 选择数据库1 - $this->redis->select(1); - } 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) - { - // 最大重试次数 - try { - // 1. 验证参数并提前处理错误 - $validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids); - if ($validationResult !== true) { - return $validationResult; - } - // 2. 预加载必要数据 - $loadResult = $this->loadDrawData($gift_bag_id, $user_id, $room_id, $num, $gift_user_ids); - if ($loadResult['code'] !== 1) { - return $loadResult; - } - // 添加以下检查以防止 null 错误 - if (!isset($loadResult['data']) || !is_array($loadResult['data'])) { - return ['code' => 0, 'msg' => '数据加载失败', 'data' => null]; - } - ['bag_data' => $bag_data, 'room' => $room, 'xlh_ext' => $xlh_ext] = $loadResult['data']; - - // 3. 预计算抽奖结果 - $precomputeResult = $this->precomputeDrawResults( - $bag_data, - $gift_user_ids, - $num - ); - if ($precomputeResult['code'] !== 1) { - return $precomputeResult; - } - $precomputedResults = $precomputeResult['data']['results']; - $availableGiftss = $precomputeResult['data']['availableGifts']; - $currentXlhPeriodsNum = $precomputeResult['data']['current_xlh_periods_num']; - $addcurrentXlhPeriodsNum = $precomputeResult['data']['addcurrentXlhPeriodsNum']; - $expectedCount = count(explode(',', $gift_user_ids)) * $num; - if (count($precomputedResults) != $expectedCount) { - // 记录错误到Redis - $this->recordDrawErrorToRedis($expectedCount, count($precomputedResults), $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults); - return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null]; - } - foreach ($precomputedResults as $result) { - $key = $result['gift_user_id'] . '_' . $result['gift_bag_detail']['foreign_id']; - if (!isset($giftUserCountsJianCha[$key])) { - $giftUserCountsJianCha[$key] = [ - 'gift_user_id' => $result['gift_user_id'], - 'gift_id' => $result['gift_bag_detail']['foreign_id'], - 'count' => 0, - 'gift_price' => $result['gift']['gift_price'], - 'quantity' => $result['gift_bag_detail']['quantity'], - ]; - } - $giftUserCountsJianCha[$key]['count']++; - if($giftUserCountsJianCha[$key]['count'] > $result['gift_bag_detail']['quantity']){ - // 记录错误到Redis - // 使用正确的Redis方法存储数据 - $key = 'blind_box_draw_world_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, - 'giftUserCountsJianCha' => $giftUserCountsJianCha, - ]; - $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, - $precomputedResults, - $availableGiftss, - $gift_user_ids, - $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, - $xlh_ext, - $currentXlhPeriodsNum, - $addcurrentXlhPeriodsNum, - $room - ); - - // 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]; - } - } - /** - * 验证抽奖参数 - */ - private function validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids) - { - // 提前验证收礼人 - $toarray = explode(',', $gift_user_ids); - if (in_array($user_id, $toarray)) { - 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]; - } - return true; - } - /** - * 预加载必要数据 - */ - private function loadDrawData($gift_bag_id, $user_id, $room_id,$num,$gift_user_ids) - { - // 1. 合并查询盲盒配置和礼物信息 - $bag_data = db::name("vs_gift_bag") - ->alias('bag') - ->join('vs_gift gift', 'gift.gid = JSON_UNQUOTE(JSON_EXTRACT(bag.ext, "$.gift_id"))', 'LEFT') - ->field('bag.id,bag.name,bag.ext,gift.gid as gift_id,gift.gift_price') - ->where('bag.id', $gift_bag_id) - ->find(); - if (!$bag_data || !is_array($bag_data) || !isset($bag_data['gift_price'])) { - return ['code' => 0, 'msg' => '盲盒配置不存在或盲盒礼物不存在', 'data' => null]; - } - // 2. 获取房间信息 - $room = db::name('vs_room') - ->field('id,room_name,xlh_periods,xlh_periods_num,is_open_blind_box_turntable') - ->where(['id' => $room_id]) - ->find(); - - if (!$room) { - return ['code' => 0, 'msg' => '房间不存在', 'data' => null]; - } - - // 3. 检查用户金币 - $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'] < $bag_data['gift_price'] * $num * count(explode(',', $gift_user_ids))) { - return ['code' => 0, 'msg' => '用户金币不足', 'data' => null]; - } - // 4. 获取巡乐会配置(使用缓存) - $xlh_ext = $this->getCachedXlhConfig(); - - return [ - 'code' => 1, - 'msg' => '数据加载成功', - 'data' => [ - 'bag_data' => $bag_data, - 'room' => $room, - 'xlh_ext' => $xlh_ext, -// 'user_waller' => $user_waller - ] - ]; - } - /** - * 预计算抽奖结果 - */ - private function precomputeDrawResults($bag_data, $gift_user_ids, $num) - { - $toarray = explode(',', $gift_user_ids); - // 1. 计算奖池信息 - $poolInfo = $this->calculatePoolInfo($bag_data['id']); - if ($poolInfo['code'] !== 1) { - return $poolInfo; - } - $totalRemaining = $poolInfo['data']['total_remaining'];//奖池剩余数量 - $periods = $poolInfo['data']['periods']; //奖池期数 - $totalDrawTimes = $poolInfo['data']['total_draw_times']; //总抽奖次数 - - // 2. 获取可用礼物 - $availableGifts = $this->getAvailableGifts($bag_data['id'], $totalDrawTimes); - if (empty($availableGifts)) { - $availableGifts = $this->resetPoolAndReload($bag_data['id']); //重置奖池并重新加载 - if (empty($availableGifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - $totalDrawTimes = 0;//总抽奖次数重置 - } - // 在处理奖池重置逻辑之前添加检查 - if (!is_array($availableGifts)) { - throw new \Exception('可用礼物数据格式错误'); - } - // 3. 预加载礼物信息(减少后续查询) - $giftInfoMap = $this->preloadGiftInfo($availableGifts); - - // 4. 处理奖池重置逻辑 - 优化版本 - $needGiftNum = count($toarray) * $num; //总需要的礼物数 - $remaining_available_gifts = []; - $remainingGiftCount = array_sum(array_column($availableGifts, 'remaining_number')); - - if ($remainingGiftCount < $needGiftNum) { - // 如果当前奖池礼物不足以满足需求 - // 保存当前剩余礼物作为上期剩余 - $remaining_available_gifts = $availableGifts; - - // 重置奖池 - $availableGifts = $this->resetPoolAndReload($bag_data['id']); - if (empty($availableGifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - $totalDrawTimes = 0; // 总抽奖次数重置 - } - $current_xlh_periods_num = $this->getCachedXlhPeriodsNum("get"); - // 5. 使用Alias Method预计算抽奖结果(O(1)复杂度) - $precomputedResults = $this->precomputeResultsWithAliasMethod( - $toarray, - $num, - $availableGifts, - $giftInfoMap, - $totalDrawTimes, - $periods, - $current_xlh_periods_num, - $remaining_available_gifts - ); - if (empty($precomputedResults['precomputedResults'])) { - throw new \Exception('预计算抽奖结果失败'); - } - - return [ - 'code' => 1, - 'msg' => '预计算成功', - 'data' => [ - 'results' => $precomputedResults['precomputedResults'], - 'current_xlh_periods_num' => $current_xlh_periods_num, - 'availableGifts' => $precomputedResults['precomputedResultss'], - 'addcurrentXlhPeriodsNum' => $precomputedResults['addcurrentXlhPeriodsNum'], - ] - ]; - } - - /** - * 使用Alias Method预计算抽奖结果(O(1)复杂度) - */ - private function precomputeResultsWithAliasMethod( - $toarray, - $num, - $availableGifts, - $giftInfoMap, - &$totalDrawTimes, - &$periods, - &$currentXlhPeriodsNum, - $remaining_available_gifts - ) { - if (!is_array($toarray) || !is_array($availableGifts) || !is_array($giftInfoMap)) { - throw new \Exception('预计算参数格式错误'); - } - $precomputedResults = []; - $precomputedResultss = []; - $addcurrentXlhPeriodsNum = 0; - - // 计算上期剩余礼物总数 - $remainingGiftCount = array_sum(array_column($remaining_available_gifts, 'remaining_number')); - $needGiftNum = count($toarray) * $num; - $newGiftsNeeded = max(0, $needGiftNum - $remainingGiftCount); // 计算还需要多少礼物从新奖池中抽取 - - // 先从上期剩余礼物中分配 - if (!empty($remaining_available_gifts)) { - $aliasTableForRemaining = $this->buildAliasTable($remaining_available_gifts); - - foreach ($toarray as $giftUserId) { - // 为每个用户先分配上期剩余礼物 - $userRemainingAllocation = floor($remainingGiftCount / count($toarray)); - if (count($toarray) > 0) { // 防止除零错误 - $extraGifts = $remainingGiftCount % count($toarray); - if (array_search($giftUserId, $toarray) < $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' => $totalDrawTimes, - 'periods' => $periods, - ]; -// $precomputedResultss[] = [ -// 'gift_user_id' => $giftUserId, -// 'gift_bag_detail' => $selectedGift, -// 'gift' => $gift, -// 'draw_times' => $totalDrawTimes, -// 'periods' => $periods, -// ]; -// $totalDrawTimes++; - $currentXlhPeriodsNum++; - $addcurrentXlhPeriodsNum++; - - // 更新Alias表 - $this->updateAliasTable($aliasTableForRemaining, $selectedGift['id']); - } - } - } - $totalDrawTimes = 0; - } - - // 再从新奖池中分配剩余所需礼物 - if ($newGiftsNeeded > 0 && !empty($availableGifts)) { - $aliasTableForNew = $this->buildAliasTable($availableGifts); - - foreach ($toarray as $giftUserId) { - // 计算每个用户需要从新奖池获得的礼物数量 - $userNewAllocation = floor($newGiftsNeeded / count($toarray)); - if (count($toarray) > 0) { - $extraGifts = $newGiftsNeeded % count($toarray); - if (array_search($giftUserId, $toarray) < $extraGifts) { - $userNewAllocation++; - } - } - - for ($i = 0; $i < $userNewAllocation; $i++) { - $selectedGift = $this->selectGiftWithAliasMethod($aliasTableForNew); - if ($selectedGift) { - $giftInfoMap = $this->preloadGiftInfo($availableGifts); - $gift = $giftInfoMap[$selectedGift['foreign_id']]; - if($gift) - $precomputedResults[] = [ - 'gift_user_id' => $giftUserId, - 'gift_bag_detail' => $selectedGift, - 'gift' => $gift, - 'draw_times' => $totalDrawTimes, - 'periods' => $periods, - ]; - $precomputedResultss[] = [ - 'gift_user_id' => $giftUserId, - 'gift_bag_detail' => $selectedGift, - 'gift' => $gift, - 'draw_times' => $totalDrawTimes, - 'periods' => $periods, - ]; - $totalDrawTimes++; - $currentXlhPeriodsNum++; - $addcurrentXlhPeriodsNum++; - - // 更新Alias表 - $this->updateAliasTable($aliasTableForNew, $selectedGift['id']); - } - } - } - } - - return ['precomputedResults' => $precomputedResults, 'precomputedResultss' => $precomputedResultss, 'addcurrentXlhPeriodsNum' => $addcurrentXlhPeriodsNum]; - } - - /** - * 构建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 - ]; - } - - /** - * 使用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表(模拟库存减少) - */ - 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 executeDrawTransaction($bag_data, $user_id, $room_id, $num, $precomputedResults,$availableGiftss,$gift_user_ids,$heart_id,$auction_id) - { - $gift_user_num = count(explode(',', $gift_user_ids)); //人数 - $bagGiftPrice = $bag_data['gift_price'] * $num * $gift_user_num; - - // 增加重试机制 - try { - db::startTrans(); - // 按照固定顺序处理事务步骤 - // 1. 扣除用户金币(优先处理) - $this->deductUserCoins($user_id, $bagGiftPrice, $room_id); - - // 2. 创建抽奖记录 - $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(); - throw new \Exception($e->getMessage()); - } - } - - /** - * 批量更新礼物库存 - */ - private function batchUpdateGiftInventory($precomputedResults, $room_id) - { - // 按礼物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) { - Log::record('巡乐会更新礼物剩余数量: ' . $room_id."【数据】".var_export($precomputedResults, true),"info"); - throw new \Exception('更新礼物剩余数量失败'); - } - } - } - - /** - * 批量插入礼包发放记录 - */ - private function batchInsertGiftBagReceiveLog($user_id, $boxTurntableLog, $bag_data, $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['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 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 handlePostDrawOperations( - $precomputedResults, - $boxTurntableLog, - $room_id, - $xlh_ext, - $currentXlhPeriodsNum, - $addcurrentXlhPeriodsNum, - $room - ) { - // 1. 批量插入盲盒转盘结果记录 - $this->batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog); - - // 3. 处理巡乐会相关操作 - if (!empty($xlh_ext) && $xlh_ext['inlet_bag_id'] == $precomputedResults[0]['gift_bag_detail']['gift_bag_id']) { - $this->handleXlhOperations($room_id, $xlh_ext, $currentXlhPeriodsNum,$addcurrentXlhPeriodsNum,$room); - } - } - - /** - * 批量插入盲盒转盘结果记录 - */ - private function batchInsertBlindBoxResults($precomputedResults, $boxTurntableLog) - { - // 统计每个用户每个礼物的数量 - $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']++; - } - // 批量插入 - $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 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 handleXlhOperations($room_id, $xlh_ext, $currentXlhPeriodsNum,$addcurrentXlhPeriodsNum,$room) - { - $xlhIsPiaoPing = 0; - $xlhPeriodsNum = $this->getCachedXlhPeriodsNum("get"); - if($xlhPeriodsNum < $xlh_ext['open_condition']['waiting_start_num'] && $currentXlhPeriodsNum >= $xlh_ext['open_condition']['waiting_start_num']){ - $xlhIsPiaoPing = 1; - } - if($xlhPeriodsNum < $xlh_ext['open_condition']['start_num'] && $currentXlhPeriodsNum >= $xlh_ext['open_condition']['start_num']){ - $xlhIsPiaoPing = 2; - } - // 更新房间巡乐会次数 - $this->getCachedXlhPeriodsNum("set",$addcurrentXlhPeriodsNum); - - // 处理飘屏 - if ($xlhIsPiaoPing == 1 || $xlhIsPiaoPing == 2) { - $this->handleXlhPiaoPing($room_id, $xlh_ext, $xlhIsPiaoPing); - } - - $this->updateAndPushXlhStatus($room_id, $xlh_ext, $currentXlhPeriodsNum); - } - 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, $currentXlhPeriodsNum){ - $xlh['waiting_start_num'] = $xlh_ext['open_condition']['waiting_start_num'];//等待开奖次数 - $xlh['start_num'] = $xlh_ext['open_condition']['start_num'];//开始开奖次数 - // 当前抽奖次数 - $xlh['current_num'] = $currentXlhPeriodsNum; - $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); - - // 聊天室推送系统消息 - // 推送 -// $text = [ -// 'xlh_data' => $xlh, -// 'text' => "" -// ]; -// model('Chat')->sendMsg(1056,$room_id,$text); - } - /** - * 统计礼物数量 - */ - private function countGifts($precomputedResults) - { - $giftCounts = []; - foreach ($precomputedResults as $result) { - $giftId = $result['gift_bag_detail']['foreign_id']; - if (!isset($giftCounts[$giftId])) { - $giftCounts[$giftId] = [ - 'gift_id' => $giftId, - 'count' => 0, - 'gift_price' => $result['gift']['gift_price'] - ]; - } - $giftCounts[$giftId]['count']++; - } - return $giftCounts; - } - - /** - * 构建抽奖结果 - */ - 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 - ] - ]; - } - /** - * 计算奖池信息 - */ - private function calculatePoolInfo($gift_bag_id) - { - $roomPanInfo = db::name("vs_gift_bag_detail") - ->where(['gift_bag_id' => $gift_bag_id]) - ->field('SUM(quantity) as total_quantity, SUM(remaining_number) as total_remaining') - ->find(); - $periods = db::name("vs_gift_bag") - ->where(['id' => $gift_bag_id]) - ->value('periods'); - $totalQuantity = $roomPanInfo['total_quantity'] ?: 0; - $totalRemaining = $roomPanInfo['total_remaining'] ?: 0; - $periods = $periods ?: 0; - $totalDrawTimes = max(0, $totalQuantity - $totalRemaining); //总抽奖次数 - return [ - 'code' => 1, - 'msg' => '计算成功', - 'data' => [ - 'total_quantity' => $totalQuantity, - 'total_remaining' => $totalRemaining, - 'periods' => $periods, - 'total_draw_times' => $totalDrawTimes - ] - ]; - } - - /** - * 获取可用礼物 - * @param $gift_bag_id - * @param $total_draw_times 总抽奖次数 - */ - private function getAvailableGifts($gift_bag_id, $total_draw_times = 0) - { - $where = [ - 'gift_bag_id' => $gift_bag_id, - 'quantity' => ['>', 0], - 'remaining_number' => ['>', 0], - 'weight' => ['<=', $total_draw_times], - ]; - return db::name("vs_gift_bag_detail") - ->field('id,quantity,remaining_number,weight,foreign_id,gift_bag_id') - ->where($where) - ->select(); - } - - /** - * 预加载礼物信息 - */ - private function preloadGiftInfo($available_gifts) - { - $giftIds = array_unique(array_column($available_gifts, 'foreign_id')); - if (empty($giftIds)) return []; - $gifts = db::name('vs_gift') - ->where('gid', 'in', $giftIds) - ->select(); - return array_column($gifts, null, 'gid'); - } - - /** - * 重置奖池并重新加载 - */ - private function resetPoolAndReload($gift_bag_id) - { - $this->reset_gift_pool($gift_bag_id); - return $this->getAvailableGifts($gift_bag_id); - } - - /** - * 获取缓存的巡乐会配置 - */ - private function getCachedXlhConfig() { - $cacheKey = "xlh_config_13"; - $ext = Cache::get($cacheKey); - if (!$ext) { - $bag_data = db::name("vs_gift_bag") - ->field('id,name,ext,periods') - ->where('id', 13) - ->find(); - if (!$bag_data) { - return []; - } - $ext = json_decode($bag_data['ext'], true); - $ext['gift_bag_name'] = $bag_data['name']; - Cache::set($cacheKey, $ext, 3600); // 缓存1小时 - } - return $ext; - } - /** - * 获取缓存的巡乐会开启次数 - */ - private function getCachedXlhPeriodsNum($type="get",$num=1) { - $cacheKey = "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 = "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; - } - - /** - * 重置奖池 - * @param int $room_id 房间ID - * @param int $gift_bag_id 礼物包ID - */ - private function reset_gift_pool($gift_bag_id,$remaining_available_gifts=[]) { - $bag_detail = db::name("vs_gift_bag_detail")->where('gift_bag_id',$gift_bag_id)->select(); - db::name("vs_gift_bag")->where('id',$gift_bag_id)->setInc('periods'); //更新期数 - db::name("vs_gift_bag_detail")->where('gift_bag_id',$gift_bag_id)->update(['remaining_number'=>db::raw('quantity')]);//重置奖池 - //防止并发,上把如果件数小于0,则加上 - foreach ($bag_detail as $pan) { - if($pan['remaining_number']<0){ - db::name("vs_gift_bag_detail")->where('id', $pan['id'])->setInc('remaining_number', $pan['remaining_number']); - } - } - //补充上把礼物有剩余 - if(!empty($remaining_available_gifts)){ - foreach ($remaining_available_gifts as $gift) { - db::name("vs_gift_bag_detail")->where('id', $gift['id'])->setInc('remaining_number',$gift['remaining_number']); - } - } - } - - /* - * 巡乐会抽奖----------------------------------------------------------------------------------------- - */ - /* - * 巡乐会抽奖(优化版) - */ - public function xlh_draw_gift($user_id, $num, $room_id) - { - $gift_bag_id = 13; - // 1. 获取并缓存盲盒配置 - $ext = $this->getCachedXlhConfig(); - // 2. 检查用户金币和房间状态 - $bag_gift_price = $ext['xlh_box_price'] * $num; - // 3. 检查巡乐会状态 - $pan_xlh = db::name('vs_room_pan_xlh') - ->where(['send_time' => 0]) - ->order('id', 'desc') - ->find(); - if (empty($pan_xlh)) { - return ['code' => 0, 'msg' => '本轮未开始', 'data' => null]; - } - if ($pan_xlh['end_time'] <= time()) { - return ['code' => 0, 'msg' => '本轮已结束', 'data' => null]; - } - // 4. 预加载必要数据 - $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]) - ->where(['remaining_number' => ['>', 0]]) - ->select(); - - // 计算总数量和剩余数量 - $total_quantity = db::name("vs_gift_bag_detail") - ->where(['gift_bag_id' => $gift_bag_id]) - ->sum('quantity'); - - $total_remaining = array_sum(array_column($gift_bag_detail, 'remaining_number')); - $total_draw_times = $total_quantity - $total_remaining; // 已抽奖次数 - - if ($total_draw_times < 0) $total_draw_times = 0; - - // 5. 获取可用礼物(提前过滤无效礼物) - $available_gifts = []; - foreach ($gift_bag_detail as $pan) { - if ($pan['remaining_number'] > 0 && $pan['weight'] <= $total_draw_times) { - $available_gifts[] = $pan; - } - } - if (empty($available_gifts)) { - // 移除已无剩余数量的礼物 - $remaining_available_gifts = array_filter($gift_bag_detail, function($gift) { - return $gift['remaining_number'] > 0; - }); - $this->reset_gift_pool($gift_bag_id,$remaining_available_gifts); - $available_gifts = $this->getAvailableGifts($gift_bag_id); - } - - // 6. 预计算抽奖结果 - $drawn_gifts = []; // 用于统计抽中结果 - $main_prize_updates = []; // 用于记录主奖品更新 - $is_zhong_jiang = 0; - $pan_xlh_num = $pan_xlh['num']; - - // 批量处理配置 - $batch_size = min(10, $num); // 每批次处理10次 - $total_processed = 0; - $all_results = []; // 存储所有抽奖结果 - try { - db::startTrans(); - while ($total_processed < $num) { - $current_batch = min($batch_size, $num - $total_processed); // 当前批次处理数量 - // 批量扣除金币(只在第一次事务中处理) - if ($total_processed == 0) { - $user_waller = db::name('user_wallet')->where(['user_id' => $user_id])->find(); - if (!$user_waller || $user_waller['coin'] < $bag_gift_price) { - return ['code' => 0, 'msg' => '用户金币不足', 'data' => null]; - } - $wallet_update = model('GiveGift')->change_user_cion_or_earnings_log( - $user_id, - $bag_gift_price, - $room_id, - 1, - 10, - $ext['gift_bag_name'].'抽奖消耗' - ); - if (!$wallet_update) { - throw new \Exception('扣除用户金币失败'); - } - - $user_level = model('Level')->user_level_data_update( - $user_id, - $bag_gift_price, - 1, - $room_id - ); - if (!$user_level) { - throw new \Exception('用户等级更新失败'); - } - } - - // 处理当前批次的抽奖 - $inventory_updates = []; // 用于记录库存变化 - $remaining_available_gifts = $available_gifts; - for ($i = 0; $i < $current_batch; $i++) { - // 从可用礼物中选择 - $selected_gift = $this->selectGiftFromAvailable($available_gifts); - if (!$selected_gift) { - $gift_bag_detail = $this->resetPoolAndReload($gift_bag_id); - $selected_gift = $this->selectGiftFromAvailable($gift_bag_detail); - if(!$selected_gift){ - throw new \Exception('预计算抽奖失败,重置后无可用礼物'); - } - } - // 记录库存变化 - $inventory_updates[$selected_gift['id']] = - ($inventory_updates[$selected_gift['id']] ?? 0) + 1; - - // 记录抽中结果 - $gift_id = $selected_gift['foreign_id']; - $drawn_gifts[$gift_id] = ($drawn_gifts[$gift_id] ?? 0) + 1; - - // 处理主奖品 - if ($gift_id == $ext['locking_condition']['selected_gift_id']) { - $pan_xlh_num++; - $main_prize_updates[] = [ - 'num' => $pan_xlh_num, - 'user_id' => $user_id, - 'gift_id' => $gift_id - ]; - - // 计算延长时间 - $add_end_time = $this->calculateEndTime($pan_xlh_num, $ext, $room_id); - $end_time = time() + $add_end_time; - - // 记录主奖品更新 - $main_prize_updates[count($main_prize_updates) - 1]['end_time'] = $end_time; - } - - // 记录完整结果 - $all_results[] = [ - 'gift_id' => $gift_id, - 'gift_detail_id' => $selected_gift['id'], - 'gift_bag_detail' => $selected_gift - ]; - - // 更新可用礼物缓存 - foreach ($available_gifts as &$gift) { - if ($gift['id'] == $selected_gift['id']) { - $gift['remaining_number']--; - break; - } - } - unset($gift); - - // 移除已无剩余数量的礼物 - $available_gifts = array_filter($available_gifts, function($gift) { - return $gift['remaining_number'] > 0; - }); - - // 检查是否需要重置奖池 - $total_remaining = array_sum(array_column($available_gifts, 'remaining_number')); - if ($total_remaining <= 0 && $total_processed + $i + 1 < $num) {// 剩余数量小于等于0且当前处理数量小于总数量 - $available_gifts = $this->resetPoolAndReload($gift_bag_id); - if (empty($available_gifts)) { - throw new \Exception('重置奖池后仍无可用礼物'); - } - } - } - - // 批量更新库存 - ksort($inventory_updates); // 按ID排序 - foreach ($inventory_updates as $detail_id => $count) { - db::name("vs_gift_bag_detail")->where('id',$detail_id)->setDec('remaining_number', $count); - } - - // 处理主奖品更新 - if (!empty($main_prize_updates)) { - $last_update = end($main_prize_updates); - db::name('vs_room_pan_xlh')->where('id', $pan_xlh['id'])->update([ - 'user_id' => $last_update['user_id'], - 'room_id' => $room_id, - 'pay_price' => $ext['xlh_box_price'], - 'locking_gift_id' => $last_update['gift_id'], - 'num' => $last_update['num'], - 'end_time' => $last_update['end_time'], - 'updatetime' => time() - ]); - - db::name('vs_room_pan_xlh_log')->insert([ - 'xlh_id' => $pan_xlh['id'], - 'user_id' => $last_update['user_id'], - 'room_id' => $room_id, - 'num' => $last_update['num'], - 'locking_end_time' => $last_update['end_time'], - 'createtime' => time() - ]); - $is_zhong_jiang = 1; - unset($main_prize_updates); - }else{ - $is_zhong_jiang = 0; - } - $total_processed += $current_batch; - } - // 7. 批量处理结果记录 - if(count($drawn_gifts) > $num){ - $key = 'xlh_draw_gift_errors_' . date('Y-m-d-H-i-s'); - $errorData = [ - 'user_id' => $user_id, - 'gift_bag_id' => $gift_bag_id, - 'room_id' => $room_id, - 'num' => $num, - 'drawn_gifts_num' => count($drawn_gifts) - ]; - $this->redis->setex($key, 86400 * 7, "巡乐会抽奖失败-数量超限". ' ' .json_encode($errorData)); - return ['code' => 0, 'msg' => "抽奖中,请稍等...", 'data' => null]; - } - // 批量插入礼包发放记录 - $gift_records = []; - $periods = $this->getCachedXlhPeriods('get') ?? 0; - foreach ($drawn_gifts as $gift_id => $count) { - $gift_records[] = [ - 'user_id' => $user_id, - 'parent_id' => $pan_xlh['id'], - 'gift_bag_id' => $gift_bag_id, - 'gift_id' => $gift_id, - 'periods' => $periods, - 'room_id' => $room_id, - 'num' => $count, - 'gift_price' => $this->getGiftPrice($gift_id), - 'bag_price' => $ext['xlh_box_price'], - 'createtime' => time() - ]; - } - - if (!empty($gift_records)) { - db::name("vs_gift_bag_receive_pan_log")->insertAll($gift_records); - } - - // 批量处理用户礼物包 - foreach ($drawn_gifts as $gift_id => $count) { - $res = model('UserGiftPack')->change_user_gift_pack( - $user_id, - $gift_id, - $count, - model('UserGiftPack')::XLH_DRAW_GIFT_GET, - "巡乐会抽奖所得" - ); - if ($res['code'] != 1) { - throw new \Exception($res['msg']); - } - } - - // 添加活动记录 - Db::name('vs_activities_receive')->insert([ - 'user_id' => $user_id, - 'activities_id' => 6, - 'room_id' => $room_id, - 'createtime' => time(), - 'updatetime' => time() - ]); - db::commit(); - } catch (\Exception $e) { - db::rollback(); - $key = 'xlh_draw_gift_errors_' . date('Y-m-d-H-i-s'); - $errorData = [ - 'user_id' => $user_id, - 'gift_bag_id' => $gift_bag_id, - 'room_id' => $room_id, - ]; - $this->redis->setex($key, 86400 * 7, $e->getMessage(). ' ' .json_encode($errorData)); - return ['code' => 0, 'msg' => "抽奖中,请稍等...", 'data' => null]; - } - // 8. 处理推送消息 - if ($is_zhong_jiang == 1) { - $this->handlePrizeNotification($user_id,$gift_id, $room_id, $pan_xlh_num, $end_time,$ext['locking_condition']['locking_gift_id']); - } - - // 9. 构建返回结果 - return $this->buildResult($drawn_gifts); - } - - /** - * 巡乐会抽奖-从可用礼物中选择一个 - */ - private function selectGiftFromAvailable(array &$available_gifts) - { - $remaining = array_sum(array_column($available_gifts, 'remaining_number')); - if ($remaining <= 0) { - return null; - } - // 循环尝试直到抽中有效礼物 - $max_attempts = 5; // 最大尝试次数,防止无限循环 - $attempt = 0; - $selected_gift = null; - while ($attempt < $max_attempts && !$selected_gift) { - $rand_value = mt_rand(1, $remaining); - $current_sum = 0; - - foreach ($available_gifts as $gift) { - if ($gift['remaining_number'] <= 0) { - continue; - } - $current_sum += $gift['remaining_number']; - if ($rand_value <= $current_sum) { - $selected_gift = $gift; - break; - } - } - $attempt++; - } - - return $selected_gift; - } - - /** - * 巡乐会抽奖-计算结束时间 - */ - private function calculateEndTime($pan_xlh_num, $ext, $room_id) - { - $cache_key = 'selected_gift_id_' . $ext['locking_condition']['selected_gift_id']; - - if ($pan_xlh_num <= 1) { - $add_end_time = $ext['locking_time']['tow_no_locking_time'] * 60; - Cache::set($cache_key, $add_end_time, $add_end_time); - return $add_end_time; - } - - if (Cache::get($cache_key)) { - $erci_xlh_num = Cache::get($cache_key); - $add_end_time = $erci_xlh_num - $ext['locking_time']['next_time'] * 60; - Cache::set($cache_key, $add_end_time, $add_end_time); - } else { - $add_end_time = ($ext['locking_time']['tow_no_locking_time'] - $ext['locking_time']['next_time']) * 60; - } - - if ($add_end_time <= 30) { - Cache::set($cache_key, 30, 30); - return 30; - } - - return $add_end_time; - } - - /** - * 巡乐会抽奖-重新加载奖池 - */ - private function reloadGiftPool($room_id, $gift_bag_id) - { - return 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(); - } - - /** - * 巡乐会抽奖-获取礼物价格 - */ - private function getGiftPrice($gift_id) - { - static $gift_prices = []; - - if (!isset($gift_prices[$gift_id])) { - $gift_prices[$gift_id] = db::name("vs_gift") - ->where(['gid' => $gift_id]) - ->value('gift_price'); - } - - return $gift_prices[$gift_id]; - } - - /** - * 巡乐会抽奖-处理中奖通知 - */ - private function handlePrizeNotification($user_id,$gift_id, $room_id, $pan_xlh_num, $end_time,$locking_gift_id) - { - $FromUserInfo = db::name('user')->field('nickname,avatar')->where(['id' => $user_id])->find(); - $gift_info = db::name('vs_gift')->field('gift_name')->where(['gid' => $gift_id])->find(); - $locking_gift = db::name('vs_gift')->field('gift_name')->where(['gid' => $locking_gift_id])->find(); - $room_data = db::name('vs_room')->field('room_name,user_id')->where(['id' => $room_id])->find(); - $room_user = db::name('user')->where('id',$room_data['user_id'])->field('nickname,avatar')->find(); - //锁定礼物 - -// $text = [ -// 'gift_num' => $pan_xlh_num, -// 'FromUserInfo' => $FromUserInfo, -// 'end_time' => $end_time, -// 'text' => $FromUserInfo['nickname'] . ' 在 ' . ' 巡乐会活动中 获得' . $gift_info['gift_name'] . '礼物 x 1' -// ]; -// -// model('Chat')->sendMsg(1057, $room_id, $text); - - //推送礼物横幅 - $text = $FromUserInfo['nickname'] .' 巡乐会活动中锁定礼物'.$locking_gift['gift_name'].' x ' .$pan_xlh_num ; - $push = new Push(0, $room_id); - $text_list_new = [ - 'text' => $text, - 'room_id' => $room_id, - 'from_type' => 103, - 'gift_num' => $pan_xlh_num, - 'FromUserInfo' => $FromUserInfo, - 'end_time' => $end_time, - 'room_user' => $room_user - ]; - $push->xunlehui($text_list_new); - } - - /** - * 巡乐会抽奖-构建返回结果 - */ - private function buildResult($drawn_gifts) - { - $result_list = []; - $gift_info_cache = []; - - foreach ($drawn_gifts as $gift_id => $count) { - if (!isset($gift_info_cache[$gift_id])) { - $gift_info = db::name('vs_gift') - ->where(['gid' => $gift_id]) - ->find(); - $gift_info_cache[$gift_id] = $gift_info; - } else { - $gift_info = $gift_info_cache[$gift_id]; - } - - $result_list[] = [ - 'gift_id' => $gift_id, - 'gift_name' => $gift_info['gift_name'], - 'base_image' => $gift_info['base_image'], - 'gift_price' => $gift_info['gift_price'], - 'count' => $count, - ]; - } - - // 按价格降序排序 - usort($result_list, function($a, $b) { - return $b['gift_price'] <=> $a['gift_price']; - }); - - return ['code' => 1, 'msg' => '成功', 'data' => $result_list]; - } - //错误日志记录------------------------------------------------------- - /** - * 记录抽奖错误日志到Redis - */ - private function recordDrawErrorToRedis($expectedCount, $actualCount, $room_id, $user_id, $gift_bag_id, $num, $gift_user_ids, $precomputedResults) - { - // 检查Redis连接是否可用 - if (!$this->redis) { - Log::record('Redis连接不可用,无法记录错误日志', 'error'); - return; - } - - try { - $errorData = [ - 'timestamp' => time(), - 'expected_count' => $expectedCount, - 'actual_count' => $actualCount, - 'room_id' => $room_id, - 'user_id' => $user_id, - 'gift_bag_id' => $gift_bag_id, - 'num' => $num, - 'gift_user_ids' => $gift_user_ids, - 'precomputed_results_count' => count($precomputedResults), - 'precomputed_results' => $precomputedResults, - 'error_type' => 'draw_count_mismatch' - ]; - - // 使用正确的Redis方法存储数据 - $key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s'); - $this->redis->setex($key, 86400 * 7, json_encode($errorData)); - } catch (\Exception $e) { - Log::record('Redis操作失败: ' . $e->getMessage(), 'error'); - } - } - -} diff --git a/application/cron/controller/RoomPan.php b/application/cron/controller/RoomPan.php index b4c2410f..c5b9755f 100644 --- a/application/cron/controller/RoomPan.php +++ b/application/cron/controller/RoomPan.php @@ -80,7 +80,12 @@ class RoomPan * 盲盒转盘礼物推送补发 */ public function blind_box_turntable_gift_send(){ - $blind_box_turntable = db('vs_blind_box_turntable_log')->where(['is_sued'=>0,'createtime'=>['>=',time()-60*30]])->limit(1000)->select(); + $blind_box_turntable = db('vs_blind_box_turntable_log')->where([ + 'is_sued'=>0, + 'gift_bag_id'=>10, + 'createtime'=>['>=',time()-60*30], + 'createtime'=>['<',time()-3] + ])->limit(1000)->select(); if(empty($blind_box_turntable)){ echo "没有需要发放的礼物 \n"; } @@ -132,7 +137,12 @@ class RoomPan 'base_image' => $draw_gift['base_image'] ]; } - + //再次验证防止并发 + $is_sued = db('vs_blind_box_turntable_log')->where('id',$v['id'])->value('is_sued'); + if($is_sued == 1){ + echo $v['id']." 已经发放过了 \n"; + continue; + } // 为每个用户单独推送消息 foreach($userGiftMap as $userId => $userData) { $userInfo = $userData['userInfo'];