- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。 - 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。 - 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
185 lines
7.2 KiB
PHP
185 lines
7.2 KiB
PHP
<?php
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| 【应用入口】路由与中间件
|
||
|--------------------------------------------------------------------------
|
||
| api 分组 prepend了 NegotiateLotteryLocale:
|
||
| 保证任意 API 在未进入控制器前已确定 lottery_locale / app locale,便于统一翻译 msg。
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
use App\Lottery\ErrorCode;
|
||
use App\Support\ApiResponse;
|
||
use Illuminate\Http\Request;
|
||
use App\Support\LotteryLocale;
|
||
use Illuminate\Foundation\Application;
|
||
use App\Http\Middleware\EnsureAdminApi;
|
||
use App\Http\Middleware\EnsurePlayerApi;
|
||
use App\Http\Middleware\RecordAdminApiAudit;
|
||
use Illuminate\Console\Scheduling\Schedule;
|
||
use Illuminate\Auth\AuthenticationException;
|
||
use App\Http\Middleware\EnsureAdminApiResourcePermission;
|
||
use Illuminate\Validation\ValidationException;
|
||
use App\Http\Middleware\NegotiateLotteryLocale;
|
||
use Illuminate\Foundation\Configuration\Exceptions;
|
||
use Illuminate\Foundation\Configuration\Middleware;
|
||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||
|
||
return Application::configure(basePath: dirname(__DIR__))
|
||
->withRouting(
|
||
web: __DIR__.'/../routes/web.php',
|
||
// 自动加前缀 `api` + middleware `api`,见 routes/api.php
|
||
api: __DIR__.'/../routes/api.php',
|
||
commands: __DIR__.'/../routes/console.php',
|
||
channels: __DIR__.'/../routes/channels.php',
|
||
health: '/up',
|
||
)
|
||
->withMiddleware(function (Middleware $middleware): void {
|
||
// 多语言:必须在其他 api 中间件之前执行,以便鉴权失败时也能按语言返回 msg
|
||
$middleware->api(prepend: [
|
||
NegotiateLotteryLocale::class,
|
||
]);
|
||
$middleware->alias([
|
||
// 玩家端需登录路由使用;解析 Bearer → Player
|
||
'lottery.player' => EnsurePlayerApi::class,
|
||
// 后台 API 预留:Sanctum / RBAC
|
||
'lottery.admin' => EnsureAdminApi::class,
|
||
'admin.api-resource' => EnsureAdminApiResourcePermission::class,
|
||
'admin.audit' => RecordAdminApiAudit::class,
|
||
]);
|
||
})
|
||
->withExceptions(function (Exceptions $exceptions): void {
|
||
/**
|
||
* 统一 JSON:与 {@see ApiResponse} 一致,供三端消费;多语言与 {@see LotteryLocale} 对齐。
|
||
* 覆盖:校验失败、模型/路由 404、限流、未处理异常(生产不泄露堆栈文案)。
|
||
*/
|
||
$locale = static function (Request $request): string {
|
||
return (string) ($request->attributes->get('lottery_locale') ?? LotteryLocale::resolve($request));
|
||
};
|
||
|
||
$exceptions->render(function (AuthenticationException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
|
||
return ApiResponse::error(
|
||
trans('admin.unauthenticated', [], $locale($request)),
|
||
ErrorCode::AdminUnauthenticated->value,
|
||
null,
|
||
401,
|
||
);
|
||
});
|
||
|
||
$exceptions->render(function (ValidationException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
|
||
return ApiResponse::error(
|
||
trans('api.validation_failed', [], $locale($request)),
|
||
ErrorCode::ValidationFailed->value,
|
||
['errors' => $e->errors()],
|
||
422,
|
||
);
|
||
});
|
||
|
||
$exceptions->render(function (ModelNotFoundException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
|
||
return ApiResponse::error(
|
||
trans('api.not_found', [], $locale($request)),
|
||
ErrorCode::NotFound->value,
|
||
null,
|
||
404,
|
||
);
|
||
});
|
||
|
||
$exceptions->render(function (NotFoundHttpException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
|
||
return ApiResponse::error(
|
||
trans('api.not_found', [], $locale($request)),
|
||
ErrorCode::NotFound->value,
|
||
null,
|
||
404,
|
||
);
|
||
});
|
||
|
||
$exceptions->render(function (TooManyRequestsHttpException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
|
||
return ApiResponse::error(
|
||
trans('api.too_many_requests', [], $locale($request)),
|
||
ErrorCode::TooManyRequests->value,
|
||
null,
|
||
429,
|
||
);
|
||
});
|
||
|
||
$exceptions->render(function (HttpException $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
if ($e instanceof NotFoundHttpException || $e instanceof TooManyRequestsHttpException) {
|
||
return null;
|
||
}
|
||
|
||
$status = $e->getStatusCode();
|
||
$msg = $e->getMessage();
|
||
if ($msg === '') {
|
||
$msg = trans('api.client_error', [], $locale($request));
|
||
}
|
||
$code = $status >= 500 ? ErrorCode::InternalError->value : ErrorCode::ClientHttpError->value;
|
||
|
||
return ApiResponse::error($msg, $code, null, $status);
|
||
});
|
||
|
||
$exceptions->render(function (Throwable $e, Request $request) use ($locale) {
|
||
if (! $request->is('api/*')) {
|
||
return null;
|
||
}
|
||
if ($e instanceof ValidationException
|
||
|| $e instanceof ModelNotFoundException
|
||
|| $e instanceof NotFoundHttpException
|
||
|| $e instanceof TooManyRequestsHttpException
|
||
|| $e instanceof HttpException
|
||
) {
|
||
return null;
|
||
}
|
||
|
||
$showDetails = (bool) config('app.debug');
|
||
$msg = $showDetails ? $e->getMessage() : trans('api.server_error', [], $locale($request));
|
||
|
||
return ApiResponse::error(
|
||
$msg !== '' ? $msg : trans('api.server_error', [], $locale($request)),
|
||
ErrorCode::InternalError->value,
|
||
$showDetails ? ['exception' => $e::class] : null,
|
||
500,
|
||
);
|
||
});
|
||
})
|
||
->withSchedule(function (Schedule $schedule): void {
|
||
$schedule->command('lottery:draw-tick')->everyMinute();
|
||
$schedule->command('lottery:wallet-transfer-reconcile --lookback-hours=24 --stale-minutes=15 --limit=1000')
|
||
->everyTenMinutes()
|
||
->withoutOverlapping();
|
||
$schedule->command('lottery:ticket-pending-confirm-reconcile --stale-minutes=5 --limit=500')
|
||
->everyMinute()
|
||
->withoutOverlapping();
|
||
/** @see docs/01-界面文档.md §2.1 `draw.countdown` */
|
||
if (config('lottery.realtime_hall_countdown', true)) {
|
||
$schedule->command('lottery:hall-countdown')->everySecond();
|
||
}
|
||
})
|
||
->create();
|