refactor: 更新权限管理与请求验证逻辑

- 在多个控制器中将权限检查从 hasAdminPermission 更新为 hasPermissionCode,以增强权限管理的灵活性。
- 引入 AdminScopePolicy,优化基于代理节点的权限和数据过滤逻辑,确保管理员能够更精确地控制访问权限。
- 在请求验证中添加 agent_node_id 字段,确保 API 接口支持代理节点的相关操作。
- 更新 AdminUser 模型,新增 hasPermissionCode 方法,以支持更细粒度的权限检查。
- 优化审计日志记录逻辑,确保在处理请求时能够准确记录管理员的操作。
This commit is contained in:
2026-06-03 10:07:38 +08:00
parent 0841fbed32
commit 1dcd4716c5
64 changed files with 2054 additions and 344 deletions

View File

@@ -0,0 +1,227 @@
<?php
namespace App\Support;
use App\Models\AuditLog;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
/**
* 审计日志列表展示:模块 / 动作 / 目标中文标签API 资源名 + 业务码映射)。
*/
final class AuditLogApiPresenter
{
/** @var array<string, string> */
private const MODULE_LABELS = [
'agent' => '代理',
'system' => '系统',
'settings' => '系统设置',
'integration' => '对接站点',
'player_manage' => '玩家管理',
'risk_cap' => '风险池',
'odds' => '赔率',
'play_config' => '玩法配置',
'settlement' => '结算',
'report_jobs' => '报表任务',
'reconcile_jobs' => '对账任务',
'draw' => '开奖',
'wallet' => '钱包',
'reconcile' => '对账',
'job' => '任务',
'bootstrap' => '系统',
];
/** @var array<string, string> */
private const ACTION_LABELS = [
'agent_role.sync_permissions' => '同步代理角色权限',
'admin_role.sync_permissions' => '同步平台角色权限',
'admin_role.create' => '创建平台角色',
'admin_role.update' => '更新平台角色',
'admin_role.delete' => '删除平台角色',
'agent_role.create' => '创建代理角色',
'agent_role.update' => '更新代理角色',
'agent_role.destroy' => '删除代理角色',
'agent_admin_user.create' => '创建代理账号',
'agent_admin_user.sync_roles' => '同步代理账号角色',
'agent_node.create' => '创建代理节点',
'agent_node.update' => '更新代理节点',
'agent_node.destroy' => '删除代理节点',
'agent.delegation.sync' => '同步代理授权',
'admin_user.create' => '创建管理员',
'admin_user.update' => '更新管理员',
'admin_user.delete' => '删除管理员',
'sync_permissions' => '同步权限',
'batch_update' => '批量更新设置',
'payout_adjustment' => '派彩调整',
'rotate_secrets' => '轮换密钥',
'toggle_active' => '切换启用状态',
'enqueue' => '提交报表任务',
];
/** @var array<string, string> */
private const TARGET_TYPE_LABELS = [
'admin_role' => '角色',
'admin_user' => '管理员',
'agent_node' => '代理节点',
'admin_site' => '对接站点',
'player' => '玩家',
'lottery_settings' => '系统设置',
'lottery_setting' => '设置项',
'risk_cap_version' => '风险池版本',
'odds_version' => '赔率版本',
'play_config_item' => '玩法',
'play_config_version' => '玩法配置版本',
'settlement_batch_adjustment' => '结算调整单',
'report_job' => '报表任务',
'reconcile_job' => '对账任务',
'transfer_no' => '转账单',
'draw' => '期次',
'batch' => '批次',
];
/** @var array<string, string> */
private const VERB_LABELS = [
'sync' => '同步',
'store' => '创建',
'update' => '更新',
'destroy' => '删除',
'delete' => '删除',
'create' => '创建',
'publish' => '发布',
'reopen' => '重新开奖',
'freeze' => '冻结',
'unfreeze' => '解冻',
'run' => '执行',
'transfer' => '转账',
'start' => '开始',
'test' => '测试',
];
/**
* @param LengthAwarePaginator<AuditLog> $paginator
* @return array{items: list<array<string, mixed>>, meta: array{current_page: int, per_page: int, total: int, last_page: int}}
*/
public static function listPayload(LengthAwarePaginator $paginator): array
{
$items = collect($paginator->items());
$resourceNames = self::loadResourceNames($items);
return AdminApiList::payload(
$paginator,
fn (AuditLog $r) => self::row($r, $resourceNames),
);
}
/**
* @param array<string, string> $resourceNames
* @return array<string, mixed>
*/
public static function row(AuditLog $r, array $resourceNames = []): array
{
return [
'id' => (int) $r->id,
'operator_type' => $r->operator_type,
'operator_id' => (int) $r->operator_id,
'module_code' => $r->module_code,
'action_code' => $r->action_code,
'target_type' => $r->target_type,
'target_id' => $r->target_id,
'module_label' => self::moduleLabel($r->module_code),
'action_label' => self::actionLabel($r, $resourceNames),
'target_label' => self::targetLabel($r, $resourceNames),
'before_json' => $r->before_json,
'after_json' => $r->after_json,
'ip' => $r->ip,
'user_agent' => $r->user_agent,
'created_at' => $r->created_at?->toIso8601String(),
];
}
/**
* @param Collection<int, AuditLog> $items
* @return array<string, string>
*/
private static function loadResourceNames(Collection $items): array
{
$codes = $items
->pluck('target_type')
->filter(fn (?string $type) => self::isApiResourceCode($type))
->unique()
->values()
->all();
if ($codes === []) {
return [];
}
/** @var array<string, string> */
return DB::table('admin_api_resources')
->whereIn('code', $codes)
->pluck('name', 'code')
->all();
}
private static function moduleLabel(?string $code): string
{
if ($code === null || $code === '') {
return '—';
}
return self::MODULE_LABELS[$code] ?? $code;
}
/**
* @param array<string, string> $resourceNames
*/
private static function actionLabel(AuditLog $r, array $resourceNames): string
{
$action = (string) ($r->action_code ?? '');
if ($action === '') {
return '—';
}
if (isset(self::ACTION_LABELS[$action])) {
return self::ACTION_LABELS[$action];
}
$targetType = (string) ($r->target_type ?? '');
if (self::isApiResourceCode($targetType) && isset($resourceNames[$targetType])) {
return $resourceNames[$targetType];
}
if (isset(self::VERB_LABELS[$action])) {
return self::VERB_LABELS[$action];
}
return $action;
}
/**
* @param array<string, string> $resourceNames
*/
private static function targetLabel(AuditLog $r, array $resourceNames): string
{
$type = (string) ($r->target_type ?? '');
$id = trim((string) ($r->target_id ?? ''));
if ($type === '') {
return $id !== '' ? '#'.$id : '—';
}
if (self::isApiResourceCode($type)) {
$name = $resourceNames[$type] ?? $type;
return $id !== '' ? sprintf('%s #%s', $name, $id) : $name;
}
$typeLabel = self::TARGET_TYPE_LABELS[$type] ?? $type;
return $id !== '' ? sprintf('%s #%s', $typeLabel, $id) : $typeLabel;
}
private static function isApiResourceCode(?string $code): bool
{
return is_string($code) && str_starts_with($code, 'admin.');
}
}