329 lines
11 KiB
PHP
329 lines
11 KiB
PHP
<?php
|
||
|
||
namespace App\Support;
|
||
|
||
use App\Models\AuditLog;
|
||
use App\Models\AdminUser;
|
||
use App\Models\Player;
|
||
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);
|
||
$operatorLabels = self::loadOperatorLabels($items);
|
||
|
||
return AdminApiList::payload(
|
||
$paginator,
|
||
fn (AuditLog $r) => self::row($r, $resourceNames, $operatorLabels),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* @param array<string, string> $resourceNames
|
||
* @param array<string, string> $operatorLabels
|
||
* @return array<string, mixed>
|
||
*/
|
||
public static function row(AuditLog $r, array $resourceNames = [], array $operatorLabels = []): array
|
||
{
|
||
$operatorDisplay = self::operatorDisplay($r, $operatorLabels);
|
||
|
||
return [
|
||
'id' => (int) $r->id,
|
||
'operator_type' => $r->operator_type,
|
||
'operator_id' => (int) $r->operator_id,
|
||
'operator_label' => $operatorDisplay['label'],
|
||
'operator_subtitle' => $operatorDisplay['subtitle'],
|
||
'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 loadOperatorLabels(Collection $items): array
|
||
{
|
||
$labels = [];
|
||
|
||
$adminIds = $items
|
||
->filter(fn (AuditLog $row) => $row->operator_type === 'admin' && (int) $row->operator_id > 0)
|
||
->pluck('operator_id')
|
||
->map(fn (mixed $id): int => (int) $id)
|
||
->unique()
|
||
->values()
|
||
->all();
|
||
if ($adminIds !== []) {
|
||
$admins = AdminUser::query()
|
||
->whereIn('id', $adminIds)
|
||
->get(['id', 'username', 'name']);
|
||
|
||
foreach ($admins as $admin) {
|
||
$labels['admin:'.$admin->id] = self::formatIdentityLabel(
|
||
(string) ($admin->name ?? ''),
|
||
(string) ($admin->username ?? ''),
|
||
);
|
||
}
|
||
}
|
||
|
||
$playerIds = $items
|
||
->filter(fn (AuditLog $row) => $row->operator_type === 'player' && (int) $row->operator_id > 0)
|
||
->pluck('operator_id')
|
||
->map(fn (mixed $id): int => (int) $id)
|
||
->unique()
|
||
->values()
|
||
->all();
|
||
if ($playerIds !== []) {
|
||
$players = Player::query()
|
||
->whereIn('id', $playerIds)
|
||
->get(['id', 'username', 'nickname']);
|
||
|
||
foreach ($players as $player) {
|
||
$labels['player:'.$player->id] = self::formatIdentityLabel(
|
||
(string) ($player->nickname ?? ''),
|
||
(string) ($player->username ?? ''),
|
||
);
|
||
}
|
||
}
|
||
|
||
return $labels;
|
||
}
|
||
|
||
/**
|
||
* @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> $operatorLabels
|
||
*/
|
||
private static function operatorDisplay(AuditLog $r, array $operatorLabels): array
|
||
{
|
||
$type = (string) ($r->operator_type ?? '');
|
||
$id = (int) ($r->operator_id ?? 0);
|
||
|
||
if ($type === 'system') {
|
||
return ['label' => '系统', 'subtitle' => null];
|
||
}
|
||
|
||
$key = $type.':'.$id;
|
||
if (isset($operatorLabels[$key]) && $operatorLabels[$key] !== '') {
|
||
return [
|
||
'label' => $operatorLabels[$key],
|
||
'subtitle' => $id > 0 ? sprintf('%s #%d', $type === 'admin' ? '管理员' : ($type === 'player' ? '玩家' : $type), $id) : null,
|
||
];
|
||
}
|
||
|
||
$fallbackLabel = match ($type) {
|
||
'admin' => $id > 0 ? '管理员 #'.$id : '管理员',
|
||
'player' => $id > 0 ? '玩家 #'.$id : '玩家',
|
||
'system' => '系统',
|
||
default => $id > 0 ? sprintf('%s #%d', $type !== '' ? $type : '未知', $id) : ($type !== '' ? $type : '未知'),
|
||
};
|
||
|
||
return ['label' => $fallbackLabel, 'subtitle' => null];
|
||
}
|
||
|
||
private static function formatIdentityLabel(string $primary, string $secondary): string
|
||
{
|
||
$primary = trim($primary);
|
||
$secondary = trim($secondary);
|
||
|
||
if ($primary !== '') {
|
||
return $primary;
|
||
}
|
||
|
||
return $secondary;
|
||
}
|
||
|
||
/**
|
||
* @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.');
|
||
}
|
||
}
|