259 lines
9.8 KiB
PHP
259 lines
9.8 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\api\controller;
|
||
|
||
use support\Log;
|
||
use support\Request;
|
||
use support\Response;
|
||
use support\think\Db;
|
||
use app\api\logic\GameLogic;
|
||
use app\api\logic\PlayStartLogic;
|
||
use app\api\util\ReturnCode;
|
||
use app\dice\model\config\DiceConfig;
|
||
use app\dice\model\play_record\DicePlayRecord;
|
||
use app\dice\model\player\DicePlayer;
|
||
use app\dice\model\reward\DiceRewardConfig;
|
||
use app\api\controller\BaseController;
|
||
use app\api\util\ApiLang;
|
||
use plugin\saiadmin\exception\ApiException;
|
||
|
||
/**
|
||
* 游戏相关接口(购买抽奖券等)
|
||
*/
|
||
class GameController extends BaseController
|
||
{
|
||
/**
|
||
* 获取游戏配置(按 group 分组)
|
||
* GET /api/game/config
|
||
* header: lang = zh | en
|
||
* 返回 data[group] = [ { name, title, value, create_time, update_time }, ... ]
|
||
* - lang=zh 或默认:title/value = 中文
|
||
* - lang=en:若存在英文配置则返回 title_en/value_en,否则回退到中文
|
||
*/
|
||
public function config(Request $request): Response
|
||
{
|
||
$rows = DiceConfig::select('name', 'group', 'title', 'title_en', 'value', 'value_en', 'create_time', 'update_time')->get();
|
||
$lang = $request->header('lang', 'zh');
|
||
if (!is_string($lang) || $lang === '') {
|
||
$lang = 'zh';
|
||
}
|
||
$langLower = strtolower($lang);
|
||
$isEn = $langLower === 'en' || str_starts_with($langLower, 'en-');
|
||
$data = [];
|
||
foreach ($rows as $row) {
|
||
$group = $row->group ?? '';
|
||
if (!isset($data[$group])) {
|
||
$data[$group] = [];
|
||
}
|
||
$title = $row->title;
|
||
$value = $row->value;
|
||
if ($isEn) {
|
||
$titleEn = $row->title_en ?? '';
|
||
$valueEn = $row->value_en ?? '';
|
||
if ($titleEn !== '') {
|
||
$title = $titleEn;
|
||
}
|
||
if ($valueEn !== '') {
|
||
$value = $valueEn;
|
||
}
|
||
}
|
||
$data[$group][] = [
|
||
'name' => $row->name,
|
||
'title' => $title,
|
||
'value' => $value,
|
||
'create_time' => $row->create_time,
|
||
'update_time' => $row->update_time,
|
||
];
|
||
}
|
||
return $this->success($data);
|
||
}
|
||
|
||
/**
|
||
* 购买抽奖券
|
||
* POST /api/game/buyLotteryTickets
|
||
* header: token(由 TokenMiddleware 注入 request->player_id)
|
||
* body: count = 1 | 5 | 10(1次/100coin, 5次/500coin, 10次/1000coin)
|
||
*/
|
||
public function buyLotteryTickets(Request $request): Response
|
||
{
|
||
$userId = (int) ($request->player_id ?? 0);
|
||
$count = (int) $request->post('count', 0);
|
||
if (!in_array($count, [1, 5, 10], true)) {
|
||
return $this->fail('Invalid lottery ticket purchase', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
|
||
try {
|
||
$logic = new GameLogic();
|
||
$data = $logic->buyLotteryTickets($userId, $count);
|
||
return $this->success($data);
|
||
} catch (ApiException $e) {
|
||
$msg = $e->getMessage();
|
||
if ($msg === '平台币不足') {
|
||
$player = DicePlayer::find($userId);
|
||
$coin = $player ? (float) $player->coin : 0;
|
||
return $this->success(['coin' => $coin], $msg);
|
||
}
|
||
return $this->fail($msg, ReturnCode::BUSINESS_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取彩金池(中奖配置表)
|
||
* GET /api/game/lotteryPool
|
||
* header: token
|
||
* 返回 DiceRewardConfig 列表(彩金池/中奖配置),不包含 tier=BIGWIN
|
||
*/
|
||
public function lotteryPool(Request $request): Response
|
||
{
|
||
$list = DiceRewardConfig::getCachedList();
|
||
$list = array_values(array_filter($list, function ($row) {
|
||
return (string) ($row['tier'] ?? '') !== 'BIGWIN';
|
||
}));
|
||
|
||
$lang = $request->header('lang', 'zh');
|
||
if (!is_string($lang) || $lang === '') {
|
||
$lang = 'zh';
|
||
}
|
||
$langLower = strtolower($lang);
|
||
$isEn = $langLower === 'en' || str_starts_with($langLower, 'en-');
|
||
|
||
if ($isEn) {
|
||
foreach ($list as $index => $row) {
|
||
$uiEn = '';
|
||
if (is_array($row) && array_key_exists('ui_text_en', $row) && $row['ui_text_en'] !== null) {
|
||
$uiEn = (string) $row['ui_text_en'];
|
||
}
|
||
if ($uiEn !== '') {
|
||
$row['ui_text'] = $uiEn;
|
||
}
|
||
$list[$index] = $row;
|
||
}
|
||
}
|
||
|
||
return $this->success($list);
|
||
}
|
||
|
||
/**
|
||
* 开始游戏(抽奖一局)
|
||
* POST /api/game/playStart
|
||
* header: token(由 TokenMiddleware 注入 request->player_id)
|
||
* body: direction 必传,0=无 1=中奖
|
||
*/
|
||
public function playStart(Request $request): Response
|
||
{
|
||
$userId = (int) ($request->player_id ?? 0);
|
||
$direction = $request->post('direction');
|
||
if ($direction !== null) {
|
||
$direction = (int) $direction;
|
||
}
|
||
if (!in_array($direction, [0, 1], true)) {
|
||
return $this->fail('direction must be 0 or 1', ReturnCode::PARAMS_ERROR);
|
||
}
|
||
|
||
$player = DicePlayer::find($userId);
|
||
if (!$player) {
|
||
return $this->fail('User not found', ReturnCode::NOT_FOUND);
|
||
}
|
||
$minEv = DiceRewardConfig::getCachedMinRealEv();
|
||
$minCoin = abs($minEv + 100);
|
||
$coin = (float) $player->coin;
|
||
if ($coin < $minCoin) {
|
||
$msg = ApiLang::translateParams('Balance %s is less than %s, cannot continue', [$coin, $minCoin], $request);
|
||
return $this->success([], $msg);
|
||
}
|
||
|
||
$lockName = 'play_start_' . $userId;
|
||
$lockResult = Db::query('SELECT GET_LOCK(?, 30) as l', [$lockName]);
|
||
if (empty($lockResult) || (int) ($lockResult[0]['l'] ?? 0) !== 1) {
|
||
return $this->fail('too many requests, please try again later', ReturnCode::BUSINESS_ERROR);
|
||
}
|
||
try {
|
||
$logic = new PlayStartLogic();
|
||
$data = $logic->run($userId, (int)$direction);
|
||
|
||
$lang = $request->header('lang', 'zh');
|
||
if (!is_string($lang) || $lang === '') {
|
||
$lang = 'zh';
|
||
}
|
||
$langLower = strtolower($lang);
|
||
$isEn = $langLower === 'en' || str_starts_with($langLower, 'en-');
|
||
|
||
if (is_array($data) && array_key_exists('reward_config_id', $data)) {
|
||
$rewardConfigId = (int) $data['reward_config_id'];
|
||
if ($rewardConfigId > 0) {
|
||
$configRow = DiceRewardConfig::getCachedById($rewardConfigId);
|
||
if ($configRow !== null) {
|
||
$uiText = '';
|
||
$uiTextEn = '';
|
||
if (array_key_exists('ui_text', $configRow) && $configRow['ui_text'] !== null) {
|
||
$uiText = (string) $configRow['ui_text'];
|
||
}
|
||
if (array_key_exists('ui_text_en', $configRow) && $configRow['ui_text_en'] !== null) {
|
||
$uiTextEn = (string) $configRow['ui_text_en'];
|
||
}
|
||
if ($isEn && $uiTextEn !== '') {
|
||
$data['ui_text'] = $uiTextEn;
|
||
} else {
|
||
$data['ui_text'] = $uiText;
|
||
}
|
||
$data['ui_text_en'] = $uiTextEn;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $this->success($data);
|
||
} catch (ApiException $e) {
|
||
return $this->fail($e->getMessage(), ReturnCode::BUSINESS_ERROR);
|
||
} catch (\Throwable $e) {
|
||
// 记录抽奖逻辑抛出的真实异常,便于排查“服务超时,没有原因”
|
||
Log::error('playStart 异常: ' . $e->getMessage(), [
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'trace' => $e->getTraceAsString(),
|
||
'player_id' => $userId,
|
||
'direction' => $direction,
|
||
]);
|
||
$timeoutRecord = null;
|
||
$timeout_message = '';
|
||
$adminId = null;
|
||
try {
|
||
$timeoutPlayer = DicePlayer::find($userId);
|
||
$adminId = ($timeoutPlayer && ($timeoutPlayer->admin_id ?? null)) ? (int) $timeoutPlayer->admin_id : null;
|
||
} catch (\Throwable $_) {
|
||
}
|
||
try {
|
||
$timeoutRecord = DicePlayRecord::create([
|
||
'player_id' => $userId,
|
||
'admin_id' => $adminId,
|
||
'lottery_config_id' => 0,
|
||
'lottery_type' => 0,
|
||
'is_win' => 0,
|
||
'win_coin' => 0,
|
||
'super_win_coin' => 0,
|
||
'reward_win_coin' => 0,
|
||
'use_coins' => 0,
|
||
'direction' => $direction,
|
||
'reward_config_id' => 0,
|
||
'start_index' => 0,
|
||
'target_index' => 0,
|
||
'roll_array' => '[]',
|
||
'roll_number' => 0,
|
||
'status' => PlayStartLogic::RECORD_STATUS_TIMEOUT,
|
||
]);
|
||
} catch (\Exception $inner) {
|
||
$timeout_message = $inner->getMessage();
|
||
Log::error('游玩记录写入超时: ' . $inner->getMessage());
|
||
}
|
||
$payload = $timeoutRecord ? ['record' => $timeoutRecord->toArray()] : [];
|
||
$msg = $timeout_message !== '' ? $timeout_message : $e->getMessage();
|
||
if ($msg === '') {
|
||
$msg = '没有原因';
|
||
}
|
||
return $this->fail('Service timeout: ' . $msg);
|
||
} finally {
|
||
Db::execute('SELECT RELEASE_LOCK(?)', [$lockName]);
|
||
}
|
||
}
|
||
}
|