- 在多个控制器中将权限检查从 hasAdminPermission 更新为 hasPermissionCode,以增强权限管理的灵活性。 - 引入 AdminScopePolicy,优化基于代理节点的权限和数据过滤逻辑,确保管理员能够更精确地控制访问权限。 - 在请求验证中添加 agent_node_id 字段,确保 API 接口支持代理节点的相关操作。 - 更新 AdminUser 模型,新增 hasPermissionCode 方法,以支持更细粒度的权限检查。 - 优化审计日志记录逻辑,确保在处理请求时能够准确记录管理员的操作。
228 lines
7.3 KiB
PHP
228 lines
7.3 KiB
PHP
<?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.');
|
||
}
|
||
}
|