refactor: 重构语言协商中间件,简化语言解析逻辑并增强异常处理,更新相关配置以支持多环境开发

This commit is contained in:
2026-05-08 17:40:09 +08:00
parent 85e57782cc
commit e478597d13
11 changed files with 295 additions and 54 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Support\LotteryLocale;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -16,62 +17,18 @@ use Symfony\Component\HttpFoundation\Response;
* 【协商顺序】① 请求头 X-Locale必须为 config 支持的 zh/en/ne 之一);② Accept-Language 首选项;③ fallback。
*
* 【与前端】浏览器或 App 可直接带 X-Locale或仅靠 Accept-Language无需前端维护文案 JSON。
*
* 【与异常 JSON】{@see LotteryLocale} middleware 同源,在未命中路由、`lottery_locale` 未写入时仍可从 Header 推导。
*/
class NegotiateLotteryLocale
{
public function handle(Request $request, Closure $next): Response
{
$locale = $this->resolveLocale($request);
$locale = LotteryLocale::resolve($request);
// attribute 名称固定为 lottery_locale与 Request::lotteryLocale() 宏一致
$request->attributes->set('lottery_locale', $locale);
app()->setLocale($locale);
return $next($request);
}
/** 综合 X-Locale 与 Accept-Language得到最终语言代码 */
private function resolveLocale(Request $request): string
{
/** @var list<string> $supported */
$supported = array_values(array_unique(config('lottery.locales.supported', ['en', 'zh', 'ne'])));
$fallback = (string) config('lottery.locales.fallback', 'en');
$header = strtolower(trim((string) $request->header('X-Locale', '')));
if ($header !== '' && in_array($header, $supported, true)) {
return $header;
}
return $this->fromAcceptLanguage((string) $request->header('Accept-Language', ''), $supported, $fallback);
}
/**
* 解析标准 Accept-Language可带权重 q=),只认主语言段 zh / en / ne。
*
* @param list<string> $supported config('lottery.locales.supported')
*/
private function fromAcceptLanguage(string $header, array $supported, string $fallback): string
{
if ($header === '') {
return $fallback;
}
foreach (explode(',', $header) as $part) {
$code = strtolower(trim(explode(';', trim($part), 2)[0] ?? ''));
if ($code === '') {
continue;
}
$primary = explode('-', $code, 2)[0] ?? $code;
if ($primary === 'zh' && in_array('zh', $supported, true)) {
return 'zh';
}
if ($primary === 'ne' && in_array('ne', $supported, true)) {
return 'ne';
}
if ($primary === 'en' && in_array('en', $supported, true)) {
return 'en';
}
}
return $fallback;
}
}

25
app/Lottery/ErrorCode.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace App\Lottery;
/**
* HTTP JSON 中与 `ApiResponse` 对齐的业务码常量(对齐 docs/04 §10
* SSO 等特殊场景仍由各模块直接写整数(如 EnsurePlayerApi 使用的 8001)。
*/
enum ErrorCode: int
{
/** 表单 / Query 校验失败(见 ValidationException → 422 */
case ValidationFailed = 9001;
/** 资源或路由不存在 */
case NotFound = 9004;
/** 请求过于频繁 */
case TooManyRequests = 9031;
/** `abort(4xx)` 等客户端类 Http 异常HTTP 状态码见响应头;本码作业务归类) */
case ClientHttpError = 9010;
/** 未分类服务端异常(生产环境不向客户端暴露细节) */
case InternalError = 9999;
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Support;
use Illuminate\Http\Request;
/**
* NegotiateLotteryLocale 相同的协商规则;供异常 render 等非中间件链路使用。
*/
final class LotteryLocale
{
public static function resolve(Request $request): string
{
/** @var list<string> $supported */
$supported = array_values(array_unique(config('lottery.locales.supported', ['en', 'zh', 'ne'])));
$fallback = (string) config('lottery.locales.fallback', 'en');
$header = strtolower(trim((string) $request->header('X-Locale', '')));
if ($header !== '' && in_array($header, $supported, true)) {
return $header;
}
return self::fromAcceptLanguage((string) $request->header('Accept-Language', ''), $supported, $fallback);
}
/** @param list<string> $supported */
private static function fromAcceptLanguage(string $header, array $supported, string $fallback): string
{
if ($header === '') {
return $fallback;
}
foreach (explode(',', $header) as $part) {
$code = strtolower(trim(explode(';', trim($part), 2)[0] ?? ''));
if ($code === '') {
continue;
}
$primary = explode('-', $code, 2)[0] ?? $code;
if ($primary === 'zh' && in_array('zh', $supported, true)) {
return 'zh';
}
if ($primary === 'ne' && in_array('ne', $supported, true)) {
return 'ne';
}
if ($primary === 'en' && in_array('en', $supported, true)) {
return 'en';
}
}
return $fallback;
}
}

View File

@@ -22,7 +22,7 @@ final class LotteryMessage
public static function sso(Request $request, int $code): string
{
$fallback = (string) config('lottery.locales.fallback', 'en');
$locale = (string) ($request->attributes->get('lottery_locale') ?? $fallback);
$locale = (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
$key = 'sso.'.$code; // 对应 lang/{locale}/sso.php 内键名,如 '8001'
$msg = trans($key, [], $locale);