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

1183 lines
45 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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