feat(admin): 更新后台权限管理与同步逻辑,简化权限检查并优化文档
- 新增后台 RBAC 相关文档,提供权限目录与维护命令说明。 - 移除不必要的角色资源同步检查,简化权限审计命令。 - 更新权限描述与同步逻辑,确保一致性与可维护性。 - 统一权限注册表,替换过时的权限别名,增强代码可读性。
This commit is contained in:
@@ -11,10 +11,9 @@ final class AuditAdminAuthorizationCommand extends Command
|
||||
{
|
||||
protected $signature = 'lottery:admin-auth-audit
|
||||
{--skip-route-coverage : 跳过受保护后台路由是否已注册 API 资源的检查}
|
||||
{--skip-resource-bindings : 跳过 permission_required 资源是否绑定动作权限的检查}
|
||||
{--skip-role-resource-sync : 跳过 role_menu_actions 与 role_api_resources 一致性检查}';
|
||||
{--skip-resource-bindings : 跳过 permission_required 资源是否绑定动作权限的检查}';
|
||||
|
||||
protected $description = '检查后台权限配置是否存在路由覆盖缺失、资源绑定缺失或角色资源漂移';
|
||||
protected $description = '检查后台权限配置是否存在路由覆盖缺失或资源绑定缺失';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
@@ -28,10 +27,6 @@ final class AuditAdminAuthorizationCommand extends Command
|
||||
$issues = array_merge($issues, $this->checkPermissionResourceBindings());
|
||||
}
|
||||
|
||||
if (! (bool) $this->option('skip-role-resource-sync')) {
|
||||
$issues = array_merge($issues, $this->checkRoleApiResourceSync());
|
||||
}
|
||||
|
||||
if ($issues === []) {
|
||||
$this->info('Admin authorization audit passed.');
|
||||
|
||||
@@ -116,70 +111,6 @@ final class AuditAdminAuthorizationCommand extends Command
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{type: string, message: string}>
|
||||
*/
|
||||
private function checkRoleApiResourceSync(): array
|
||||
{
|
||||
$expectedRows = DB::table('admin_role_menu_actions as rma')
|
||||
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
|
||||
->select('rma.role_id', 'arb.api_resource_id')
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
$expectedSet = [];
|
||||
foreach ($expectedRows as $row) {
|
||||
$expectedSet[$this->roleApiKey((int) $row->role_id, (int) $row->api_resource_id)] = true;
|
||||
}
|
||||
|
||||
$actualRows = DB::table('admin_role_api_resources')
|
||||
->select('role_id', 'api_resource_id')
|
||||
->get();
|
||||
|
||||
$actualSet = [];
|
||||
foreach ($actualRows as $row) {
|
||||
$actualSet[$this->roleApiKey((int) $row->role_id, (int) $row->api_resource_id)] = true;
|
||||
}
|
||||
|
||||
$roleSlugs = DB::table('admin_roles')->pluck('slug', 'id')->all();
|
||||
$resourceCodes = DB::table('admin_api_resources')->pluck('code', 'id')->all();
|
||||
$issues = [];
|
||||
|
||||
foreach (array_keys($expectedSet) as $key) {
|
||||
if (isset($actualSet[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$roleId, $resourceId] = array_map('intval', explode(':', $key, 2));
|
||||
$issues[] = [
|
||||
'type' => 'role_resource_sync',
|
||||
'message' => sprintf(
|
||||
'Missing role-resource grant: role `%s` should include API resource `%s`.',
|
||||
(string) ($roleSlugs[$roleId] ?? $roleId),
|
||||
(string) ($resourceCodes[$resourceId] ?? $resourceId),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
foreach (array_keys($actualSet) as $key) {
|
||||
if (isset($expectedSet[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$roleId, $resourceId] = array_map('intval', explode(':', $key, 2));
|
||||
$issues[] = [
|
||||
'type' => 'role_resource_sync',
|
||||
'message' => sprintf(
|
||||
'Extra role-resource grant: role `%s` has API resource `%s` without any supporting action binding.',
|
||||
(string) ($roleSlugs[$roleId] ?? $roleId),
|
||||
(string) ($resourceCodes[$resourceId] ?? $resourceId),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{name: string, method: string, uri: string}>
|
||||
*/
|
||||
@@ -222,9 +153,4 @@ final class AuditAdminAuthorizationCommand extends Command
|
||||
{
|
||||
return preg_replace('/^(api\.v1\.admin\.)+/', 'api.v1.admin.', $routeName) ?? $routeName;
|
||||
}
|
||||
|
||||
private function roleApiKey(int $roleId, int $apiResourceId): string
|
||||
{
|
||||
return $roleId.':'.$apiResourceId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ final class SyncAdminAuthorizationCommand extends Command
|
||||
protected $signature = 'lottery:admin-auth-sync
|
||||
{--audit : 同步完成后立即执行后台权限体检}';
|
||||
|
||||
protected $description = '根据后台统一注册表同步 admin_api_resources / bindings / role_api_resources';
|
||||
protected $description = '根据后台统一注册表同步 admin_api_resources 与 resource_bindings';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
@@ -69,25 +69,9 @@ final class SyncAdminAuthorizationCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('admin_role_api_resources')->delete();
|
||||
|
||||
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
|
||||
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
|
||||
->select('rma.role_id', 'arb.api_resource_id')
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
foreach ($roleResourceRows as $row) {
|
||||
DB::table('admin_role_api_resources')->insert([
|
||||
'role_id' => (int) $row->role_id,
|
||||
'api_resource_id' => (int) $row->api_resource_id,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->info(sprintf(
|
||||
'Admin authorization synced: %d resources, %d role-resource rows.',
|
||||
'Admin authorization synced: %d resources.',
|
||||
count(AdminAuthorizationRegistry::resources()),
|
||||
$roleResourceRows->count(),
|
||||
));
|
||||
|
||||
if ((bool) $this->option('audit')) {
|
||||
|
||||
@@ -37,15 +37,12 @@ final class AdminSettingController extends Controller
|
||||
public function update(AdminSettingUpdateRequest $request, string $key): JsonResponse
|
||||
{
|
||||
$setting = LotterySetting::query()->where('setting_key', $key)->first();
|
||||
if ($setting === null) {
|
||||
return ApiResponse::error('Setting not found', 404);
|
||||
}
|
||||
|
||||
LotterySettings::put(
|
||||
$key,
|
||||
$request->validated('value'),
|
||||
$setting->group_name,
|
||||
$setting->description_zh,
|
||||
$setting ? $setting->group_name : (explode('.', $key)[0] ?? 'general'),
|
||||
$setting ? $setting->description_zh : null,
|
||||
);
|
||||
|
||||
$fresh = LotterySetting::query()->where('setting_key', $key)->first();
|
||||
|
||||
@@ -17,10 +17,10 @@ final class AdminUserPermissionSyncController extends Controller
|
||||
public function __invoke(AdminUserPermissionSyncRequest $request, AdminUser $admin_user): JsonResponse
|
||||
{
|
||||
$input = $request->validated();
|
||||
$slugs = array_values(array_unique(array_values(array_filter(
|
||||
$slugs = AdminPermissionBridge::normalizeCanonicalLegacySlugs(array_values(array_filter(
|
||||
(array) ($input['permissions'] ?? $input['permission_slugs'] ?? []),
|
||||
static fn ($v) => is_string($v) && $v !== '',
|
||||
))));
|
||||
)));
|
||||
$siteId = AdminUser::defaultAdminSiteId();
|
||||
|
||||
$codes = [];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Setting;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LotterySetting;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* 玩家端/公开:读取 KV 配置(公开访问)
|
||||
*/
|
||||
final class SettingIndexController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$group = $request->query('group');
|
||||
|
||||
$query = LotterySetting::query()->orderBy('setting_key');
|
||||
|
||||
if (! empty($group)) {
|
||||
$query->where('group_name', $group);
|
||||
}
|
||||
|
||||
$items = $query->get()->map(fn (LotterySetting $s): array => [
|
||||
'key' => $s->setting_key,
|
||||
'value' => $s->value_json,
|
||||
'group' => $s->group_name,
|
||||
'description' => $s->description_zh,
|
||||
]);
|
||||
|
||||
return ApiResponse::success(['items' => $items]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,12 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* 权限模块树节点(表 `admin_menus`)。
|
||||
*
|
||||
* 仅用于 {@see AdminMenuAction} 的分组,不驱动后台侧栏。
|
||||
* 侧栏见 {@see \App\Support\AdminAuthorizationRegistry::navigationDefinitions()}。
|
||||
*/
|
||||
final class AdminMenu extends Model
|
||||
{
|
||||
protected $table = 'admin_menus';
|
||||
|
||||
@@ -57,29 +57,12 @@ final class AdminRole extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* 由已授权的 menu_action 反推 `prd.*`(与 Registry 映射一致)。
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function legacyPermissionSlugs(): array
|
||||
{
|
||||
if (DB::getSchemaBuilder()->hasTable('admin_role_legacy_permissions')) {
|
||||
$slugs = DB::table('admin_role_legacy_permissions')
|
||||
->where('role_id', $this->id)
|
||||
->pluck('permission_slug')
|
||||
->all();
|
||||
|
||||
$out = [];
|
||||
foreach ($slugs as $slug) {
|
||||
if (is_string($slug) && $slug !== '') {
|
||||
$out[$slug] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array_keys($out);
|
||||
sort($keys);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
$codes = DB::table('admin_role_menu_actions as rma')
|
||||
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
|
||||
->where('rma.role_id', $this->id)
|
||||
@@ -95,10 +78,7 @@ final class AdminRole extends Model
|
||||
*/
|
||||
public function syncLegacyPermissionSlugs(array $slugs): void
|
||||
{
|
||||
$legacySlugs = array_values(array_unique(array_filter(
|
||||
$slugs,
|
||||
static fn ($slug): bool => is_string($slug) && $slug !== '',
|
||||
)));
|
||||
$legacySlugs = AdminPermissionBridge::normalizeCanonicalLegacySlugs($slugs);
|
||||
|
||||
$codes = [];
|
||||
foreach ($legacySlugs as $slug) {
|
||||
@@ -119,19 +99,6 @@ final class AdminRole extends Model
|
||||
'menu_action_id' => (int) $mid,
|
||||
]);
|
||||
}
|
||||
|
||||
if (DB::getSchemaBuilder()->hasTable('admin_role_legacy_permissions')) {
|
||||
DB::table('admin_role_legacy_permissions')->where('role_id', $this->id)->delete();
|
||||
$now = now();
|
||||
foreach ($legacySlugs as $slug) {
|
||||
DB::table('admin_role_legacy_permissions')->insert([
|
||||
'role_id' => $this->id,
|
||||
'permission_slug' => $slug,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function assignedUserCount(): int
|
||||
|
||||
@@ -51,14 +51,9 @@ final class AdminAuthorizationRegistry
|
||||
['slug' => 'prd.payout.review', 'name' => '派彩确认·可审核', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.batch.review', 'settlement.batch.view']],
|
||||
['slug' => 'prd.payout.view', 'name' => '派彩确认·查看', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.batch.view']],
|
||||
|
||||
['slug' => 'prd.audit.all', 'name' => '审计日志·全部', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.self', 'name' => '审计日志·自身相关', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.finance', 'name' => '审计日志·资金相关', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.view', 'name' => '审计日志·查看', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
|
||||
['slug' => 'prd.report.all', 'name' => '报表中心·全部', 'nav_segment' => 'reports', 'permission_codes' => ['service.report.view']],
|
||||
['slug' => 'prd.report.risk', 'name' => '报表中心·风控', 'nav_segment' => 'reports', 'permission_codes' => ['service.report.view']],
|
||||
['slug' => 'prd.report.finance', 'name' => '报表中心·财务', 'nav_segment' => 'reports', 'permission_codes' => ['service.report.view']],
|
||||
['slug' => 'prd.report.player', 'name' => '报表中心·玩家', 'nav_segment' => 'reports', 'permission_codes' => ['service.report.view']],
|
||||
['slug' => 'prd.report.view', 'name' => '报表中心·查看', 'nav_segment' => 'reports', 'permission_codes' => ['service.report.view']],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -129,13 +124,13 @@ final class AdminAuthorizationRegistry
|
||||
['segment' => 'wallet', 'label' => 'Wallet', 'href' => '/admin/wallet/transactions', 'activeMatchPrefix' => '/admin/wallet', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.wallet_adjust.manage', 'prd.users.manage', 'prd.users.view_finance']],
|
||||
['segment' => 'settlement', 'label' => 'Settlement', 'href' => '/admin/settlement-batches', 'requiredAny' => ['prd.payout.manage', 'prd.payout.review', 'prd.payout.view']],
|
||||
['segment' => 'reconcile', 'label' => 'Reconcile', 'href' => '/admin/reconcile', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs']],
|
||||
['segment' => 'reports', 'label' => 'Reports', 'href' => '/admin/reports', 'requiredAny' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance']],
|
||||
['segment' => 'reports', 'label' => 'Reports', 'href' => '/admin/reports', 'requiredAny' => ['prd.report.view']],
|
||||
['segment' => 'currencies', 'label' => 'Currencies', 'href' => '/admin/currencies', 'requiredAny' => ['prd.currency.manage']],
|
||||
// 权限与系统
|
||||
['segment' => 'admin_users', 'label' => 'Admin Users', 'href' => '/admin/admin-users', 'requiredAny' => ['prd.admin_user.manage']],
|
||||
['segment' => 'admin_roles', 'label' => 'Admin Roles', 'href' => '/admin/admin-roles', 'requiredAny' => ['prd.admin_role.manage']],
|
||||
['segment' => 'risk', 'label' => 'Risk', 'href' => '/admin/risk', 'requiredAny' => ['prd.draw_result.view', 'prd.draw_result.manage']],
|
||||
['segment' => 'audit', 'label' => 'Audit Logs', 'href' => '/admin/audit-logs', 'requiredAny' => ['prd.audit.all', 'prd.audit.self', 'prd.audit.finance']],
|
||||
['segment' => 'audit', 'label' => 'Audit Logs', 'href' => '/admin/audit-logs', 'requiredAny' => ['prd.audit.view']],
|
||||
['segment' => 'settings', 'label' => 'Settings', 'href' => '/admin/settings', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.currency.manage']],
|
||||
];
|
||||
}
|
||||
@@ -203,9 +198,9 @@ final class AdminAuthorizationRegistry
|
||||
'risk' => ['prd.draw_result.manage', 'prd.draw_result.view'],
|
||||
'settlement' => ['prd.payout.manage', 'prd.payout.review', 'prd.payout.view'],
|
||||
'reconcile' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs'],
|
||||
'reports' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player'],
|
||||
'reports' => ['prd.report.view'],
|
||||
'tickets' => ['prd.users.view_cs', 'prd.users.manage'],
|
||||
'audit' => ['prd.audit.all', 'prd.audit.self', 'prd.audit.finance'],
|
||||
'audit' => ['prd.audit.view'],
|
||||
'settings' => [],
|
||||
];
|
||||
|
||||
@@ -338,7 +333,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.ping', 'module_code' => 'system', 'name' => '后台连通性探测', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/ping', 'route_name' => 'api.v1.admin.ping', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.dashboard', 'module_code' => 'dashboard', 'name' => '后台仪表盘', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/dashboard', 'route_name' => 'api.v1.admin.dashboard', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.auth.me', 'module_code' => 'system', 'name' => '后台当前管理员摘要', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/auth/me', 'route_name' => 'api.v1.admin.auth.me', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.audit.index', 'module_code' => 'audit', 'name' => '审计日志查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/audit-logs', 'route_name' => 'api.v1.admin.audit-logs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.audit.all', 'prd.audit.self', 'prd.audit.finance']],
|
||||
['code' => 'admin.audit.index', 'module_code' => 'audit', 'name' => '审计日志查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/audit-logs', 'route_name' => 'api.v1.admin.audit-logs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.audit.view']],
|
||||
|
||||
['code' => 'admin.admin-users.index', 'module_code' => 'system', 'name' => '管理员列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-users', 'route_name' => 'api.v1.admin.admin-users.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['code' => 'admin.admin-users.store', 'module_code' => 'system', 'name' => '创建管理员', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/admin-users', 'route_name' => 'api.v1.admin.admin-users.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
@@ -432,14 +427,14 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.reconcile-jobs.items.index', 'module_code' => 'reconcile', 'name' => '对账任务明细', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reconcile-jobs/{reconcile_job}/items', 'route_name' => 'api.v1.admin.reconcile-jobs.items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs']],
|
||||
['code' => 'admin.reconcile-jobs.store', 'module_code' => 'reconcile', 'name' => '创建对账任务', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/reconcile-jobs', 'route_name' => 'api.v1.admin.reconcile-jobs.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage']],
|
||||
|
||||
['code' => 'admin.reports.daily-profit', 'module_code' => 'report', 'name' => '每日盈亏汇总', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/daily-profit', 'route_name' => 'api.v1.admin.reports.daily-profit', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.reports.player-win-loss', 'module_code' => 'report', 'name' => '玩家输赢报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/player-win-loss', 'route_name' => 'api.v1.admin.reports.player-win-loss', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.reports.play-dimension', 'module_code' => 'report', 'name' => '玩法维度报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/play-dimension', 'route_name' => 'api.v1.admin.reports.play-dimension', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.reports.rebate-commission', 'module_code' => 'report', 'name' => '佣金回水报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/rebate-commission', 'route_name' => 'api.v1.admin.reports.rebate-commission', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.payout.manage', 'prd.payout.review', 'prd.payout.view', 'prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view', 'prd.audit.all', 'prd.audit.self', 'prd.audit.finance', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.report-jobs.index', 'module_code' => 'report', 'name' => '报表任务列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.report-jobs.store', 'module_code' => 'report', 'name' => '创建报表任务', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.report-jobs.show', 'module_code' => 'report', 'name' => '报表任务详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs/{report_job}', 'route_name' => 'api.v1.admin.report-jobs.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.report-jobs.download', 'module_code' => 'report', 'name' => '下载报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs/{report_job}/download', 'route_name' => 'api.v1.admin.report-jobs.download', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
['code' => 'admin.reports.daily-profit', 'module_code' => 'report', 'name' => '每日盈亏汇总', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/daily-profit', 'route_name' => 'api.v1.admin.reports.daily-profit', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.reports.player-win-loss', 'module_code' => 'report', 'name' => '玩家输赢报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/player-win-loss', 'route_name' => 'api.v1.admin.reports.player-win-loss', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.reports.play-dimension', 'module_code' => 'report', 'name' => '玩法维度报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/play-dimension', 'route_name' => 'api.v1.admin.reports.play-dimension', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.reports.rebate-commission', 'module_code' => 'report', 'name' => '佣金回水报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reports/rebate-commission', 'route_name' => 'api.v1.admin.reports.rebate-commission', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.report-jobs.index', 'module_code' => 'report', 'name' => '报表任务列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.report-jobs.store', 'module_code' => 'report', 'name' => '创建报表任务', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.report-jobs.show', 'module_code' => 'report', 'name' => '报表任务详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs/{report_job}', 'route_name' => 'api.v1.admin.report-jobs.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
['code' => 'admin.report-jobs.download', 'module_code' => 'report', 'name' => '下载报表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs/{report_job}/download', 'route_name' => 'api.v1.admin.report-jobs.download', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.report.view']],
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,48 @@ namespace App\Support;
|
||||
|
||||
final class AdminPermissionBridge
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private const DEPRECATED_LEGACY_SLUG_ALIASES = [
|
||||
'prd.audit.all' => 'prd.audit.view',
|
||||
'prd.audit.self' => 'prd.audit.view',
|
||||
'prd.audit.finance' => 'prd.audit.view',
|
||||
'prd.report.all' => 'prd.report.view',
|
||||
'prd.report.risk' => 'prd.report.view',
|
||||
'prd.report.finance' => 'prd.report.view',
|
||||
'prd.report.player' => 'prd.report.view',
|
||||
];
|
||||
|
||||
/**
|
||||
* 将请求或历史数据中的 `prd.*` 归一为当前 Registry 目录中的 slug。
|
||||
*
|
||||
* @param list<string> $slugs
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function normalizeCanonicalLegacySlugs(array $slugs): array
|
||||
{
|
||||
$canonical = [];
|
||||
$known = array_fill_keys(self::allLegacySlugs(), true);
|
||||
|
||||
foreach ($slugs as $slug) {
|
||||
if (! is_string($slug) || $slug === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset(self::DEPRECATED_LEGACY_SLUG_ALIASES[$slug])) {
|
||||
$slug = self::DEPRECATED_LEGACY_SLUG_ALIASES[$slug];
|
||||
}
|
||||
|
||||
if (isset($known[$slug])) {
|
||||
$canonical[$slug] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array_keys($canonical);
|
||||
sort($keys);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/** @return array<string, list<string>> */
|
||||
public static function legacyMap(): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user