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); } /** * 获取底注配置(全部) * GET/any /api/game/anteConfig * header: token(TokenMiddleware 注入) * 返回:dice_ante_config 列表(包含 mult/is_default 等字段) */ public function anteConfig(Request $request): Response { // 用于后续抽奖校验:在接口中实例化 model,后续逻辑可复用相同的数据读取方式。 $anteConfigModel = new DiceAnteConfig(); $rows = $anteConfigModel->order('id', 'asc')->select()->toArray(); return $this->success($rows); } /** * 开始游戏(抽奖一局) * 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; } $ante = $request->post('ante'); if ($ante !== null) { $ante = (int) $ante; } if (!in_array($direction, [0, 1], true)) { return $this->fail('direction must be 0 or 1', ReturnCode::PARAMS_ERROR); } if (!is_int($ante) || $ante <= 0) { return $this->fail('ante must be a positive integer', ReturnCode::PARAMS_ERROR); } $player = DicePlayer::find($userId); if (!$player) { return $this->fail('User not found', ReturnCode::NOT_FOUND); } $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, $ante); $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]); } } }