feat: 更新玩法配置管理,简化字段并增强功能

- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。
- 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。
- 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
This commit is contained in:
2026-05-25 14:34:24 +08:00
parent 270d2e9af1
commit e27a00f260
74 changed files with 4469 additions and 280 deletions

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\AdminUser;
use App\Services\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;
/**
* admin_api_resources.is_audit_required=true 的变更类请求,在成功响应后写入审计日志。
*
* 若请求已设置 {@see Request::ATTRIBUTE_AUDIT_RECORDED}(业务层已写更细快照),则跳过。
*/
final class RecordAdminApiAudit
{
public const ATTRIBUTE_AUDIT_RECORDED = 'admin_audit_recorded';
private const MUTATING_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if (! $this->shouldRecord($request, $response)) {
return $response;
}
$admin = $request->user();
if (! $admin instanceof AdminUser) {
return $response;
}
$resource = $this->resolveResource($request);
if ($resource === null || ! (bool) $resource->is_audit_required) {
return $response;
}
$targetId = $this->resolveTargetId($request);
$actionCode = $this->resolveActionCode((string) $resource->code);
AuditLogger::recordForAdmin(
$admin,
$request,
moduleCode: (string) $resource->module_code,
actionCode: $actionCode,
targetType: (string) $resource->code,
targetId: $targetId,
beforeJson: null,
afterJson: [
'http_method' => $request->method(),
'route_name' => $this->normalizeRouteName((string) ($request->route()?->getName() ?? '')),
'status' => $response->getStatusCode(),
'payload' => $this->sanitizedPayload($request),
],
);
return $response;
}
private function shouldRecord(Request $request, Response $response): bool
{
if ($request->attributes->get(self::ATTRIBUTE_AUDIT_RECORDED) === true) {
return false;
}
if (! in_array(strtoupper($request->method()), self::MUTATING_METHODS, true)) {
return false;
}
$status = $response->getStatusCode();
return $status >= 200 && $status < 300;
}
private function resolveResource(Request $request): ?object
{
$routeName = $request->route()?->getName();
if (! is_string($routeName) || $routeName === '') {
return null;
}
return DB::table('admin_api_resources')
->where('route_name', $this->normalizeRouteName($routeName))
->where('status', 1)
->first(['code', 'module_code', 'is_audit_required']);
}
private function normalizeRouteName(string $routeName): string
{
return preg_replace('/^(api\.v1\.admin\.)+/', 'api.v1.admin.', $routeName) ?? $routeName;
}
private function resolveActionCode(string $resourceCode): string
{
$pos = strrpos($resourceCode, '.');
if ($pos === false) {
return $resourceCode;
}
return substr($resourceCode, $pos + 1);
}
private function resolveTargetId(Request $request): ?string
{
$route = $request->route();
if ($route === null) {
return null;
}
foreach (['batch', 'draw', 'transfer_no', 'player', 'admin_user', 'admin_role', 'id', 'play_code', 'number_4d', 'key'] as $key) {
$value = $route->parameter($key);
if ($value === null) {
continue;
}
if (is_object($value) && method_exists($value, 'getKey')) {
return (string) $value->getKey();
}
return (string) $value;
}
return null;
}
/**
* @return array<string, mixed>|null
*/
private function sanitizedPayload(Request $request): ?array
{
$data = $request->except([
'password',
'password_confirmation',
'current_password',
'token',
]);
return $data === [] ? null : $data;
}
}