Files
midi-php/application/api/model/BlindBoxTurntableGiftDraw.php

1469 lines
51 KiB
PHP
Raw Normal View History

<?php
namespace app\api\model;
use app\common\controller\Push;
use think\Cache;
2025-09-19 17:38:56 +08:00
use think\Log;
use think\Model;
use think\Db;
use think\Session;
/*
* 盲盒转盘优化后方法
*
*/
class BlindBoxTurntableGiftDraw extends Model
{
/**
* 重构后的抽奖方法 - 优化响应速度
*/
public function draw_gift($gift_bag_id, $user_id, $gift_user_ids, $num = 1, $room_id = 0, $heart_id = 0)
{
// 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);
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'];
// 4. 执行抽奖事务(核心操作)
$transactionResult = $this->executeDrawTransaction(
$bag_data,
$user_id,
$room_id,
$num,
$precomputedResults,
$availableGiftss
);
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);
}
/**
* 验证抽奖参数
*/
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)
{
// 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];
}
// 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)) {
return ['code' => 0, 'msg' => '当前盲盒无可用礼物', 'data' => null];
}
// 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)) {
return ['code' => 0, 'msg' => '重置奖池后仍无可用礼物', 'data' => null];
}
$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'])) {
return ['code' => 0, 'msg' => '预计算抽奖结果失败', 'data' => null];
}
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
) {
$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;
// echo "好好玩";
}else{
$numm = $num+$remaining_num;
}
// echo $numm."---";
for ($i = 0; $i < $numm; $i++) {
// echo "好好呀";
// 使用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)
{
$bagGiftPrice = $bag_data['gift_price'] * $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);
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) {
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, $boxTurntableLog, $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, $boxTurntableLog, $room_id,$user_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) {
$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
];
$res = model('Room')->room_gift(
$user_id,
$userGift['gift_user_id'],
$userGift['gift_id'],
$userGift['count'],
1,
$room_id,
0,
0,
$giveGiftExt
);
if (isset($res) && $res['code'] != 1) {
Log::record('发送礼物失败: ' . $res['msg'].$userGift['gift_user_id'],"info");
return ['code' => 0, 'msg' => $res['msg'], 'data' => null];
// \think\facade\Log::error('发送礼物失败: ' . $res['msg']);
}
}
}
/**
* 处理巡乐会相关操作
*/
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;
// 状态
if($xlh['current_num'] >= $xlh_ext['open_condition']['start_num']){
$xlh['status'] = 1;//状态 1:巡乐会开始 2:即将开始开始 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=[]) {
// 重置奖池中所有礼物数量
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
]);
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)) {
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();
return ['code' => 0, 'msg' => $e->getMessage(), 'data' => null];
}
}
// 7. 批量处理结果记录
try {
db::startTrans();
// 批量插入礼包发放记录
$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']);
}
// 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;
}
$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) {
return $gift;
}
}
return null;
}
/**
* 巡乐会抽奖-计算结束时间
*/
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)
{
$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();
$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);
}
/**
* 巡乐会抽奖-构建返回结果
*/
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];
}
}