配置接口lang请求头

This commit is contained in:
2026-03-10 11:42:39 +08:00
parent 9452fd28e2
commit 275f94f96d
8 changed files with 178 additions and 10 deletions

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace app\api\controller;
use app\api\util\ApiLang;
use plugin\saiadmin\basic\OpenController;
use support\Response;
/**
* API 控制器基类:根据请求头 langen=英文zh=中文)对返回 message 做双语适配
*/
class BaseController extends OpenController
{
/**
* 成功返回message 按请求头 langen/zh翻译
*/
public function success(array|string $data = [], string $msg = 'success', int $option = JSON_UNESCAPED_UNICODE): Response
{
if (is_string($data)) {
$msg = $data;
$data = [];
}
$msg = ApiLang::translate((string) $msg);
return parent::success($data, $msg, $option);
}
/**
* 失败返回message 按 lang 翻译
*/
public function fail(string $msg = 'fail', int $code = 400): Response
{
$msg = ApiLang::translate($msg);
return parent::fail($msg, $code);
}
}

View File

@@ -13,13 +13,14 @@ use app\dice\model\config\DiceConfig;
use app\dice\model\play_record\DicePlayRecord; use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player\DicePlayer; use app\dice\model\player\DicePlayer;
use app\dice\model\reward_config\DiceRewardConfig; use app\dice\model\reward_config\DiceRewardConfig;
use plugin\saiadmin\basic\OpenController; use app\api\controller\BaseController;
use app\api\util\ApiLang;
use plugin\saiadmin\exception\ApiException; use plugin\saiadmin\exception\ApiException;
/** /**
* 游戏相关接口(购买抽奖券等) * 游戏相关接口(购买抽奖券等)
*/ */
class GameController extends OpenController class GameController extends BaseController
{ {
/** /**
* 获取游戏配置(按 group 分组) * 获取游戏配置(按 group 分组)
@@ -115,7 +116,8 @@ class GameController extends OpenController
$minCoin = abs($minEv + 100); $minCoin = abs($minEv + 100);
$coin = (float) $player->coin; $coin = (float) $player->coin;
if ($coin < $minCoin) { if ($coin < $minCoin) {
return $this->success([], '当前玩家余额'.$coin.'小于'.$minCoin.'无法继续游戏'); $msg = ApiLang::translateParams('当前玩家余额%s小于%s无法继续游戏', [$coin, $minCoin], $request);
return $this->success([], $msg);
} }
try { try {

View File

@@ -10,13 +10,13 @@ use app\api\logic\UserLogic;
use app\api\util\ReturnCode; use app\api\util\ReturnCode;
use app\dice\model\play_record\DicePlayRecord; use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord; use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use plugin\saiadmin\basic\OpenController; use app\api\controller\BaseController;
/** /**
* API 用户登录等 * API 用户登录等
* 登录接口 /api/user/Login 无需 token其余接口需在请求头携带 tokenbase64(username.-.time)),由 TokenMiddleware 鉴权并注入 request->player_id / request->player * 登录接口 /api/user/Login 无需 token其余接口需在请求头携带 tokenbase64(username.-.time)),由 TokenMiddleware 鉴权并注入 request->player_id / request->player
*/ */
class UserController extends OpenController class UserController extends BaseController
{ {
/** /**
* 登录form-data 参数) * 登录form-data 参数)

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace app\api\controller\v1; namespace app\api\controller\v1;
use app\api\cache\AuthTokenCache; use app\api\cache\AuthTokenCache;
use app\api\controller\BaseController;
use app\api\util\ReturnCode; use app\api\util\ReturnCode;
use plugin\saiadmin\basic\OpenController;
use support\Request; use support\Request;
use support\Response; use support\Response;
use Tinywan\Jwt\JwtToken; use Tinywan\Jwt\JwtToken;
@@ -16,7 +16,7 @@ use Tinywan\Jwt\JwtToken;
* GET 参数signature, secret, time, agent_id * GET 参数signature, secret, time, agent_id
* 签名signature = md5(agent_id.secret.time) * 签名signature = md5(agent_id.secret.time)
*/ */
class AuthTokenController extends OpenController class AuthTokenController extends BaseController
{ {
/** /**
* 获取 auth-token * 获取 auth-token

View File

@@ -10,7 +10,7 @@ use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord; use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use app\dice\model\player_ticket_record\DicePlayerTicketRecord; use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
use support\think\Db; use support\think\Db;
use plugin\saiadmin\basic\OpenController; use app\api\controller\BaseController;
use support\Request; use support\Request;
use support\Response; use support\Response;
@@ -18,7 +18,7 @@ use support\Response;
* 平台 v1 游戏接口 * 平台 v1 游戏接口
* 请求头auth-token * 请求头auth-token
*/ */
class GameController extends OpenController class GameController extends BaseController
{ {
/** /**
* 获取游戏地址 * 获取游戏地址

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* API 英文文案(请求头 lang=en 时使用)
* key 为中文原文value 为英文
*/
return [
'success' => 'Success',
'fail' => 'Fail',
'username、password 不能为空' => 'username and password are required',
'请携带 token' => 'Please provide token',
'token 无效' => 'Invalid or expired token',
'已退出登录' => 'Logged out successfully',
'用户不存在' => 'User not found',
'username 不能为空' => 'username is required',
'密码错误' => 'Wrong password',
'账号已被禁用,无法登录' => 'Account is disabled and cannot log in',
'购买抽奖券错误' => 'Invalid lottery ticket purchase',
'平台币不足' => 'Insufficient balance',
'direction 必须为 0 或 1' => 'direction must be 0 or 1',
'当前玩家余额%s小于%s无法继续游戏' => 'Balance %s is less than %s, cannot continue',
'服务超时,' => 'Service timeout: ',
'没有原因' => 'Unknown reason',
'缺少参数agent_id、secret、time、signature 不能为空' => 'Missing parameters: agent_id, secret, time, signature are required',
'服务端未配置 API_AUTH_TOKEN_SECRET' => 'API_AUTH_TOKEN_SECRET is not configured',
'密钥错误' => 'Invalid secret',
'时间戳已过期或无效,请同步时间' => 'Timestamp expired or invalid, please sync time',
'签名验证失败' => 'Signature verification failed',
'生成 token 失败' => 'Failed to generate token',
'coin 不能为空' => 'coin is required',
'coin 不能为 0' => 'coin cannot be 0',
'余额不足,无法转出' => 'Insufficient balance to transfer',
'操作失败:' => 'Operation failed: ',
'服务超时,没有原因' => 'Service timeout: Unknown reason',
// PlayStartLogic / GameLogic
'抽奖券不足' => 'Insufficient lottery tickets',
'奖池配置不存在' => 'Lottery config not found',
'该方向下暂无可用路径配置' => 'No path config available for this direction',
// UserLogic
'手机号格式错误,仅支持 +60 开头的马来西亚号码(如 +60123456789' => 'Invalid phone format, only +60 Malaysia numbers supported (e.g. +60123456789)',
// TokenMiddleware / Auth (api/user/*, api/game/*)
'请携带 auth-token' => 'Please provide auth-token',
'auth-token 已过期' => 'auth-token expired',
'auth-token 无效' => 'auth-token invalid',
'auth-token 格式无效' => 'auth-token format invalid',
'auth-token 无效或已失效' => 'auth-token invalid or expired',
'token 已过期,请重新登录' => 'Token expired, please login again',
'token 格式无效' => 'Token format invalid',
'请注册' => 'Please register',
'请重新登录' => 'Please login again',
'请重新登录(当前账号已在其他处登录)' => 'Please login again (account logged in elsewhere)',
];

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace app\api\util;
use support\Request;
/**
* API 多语言:根据请求头 langen=英文zh=中文)翻译返回文案
*/
class ApiLang
{
private const LANG_HEADER = 'lang';
private const LANG_EN = 'en';
private const LANG_ZH = 'zh';
/** @var array<string, string>|null */
private static ?array $enMap = null;
/**
* 从请求中获取语言lang 请求头 en=英文zh=中文,默认 zh
*/
public static function getLang(?Request $request = null): string
{
$request = $request ?? (function_exists('request') ? request() : null);
if ($request === null) {
return self::LANG_ZH;
}
$lang = $request->header(self::LANG_HEADER);
if ($lang !== null && $lang !== '') {
$lang = strtolower(trim((string) $lang));
if ($lang === self::LANG_EN) {
return self::LANG_EN;
}
if ($lang === self::LANG_ZH || $lang === 'chs') {
return self::LANG_ZH;
}
}
return self::LANG_ZH;
}
/**
* 翻译文案:当前请求语言为 en 时返回英文,否则返回原文(中文)
* @param string $message 中文或原文
* @param Request|null $request 当前请求,不传则自动取 request()
*/
public static function translate(string $message, ?Request $request = null): string
{
$lang = self::getLang($request);
if ($lang !== self::LANG_EN) {
return $message;
}
if (self::$enMap === null) {
$path = dirname(__DIR__) . '/lang/en.php';
self::$enMap = is_file($path) ? (require $path) : [];
}
return self::$enMap[$message] ?? $message;
}
/**
* 带占位符的翻译,如 translateParams('当前玩家余额%s小于%s无法继续游戏', [$coin, $minCoin])
* 先翻译再替换en 文案使用 %s 占位)
*/
public static function translateParams(string $message, array $params = [], ?Request $request = null): string
{
$translated = self::translate($message, $request);
if ($params !== []) {
$translated = sprintf($translated, ...$params);
}
return $translated;
}
}

View File

@@ -17,6 +17,11 @@ class ApiException extends BusinessException
{ {
public function render(Request $request): ?Response public function render(Request $request): ?Response
{ {
return json(['code' => $this->getCode() ?: 500, 'message' => $this->getMessage()]); $message = $this->getMessage();
$path = $request->path();
if (str_contains($path, 'api/')) {
$message = \app\api\util\ApiLang::translate($message, $request);
}
return json(['code' => $this->getCode() ?: 500, 'message' => $message]);
} }
} }