Files
dafuweng/app/service/DrawService.php
2026-03-02 13:44:38 +08:00

158 lines
5.1 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\service;
use addons\webman\model\DrawRecord;
use addons\webman\model\Game;
use addons\webman\model\Prize;
use Exception;
use support\Db;
use support\Log;
class DrawService
{
/**
* 执行抽奖
* @param $player
* @param int $gameId
* @param int $departmentId
* @param string $ip 用户IP
* @return array 抽奖结果
*/
public function execute( $player, int $gameId, int $departmentId, string $ip): array
{
try {
DB::beginTransaction();
// 1. 查询有效奖品(加行锁防止并发问题)
$prizes = Prize::query()->where('game_id', $gameId)
->where('department_id', $departmentId)
->where('status', 1)
->where('total_remaining', '>', 0)
->where('daily_remaining', '>', 0)
->lockForUpdate()
->get()
->toArray();
if (empty($prizes)) {
DB::rollBack();
return [
'success' => false,
'message' => '当前游戏不可用'
];
}
$game = Game::query()->select('id', 'game_type', 'consume')->where('id', $gameId)->first()->toArray();
// 2. 计算中奖结果
$result = $this->calculateWinning($player,$prizes, $game['consume']);
$prizeId = $result['prize_id'];
$prizeName = $result['prize_name'];
$prizeType = $result['prize_type'];
$game['department_id'] = $departmentId;
// 3. 记录抽奖信息
$this->createRecord($player->id, $result, $game, $ip);
if ($result['prize_type'] == 3) {
$message = '很遗憾,未中奖';
} else {
$message = '恭喜获得' . $prizeName;
}
DB::commit();
return [
'prize_id' => $prizeId,
'prize_name' => $prizeName,
'prize_type' => $prizeType,
'consume' => $game['consume'],
'message' => $message
];
} catch (Exception $e) {
DB::rollBack();
Log::error("抽奖失败:{$e->getMessage()}", [
'user_id' => $player->id,
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => '抽奖过程异常,请稍后再试'
];
}
}
/**
* 计算中奖结果
* @param array $prizes 有效奖品列表
* @return array 中奖信息
* @throws Exception
*/
private function calculateWinning($player, array $prizes, $consume): array
{
// 计算总概率权重
$totalProb = array_sum(array_column($prizes, 'probability'));
if ($totalProb <= 0) {
return ['prize_id' => null, 'type' => Prize::PRIZE_TYPE_LOSE, 'prize_name' => '未中奖', 'prize_pic' => null];
}
$player->wallet->decrement('money', $consume);
// 生成随机数0到总权重之间
$random = mt_rand(1, $totalProb * 10000) / 10000; // 提高精度
$currentSum = 0;
foreach ($prizes as $prize) {
$currentSum += $prize['probability'];
if ($random <= $currentSum) {
// 检查并扣减库存
$this->deductStock($prize['id']);
return [
'prize_id' => $prize['id'],
'prize_type' => $prize['type'],
'prize_name' => $prize['name'],
'prize_pic' => $prize['pic']
];
}
}
return ['prize_id' => null, 'type' => Prize::PRIZE_TYPE_LOSE, 'prize_name' => '未中奖', 'prize_pic' => null];
}
/**
* 扣减库存
* @param int $prizeId 奖品ID
* @throws Exception
*/
private function deductStock(int $prizeId): void
{
$prize = Prize::query()->findOrFail($prizeId);
// 再次检查库存(防止并发超卖)
if ($prize->total_remaining <= 0 || $prize->daily_remaining <= 0) {
throw new \Exception("奖品库存不足");
}
$prize->decrement('total_remaining');
$prize->decrement('daily_remaining');
$prize->save();
}
/**
* 创建抽奖记录
* @param int $userId 用户ID
* @param array|null $prize 奖品
* @param string $ip IP地址
*/
private function createRecord(int $userId, ?array $prize, array $game, string $ip): void
{
DrawRecord::query()->create([
'uid' => $userId,
'prize_id' => $prize['prize_id'],
'prize_type' => $prize['prize_type'],
'prize_name' => $prize['prize_name'],
'prize_pic' => $prize['prize_pic'],
'game_id' => $game['id'],
'consume' => $game['consume'],
'game_type' => $game['game_type'],
'department_id' => $game['department_id'],
'draw_time' => date('Y-m-d H:i:s'),
'ip' => $ip
]);
}
}