feat(admin): 更新后台权限管理与同步逻辑,简化权限检查并优化文档

- 新增后台 RBAC 相关文档,提供权限目录与维护命令说明。
- 移除不必要的角色资源同步检查,简化权限审计命令。
- 更新权限描述与同步逻辑,确保一致性与可维护性。
- 统一权限注册表,替换过时的权限别名,增强代码可读性。
This commit is contained in:
2026-05-22 16:11:48 +08:00
parent 2e8ab58970
commit 1d31f9e872
24 changed files with 489 additions and 238 deletions

View File

@@ -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;
}
}

View File

@@ -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')) {