盲盒转盘 优化 提交
This commit is contained in:
@@ -37,83 +37,100 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
*/
|
||||
public function draw_gift($gift_bag_id, $user_id, $gift_user_ids, $num = 1, $room_id = 0, $heart_id = 0,$auction_id = 0)
|
||||
{
|
||||
// 收礼人
|
||||
$gift_user_ids = explode(',', $gift_user_ids);
|
||||
$total_num = $num * count($gift_user_ids); //总数量
|
||||
$bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息
|
||||
$total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格
|
||||
$periods = $bag_data['periods']; //期数
|
||||
//1. 验证参数并提前处理错误
|
||||
$validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price);
|
||||
if ($validationResult !== true) {
|
||||
return $validationResult;
|
||||
}
|
||||
//2.预计算抽奖结果
|
||||
$precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods );
|
||||
$precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集
|
||||
$availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新
|
||||
try {
|
||||
// 收礼人
|
||||
$gift_user_ids = explode(',', $gift_user_ids);
|
||||
$total_num = $num * count($gift_user_ids); //总数量
|
||||
$bag_data = $this->getCachedGiftBag($gift_bag_id); //获取转盘信息
|
||||
$total_price = $bag_data['gift_price'] * $total_num; //礼包支付总价格
|
||||
$periods = $bag_data['periods']; //期数
|
||||
//1. 验证参数并提前处理错误
|
||||
$validationResult = $this->validateDrawParameters($gift_bag_id, $user_id, $gift_user_ids,$total_price);
|
||||
if ($validationResult !== true) {
|
||||
return $validationResult;
|
||||
}
|
||||
//2.预计算抽奖结果
|
||||
$precomputeResult = $this->precomputeDrawResults($gift_bag_id, $total_num, $gift_user_ids,$periods );
|
||||
$precomputedResults = $precomputeResult['precomputedResults']; //预计算结果集
|
||||
$availableGiftss = $precomputeResult['precomputedResultss']; //可用礼物/需更新的礼物/需更新
|
||||
|
||||
if (count($precomputedResults) != $total_num) {
|
||||
// 记录错误到Redis
|
||||
// 使用正确的Redis方法存储数据
|
||||
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
|
||||
$errorData = [
|
||||
'total_num' => $total_num,
|
||||
'actual_num' => count($precomputedResults),
|
||||
'room_id' => $room_id,
|
||||
'user_id' => $user_id,
|
||||
'gift_bag_id' => $gift_bag_id,
|
||||
'num' => $num,
|
||||
'gift_user_ids' => $gift_user_ids,
|
||||
'precomputedResults' => $precomputedResults,
|
||||
];
|
||||
$this->redis->setex($key, 86400 * 7, "超出数量".json_encode($errorData));
|
||||
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
|
||||
}
|
||||
$giftCounts = $this->countGifts($precomputedResults);
|
||||
foreach ($giftCounts as $giftId => $count) {
|
||||
if($count['count'] > $count['quantity']){
|
||||
if (count($precomputedResults) != $total_num) {
|
||||
// 记录错误到Redis
|
||||
// 使用正确的Redis方法存储数据
|
||||
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
|
||||
$errorData = [
|
||||
'count' => $count['count'],
|
||||
'quantity' => $count['quantity'],
|
||||
'gift_bag_id' => $gift_bag_id,
|
||||
'total_num' => $total_num,
|
||||
'actual_num' => count($precomputedResults),
|
||||
'room_id' => $room_id,
|
||||
'user_id' => $user_id,
|
||||
'gift_user_ids' => $gift_user_ids,
|
||||
'gift_bag_id' => $gift_bag_id,
|
||||
'num' => $num,
|
||||
'giftUserCountsJianCha' => $count,
|
||||
'gift_user_ids' => $gift_user_ids,
|
||||
'precomputedResults' => $precomputedResults,
|
||||
];
|
||||
$this->redis->setex($key, 86400 * 7, "礼物数量超出限制 ".json_encode($errorData));
|
||||
$this->redis->setex($key, 86400 * 7, "超出数量".json_encode($errorData));
|
||||
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
|
||||
}
|
||||
}
|
||||
$giftCounts = $this->countGifts($precomputedResults);
|
||||
foreach ($giftCounts as $giftId => $count) {
|
||||
if($count['count'] > $count['quantity']){
|
||||
$key = 'blind_box_draw_world_errors_' . date('Y-m-d-H-i-s');
|
||||
$errorData = [
|
||||
'count' => $count['count'],
|
||||
'quantity' => $count['quantity'],
|
||||
'gift_bag_id' => $gift_bag_id,
|
||||
'user_id' => $user_id,
|
||||
'gift_user_ids' => $gift_user_ids,
|
||||
'num' => $num,
|
||||
'giftUserCountsJianCha' => $count,
|
||||
];
|
||||
$this->redis->setex($key, 86400 * 7, "礼物数量超出限制 ".json_encode($errorData));
|
||||
return ['code' => 0, 'msg' => '网络加载失败,请重试!', 'data' => null];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 执行抽奖事务(核心操作)
|
||||
$transactionResult = $this->executeDrawTransaction(
|
||||
$bag_data,
|
||||
$user_id,
|
||||
$room_id,
|
||||
$num,
|
||||
$total_price,
|
||||
$precomputedResults,
|
||||
$availableGiftss,
|
||||
$heart_id,
|
||||
$auction_id
|
||||
);
|
||||
if ($transactionResult['code'] !== 1) {
|
||||
return $transactionResult;
|
||||
}
|
||||
$boxTurntableLog = $transactionResult['data']['log_id'];
|
||||
$giftCounts = $transactionResult['data']['gift_counts'];
|
||||
// 4. 执行抽奖事务(核心操作)
|
||||
$transactionResult = $this->executeDrawTransaction(
|
||||
$bag_data,
|
||||
$user_id,
|
||||
$room_id,
|
||||
$num,
|
||||
$total_price,
|
||||
$precomputedResults,
|
||||
$availableGiftss,
|
||||
$heart_id,
|
||||
$auction_id
|
||||
);
|
||||
if ($transactionResult['code'] !== 1) {
|
||||
return $transactionResult;
|
||||
}
|
||||
$boxTurntableLog = $transactionResult['data']['log_id'];
|
||||
$giftCounts = $transactionResult['data']['gift_counts'];
|
||||
|
||||
// 5. 处理后续操作(非事务性操作)
|
||||
$this->handlePostDrawOperations(
|
||||
$precomputedResults,
|
||||
$boxTurntableLog,
|
||||
$room_id
|
||||
);
|
||||
// 6. 构建并返回结果
|
||||
return $this->buildDrawResult($boxTurntableLog, $giftCounts);
|
||||
// 5. 处理后续操作(非事务性操作)
|
||||
$this->handlePostDrawOperations(
|
||||
$precomputedResults,
|
||||
$boxTurntableLog,
|
||||
$room_id
|
||||
);
|
||||
// 6. 构建并返回结果
|
||||
return $this->buildDrawResult($boxTurntableLog, $giftCounts);
|
||||
} catch (\Exception $e) {
|
||||
$key = 'blind_box_draw_errors_' . date('Y-m-d-H-i-s');
|
||||
$errorData = [
|
||||
'gift_bag_id' => $gift_bag_id,
|
||||
'user_id' => $user_id,
|
||||
'gift_user_ids' => $gift_user_ids,
|
||||
'num' => $num,
|
||||
'room_id' => $room_id,
|
||||
'heart_id' => $heart_id,
|
||||
'auction_id' => $auction_id,
|
||||
];
|
||||
if ($this->redis) {
|
||||
$this->redis->setex($key, 86400 * 7, $e->getMessage() . ' ' . json_encode($errorData));
|
||||
}
|
||||
return ['code' => 0, 'msg' => "网络加载失败,请重试!", 'data' => null];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 预计算抽奖结果
|
||||
@@ -124,7 +141,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
$pan_total_remaining = $this->getCachedPanTotalRemaining($gift_bag_id); //剩余数量
|
||||
//获取可用礼物
|
||||
$availableGifts = $this->getAvailableGifts($gift_bag_id,$pan_total_draw_times+$total_num);
|
||||
$giftInfoMap = cache::get("pan_gift_info_map".$gift_bag_id); //预加载礼物信息
|
||||
$giftInfoMap = $this->getCachedPanGiftInfoMap($gift_bag_id);//预加载礼物信息
|
||||
$remaining_available_gifts = [];
|
||||
if (empty($availableGifts) ||$pan_total_remaining ==0) {
|
||||
//重置奖池
|
||||
@@ -221,6 +238,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
$this->getCachedXlhPeriodsNum("set");
|
||||
// 更新Alias表
|
||||
$this->updateAliasTable($aliasTableForNew, $selectedGift['id']);
|
||||
$this->updateCachedGiftBagDetail($gift_bag_id, $selectedGift['id']); //更新缓存中礼物的数量
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,8 +253,6 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
{
|
||||
// 查找礼物在Alias表中的位置
|
||||
$gifts = &$aliasTable['gifts'];
|
||||
$indexMap = &$aliasTable['index_map'];
|
||||
|
||||
foreach ($gifts as &$gift) {
|
||||
if ($gift['id'] == $giftId) {
|
||||
$gift['remaining_number']--;
|
||||
@@ -317,18 +333,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
$total_remaining = array_sum(array_column($pan_gift_bag_detail, 'remaining_number'));
|
||||
$total_draw_times = max(0, $total_quantity - $total_remaining); //总抽奖次数
|
||||
|
||||
$gift_info_map = [];
|
||||
$gift_ids = array_unique(array_column($pan_gift_bag_detail, 'foreign_id'));
|
||||
if (!empty($gift_ids)) {
|
||||
foreach ($gift_ids as $gift_id) {
|
||||
$gift_info_map[$gift_id] = db::name("vs_gift")
|
||||
->field('gid,gift_name,gift_price,base_image')
|
||||
->where(['gid' => $gift_id])
|
||||
->find();
|
||||
}
|
||||
}
|
||||
Cache::set($cacheKey, $pan_gift_bag_detail, $this->cache_time);
|
||||
Cache::set("pan_gift_info_map".$gift_bag_id, $gift_info_map, $this->cache_time);
|
||||
Cache::set("pan_total_quantity".$gift_bag_id, $total_quantity, $this->cache_time);
|
||||
Cache::set("pan_total_remaining".$gift_bag_id, $total_remaining, $this->cache_time);
|
||||
Cache::set("pan_total_draw_times".$gift_bag_id, $total_draw_times, $this->cache_time);
|
||||
@@ -377,15 +382,26 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
{
|
||||
if (!$aliasTable) return null;
|
||||
|
||||
$n = $aliasTable['n'];
|
||||
$k = mt_rand(0, $n - 1);
|
||||
|
||||
// 随机选择
|
||||
if (mt_rand() / mt_getrandmax() < $aliasTable['prob'][$k]) {
|
||||
return $aliasTable['index_map'][$k];
|
||||
} else {
|
||||
return $aliasTable['index_map'][$aliasTable['alias'][$k]] ?? null;
|
||||
// 添加重试机制,最多尝试5次
|
||||
$maxAttempts = 5;
|
||||
$attempt = 0;
|
||||
while ($attempt < $maxAttempts) {
|
||||
$n = $aliasTable['n'];
|
||||
$k = mt_rand(0, $n - 1);
|
||||
// 随机选择
|
||||
if (mt_rand() / mt_getrandmax() < $aliasTable['prob'][$k]) {
|
||||
$selectedGift = $aliasTable['index_map'][$k];
|
||||
} else {
|
||||
$selectedGift = $aliasTable['index_map'][$aliasTable['alias'][$k]] ?? null;
|
||||
}
|
||||
// 检查选中的礼物是否还有库存
|
||||
if ($selectedGift && $selectedGift['remaining_number'] > 0) {
|
||||
return $selectedGift;
|
||||
}
|
||||
$attempt++;
|
||||
}
|
||||
// 如果重试次数用完仍未抽中有效礼物,返回null
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 构建Alias表(O(n)复杂度,只执行一次)
|
||||
@@ -468,6 +484,21 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
Cache::set($cacheKey, $gift_bag_detail_data, $this->cache_time);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取缓存中的奖池单个礼物信息
|
||||
*/
|
||||
private function getCachedGiftBagDetailItem($gift_bag_id, $detail_id) {
|
||||
$cacheKey = "pan_gift_bag_detail".$gift_bag_id;
|
||||
$gift_bag_detail_item = Cache::get($cacheKey);
|
||||
if($gift_bag_detail_item) {
|
||||
foreach ($gift_bag_detail_item as $item) {
|
||||
if ($item['id'] == $detail_id) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* 获取奖池总库存
|
||||
*/
|
||||
@@ -547,6 +578,31 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
}
|
||||
return $xlh_periods_num;
|
||||
}
|
||||
/**
|
||||
* 获取奖池总库存
|
||||
*/
|
||||
private function getCachedPanGiftInfoMap($gift_bag_id) {
|
||||
$cacheKey = "pan_gift_info_map".$gift_bag_id;
|
||||
$gift_info_map = Cache::get($cacheKey);
|
||||
if(!$gift_info_map) {
|
||||
$gift_bag_detail = Db::name("vs_gift_bag_detail")
|
||||
->field('foreign_id')
|
||||
->where(['gift_bag_id' => $gift_bag_id])
|
||||
->select();
|
||||
$gift_info_map = [];
|
||||
$gift_ids = array_unique(array_column($gift_bag_detail, 'foreign_id'));
|
||||
if (!empty($gift_ids)) {
|
||||
foreach ($gift_ids as $gift_id) {
|
||||
$gift_info_map[$gift_id] = db::name("vs_gift")
|
||||
->field('gid,gift_name,gift_price,base_image')
|
||||
->where(['gid' => $gift_id])
|
||||
->find();
|
||||
}
|
||||
}
|
||||
Cache::set($cacheKey, $gift_info_map, $this->cache_time);
|
||||
}
|
||||
return $gift_info_map;
|
||||
}
|
||||
|
||||
//开始更新操作:
|
||||
/**
|
||||
@@ -572,7 +628,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
throw new \Exception('添加盲盒转盘记录失败');
|
||||
}
|
||||
// 3. 批量更新库存(按ID排序避免死锁)
|
||||
$this->batchUpdateGiftInventory($availableGiftss);
|
||||
$this->batchUpdateGiftInventory($bag_data['gift_bag_id'],$availableGiftss);
|
||||
|
||||
// 4. 批量插入礼包发放记录
|
||||
$this->batchInsertGiftBagReceiveLog($bag_data,$user_id, $boxTurntableLog,$room_id, $precomputedResults);
|
||||
@@ -638,7 +694,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
/**
|
||||
* 批量更新礼物库存
|
||||
*/
|
||||
private function batchUpdateGiftInventory($precomputedResults)
|
||||
private function batchUpdateGiftInventory($gift_bag_id,$precomputedResults)
|
||||
{
|
||||
// 按礼物ID分组统计需要减少的数量
|
||||
$inventoryUpdates = [];
|
||||
@@ -655,21 +711,28 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
$giftBagDetail = Db::name("vs_gift_bag_detail")
|
||||
->where('id', $giftId)
|
||||
->find();
|
||||
$giftBagDetailCached = $this->getCachedGiftBagDetailItem($gift_bag_id, $giftId);
|
||||
if (!$giftBagDetail) {
|
||||
throw new \Exception("礼物详情不存在,ID: " . $giftId);
|
||||
}
|
||||
$upRemainingNumber = $giftBagDetail['remaining_number'] - $count;
|
||||
if($upRemainingNumber!=$giftBagDetailCached['remaining_number']){
|
||||
$this->redis->setex( 'blind_box_draw_errors_' . date('Y-m-d-H-i-s'), 86400 * 7, "有并发:礼物数量不一致,礼物ID: " . $giftId . '数据库数量: '.$upRemainingNumber. '缓存数量: '.$giftBagDetailCached['remaining_number']. ' ' .json_encode($giftBagDetail) . ' ' .json_encode($giftBagDetailCached));
|
||||
$upRemainingNumber = $giftBagDetailCached['remaining_number'];
|
||||
}
|
||||
// 检查库存是否足够
|
||||
if ($giftBagDetail['remaining_number'] < $count) {
|
||||
if ($upRemainingNumber < 0) {
|
||||
throw new \Exception("礼物库存不足,ID: " . $giftId);
|
||||
}
|
||||
$ret = db::name("vs_gift_bag_detail")->where('id',$giftId)
|
||||
->setDec('remaining_number', $count);
|
||||
if($upRemainingNumber = $giftBagDetail['remaining_number']){
|
||||
return;
|
||||
}
|
||||
$ret = db::name("vs_gift_bag_detail")->where('id',$giftId)->update([
|
||||
'remaining_number' => $upRemainingNumber
|
||||
]);
|
||||
if (!$ret) {
|
||||
throw new \Exception('更新礼物剩余数量失败');
|
||||
}
|
||||
|
||||
// 同时更新缓存中的库存信息
|
||||
$this->updateCachedGiftBagDetail($giftBagDetail['gift_bag_id'], $giftId, $count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,6 +1186,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
// 批量插入礼包发放记录
|
||||
$gift_records = [];
|
||||
$periods = $this->getCachedXlhPeriods('get') ?? 0;
|
||||
$giftInfoMap = $this->getCachedPanGiftInfoMap($gift_bag_id);
|
||||
foreach ($drawn_gifts as $gift_id => $count) {
|
||||
$gift_records[] = [
|
||||
'user_id' => $user_id,
|
||||
@@ -1132,7 +1196,7 @@ class BlindBoxTurntableGiftDrawWorldNew extends Model
|
||||
'periods' => $periods,
|
||||
'room_id' => $room_id,
|
||||
'num' => $count,
|
||||
'gift_price' => cache::get("pan_gift_info_map".$gift_bag_id)[$gift_id], //预加载礼物信息,
|
||||
'gift_price' => $giftInfoMap[$gift_id]['gift_price'], //预加载礼物信息,
|
||||
'bag_price' => $ext['xlh_box_price'],
|
||||
'createtime' => time()
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user