配置接口lang请求头
This commit is contained in:
36
server/app/api/controller/BaseController.php
Normal file
36
server/app/api/controller/BaseController.php
Normal 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 控制器基类:根据请求头 lang(en=英文,zh=中文)对返回 message 做双语适配
|
||||||
|
*/
|
||||||
|
class BaseController extends OpenController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 成功返回,message 按请求头 lang(en/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;其余接口需在请求头携带 token(base64(username.-.time)),由 TokenMiddleware 鉴权并注入 request->player_id / request->player
|
* 登录接口 /api/user/Login 无需 token;其余接口需在请求头携带 token(base64(username.-.time)),由 TokenMiddleware 鉴权并注入 request->player_id / request->player
|
||||||
*/
|
*/
|
||||||
class UserController extends OpenController
|
class UserController extends BaseController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 登录(form-data 参数)
|
* 登录(form-data 参数)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 获取游戏地址
|
* 获取游戏地址
|
||||||
|
|||||||
53
server/app/api/lang/en.php
Normal file
53
server/app/api/lang/en.php
Normal 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)',
|
||||||
|
];
|
||||||
72
server/app/api/util/ApiLang.php
Normal file
72
server/app/api/util/ApiLang.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\util;
|
||||||
|
|
||||||
|
use support\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 多语言:根据请求头 lang(en=英文,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user