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

@@ -128,20 +128,6 @@ return new class extends Migration
]);
}
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->join('admin_api_resources as ar', 'ar.id', '=', 'arb.api_resource_id')
->whereIn('ar.code', $reportResourceCodes)
->select('rma.role_id', 'arb.api_resource_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $row->api_resource_id,
], []);
}
}
public function down(): void
@@ -159,7 +145,6 @@ return new class extends Migration
$resourceIds = DB::table('admin_api_resources')->whereIn('code', $codes)->pluck('id');
foreach ($resourceIds as $resourceId) {
DB::table('admin_role_api_resources')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}

View File

@@ -0,0 +1,122 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/** @var list<string> */
private const STALE_RESOURCE_CODES = [
'admin.reports.index',
'admin.reports.store',
'admin.reconcile.index',
'admin.reconcile.store',
'admin.draws.publish',
];
/** @var list<string> */
private const REPORT_RESOURCE_CODES = [
'admin.reports.daily-profit',
'admin.reports.player-win-loss',
'admin.reports.play-dimension',
'admin.reports.rebate-commission',
'admin.report-jobs.index',
'admin.report-jobs.store',
'admin.report-jobs.show',
'admin.report-jobs.download',
];
public function up(): void
{
$now = Carbon::now();
$this->deleteStaleApiResources();
$this->ensureReportViewOnRolesWithReportLegacy();
$this->syncReportResourceBindings($now);
}
public function down(): void
{
// 绑定收紧与角色补权为数据修复,不回滚以免再现漂移。
}
private function deleteStaleApiResources(): void
{
$resourceIds = DB::table('admin_api_resources')
->whereIn('code', self::STALE_RESOURCE_CODES)
->pluck('id');
foreach ($resourceIds as $resourceId) {
$id = (int) $resourceId;
DB::table('admin_api_resource_bindings')->where('api_resource_id', $id)->delete();
DB::table('admin_api_resources')->where('id', $id)->delete();
}
}
private function ensureReportViewOnRolesWithReportLegacy(): void
{
$menuActionId = DB::table('admin_menu_actions')
->where('permission_code', 'service.report.view')
->where('status', 1)
->value('id');
if ($menuActionId === null) {
return;
}
$reportSlugs = ['prd.report.view', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player'];
$roleIds = DB::table('admin_role_legacy_permissions')
->whereIn('permission_slug', $reportSlugs)
->distinct()
->pluck('role_id');
foreach ($roleIds as $roleId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $menuActionId,
]);
}
}
private function syncReportResourceBindings(Carbon $now): void
{
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$registryByCode = [];
foreach (AdminAuthorizationRegistry::resources() as $resource) {
$registryByCode[$resource['code']] = $resource;
}
foreach (self::REPORT_RESOURCE_CODES as $code) {
$resource = $registryByCode[$code] ?? null;
if ($resource === null) {
continue;
}
$resourceId = DB::table('admin_api_resources')->where('code', $code)->value('id');
if ($resourceId === null) {
continue;
}
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
foreach ($resource['permission_codes'] as $permissionCode) {
$menuActionId = $menuActionIds[$permissionCode] ?? null;
if ($menuActionId === null) {
continue;
}
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => (int) $resourceId,
'menu_action_id' => (int) $menuActionId,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
/**
* 移除未接入业务或已由其它表替代的冗余表:
* - system_jobs从未使用
* - admin_role_menus侧栏改由 prd.* + Registry 驱动
* - admin_role_api_resources鉴权由 role_menu_actions + bindings 实时推导
* - admin_*_data_scopes数据范围未落地
*/
return new class extends Migration
{
public function up(): void
{
Schema::dropIfExists('admin_user_data_scopes');
Schema::dropIfExists('admin_role_data_scopes');
Schema::dropIfExists('admin_data_scopes');
Schema::dropIfExists('admin_role_api_resources');
Schema::dropIfExists('admin_role_menus');
Schema::dropIfExists('system_jobs');
}
public function down(): void
{
// 冗余表删除为单向清理,不回滚。
}
};

View File

@@ -0,0 +1,46 @@
<?php
use App\Models\AdminRole;
use App\Support\AdminPermissionBridge;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('admin_role_legacy_permissions')) {
return;
}
$roleIds = DB::table('admin_roles')->pluck('id');
foreach ($roleIds as $roleId) {
$legacySlugs = DB::table('admin_role_legacy_permissions')
->where('role_id', (int) $roleId)
->pluck('permission_slug')
->all();
$slugs = AdminPermissionBridge::normalizeCanonicalLegacySlugs(
is_array($legacySlugs) ? $legacySlugs : [],
);
if ($slugs === []) {
continue;
}
$role = AdminRole::query()->find((int) $roleId);
if ($role !== null) {
$role->syncLegacyPermissionSlugs($slugs);
}
}
Schema::dropIfExists('admin_role_legacy_permissions');
}
public function down(): void
{
// 单向清理slug 已合并,权限以 admin_role_menu_actions 为准。
}
};

View File

@@ -5,7 +5,6 @@ namespace Database\Seeders;
use App\Models\AdminRole;
use App\Models\AdminUser;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use App\Support\AdminPermissionBridge;
/**
@@ -16,27 +15,9 @@ use App\Support\AdminPermissionBridge;
final class AdminRbacAndUserSeeder extends Seeder
{
/** @param list<string> $legacySlugs */
private function syncRoleMenuActions(AdminRole $role, array $legacySlugs): void
private function syncRolePermissions(AdminRole $role, array $legacySlugs): void
{
$codes = [];
foreach ($legacySlugs as $slug) {
$codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug));
}
$codes = array_values(array_unique($codes));
$ids = DB::table('admin_menu_actions')
->whereIn('permission_code', $codes)
->where('status', 1)
->pluck('id')
->all();
DB::table('admin_role_menu_actions')->where('role_id', $role->id)->delete();
foreach ($ids as $mid) {
DB::table('admin_role_menu_actions')->insert([
'role_id' => $role->id,
'menu_action_id' => (int) $mid,
]);
}
$role->syncLegacyPermissionSlugs($legacySlugs);
}
/** @return list<string> */
@@ -51,13 +32,13 @@ final class AdminRbacAndUserSeeder extends Seeder
['slug' => AdminUser::ROLE_SUPER_ADMIN],
['name' => '超级管理员'],
);
$this->syncRoleMenuActions($super, $this->allCatalogSlugs());
$this->syncRolePermissions($super, $this->allCatalogSlugs());
$risk = AdminRole::query()->updateOrCreate(
['slug' => 'risk_operator'],
['name' => '风控运营员'],
);
$this->syncRoleMenuActions($risk, [
$this->syncRolePermissions($risk, [
'prd.play_switch.manage',
'prd.odds.manage',
'prd.risk_cap.manage',
@@ -66,15 +47,16 @@ final class AdminRbacAndUserSeeder extends Seeder
'prd.draw_result.manage',
'prd.payout.review',
'prd.wallet_reconcile.view',
'prd.audit.self',
'prd.audit.view',
'prd.player_freeze.manage',
'prd.report.view',
]);
$finance = AdminRole::query()->updateOrCreate(
['slug' => 'finance'],
['name' => '财务/对账员'],
);
$this->syncRoleMenuActions($finance, [
$this->syncRolePermissions($finance, [
'prd.users.view_finance',
'prd.risk_cap.view',
'prd.rebate.view',
@@ -83,17 +65,19 @@ final class AdminRbacAndUserSeeder extends Seeder
'prd.payout.view',
'prd.wallet_reconcile.manage',
'prd.wallet_adjust.manage',
'prd.audit.finance',
'prd.audit.view',
'prd.report.view',
]);
$cs = AdminRole::query()->updateOrCreate(
['slug' => 'customer_service'],
['name' => '客服人员'],
);
$this->syncRoleMenuActions($cs, [
$this->syncRolePermissions($cs, [
'prd.users.view_cs',
'prd.draw_result.view',
'prd.wallet_reconcile.view_cs',
'prd.report.view',
]);
$username = 'admin';

View File

@@ -88,5 +88,12 @@ final class LotterySettingsSeeder extends Seeder
'settlement',
'为 true 时结算派彩在毛赢基础上再乘 (1 - rebate_rate_snapshot);默认 false实扣已含回水',
);
LotterySettings::put(
'frontend.play_rules_html',
'',
'frontend',
'玩家端玩法规则页面显示的自定义 HTML 富文本内容',
);
}
}