Skip to content

权限定义与接入

开发一个带权限的页面,通常要做四件事:在模块里定义权限,执行同步,在路由上加鉴权,再把权限分给角色。本文用普通文件举例,路径都相对于 beta/,命令也在 beta/ 下执行。

老系统没有的权限,直接在 Beta 定义即可,不需要先给老系统补权限字段。有老权限需要承接时,再加对应的 legacy_codes

1. 在哪里定义

权限放在模块自己的 config/permissions.php,例如:

text
app/Modules/FileManagement/config/permissions.php

下面是普通文件的简化示例。实际文件还包含批准、下载和受控文件等定义,修改时保留这些内容。

php
<?php

return [
    'prefix' => 'filemanagement',
    'resources' => [
        'file' => [
            'name' => '普通文件',
            'actions' => [
                'view' => ['name' => '查看', 'legacy_codes' => ['wenjian_chakan']],
                'create' => ['name' => '新增'],
                'update' => ['name' => '修改'],
                'delete' => ['name' => '删除'],
                'review' => ['name' => '审核', 'legacy_codes' => ['wenjian_shenhe']],
            ],
            'presets' => [
                'manage' => [
                    'name' => '全部管理',
                    'includes' => ['view', 'create', 'update', 'delete'],
                    'legacy_codes' => ['wenjian_manage'],
                ],
            ],
        ],
    ],
];

权限码按三段拼起来:模块.资源.操作。上面的查看权限就是 filemanagement.file.view,新增权限就是 filemanagement.file.create

配置填什么
prefix模块编码,例如 filemanagement
resources 下的键资源编码,例如 filecontrolled-file
actions 下的键操作编码,CRUD 统一用 view/create/update/delete
name页面上显示的中文名称
legacy_codes对应的老权限编码,没有就省略
presets一组权限的组合,例如一次勾选四项 CRUD

编码统一用小写英文,多个单词用连字符,例如 controlled-file。不要把中文名称、下划线或额外的点放进编码片段。一个模块有多种资源,就在 resources 下并列添加。

新模块还要按模块结构规范注册模块 Provider。同步器通过已注册的模块查找配置,仅新建一个目录不会被发现。

manage 到底包含什么

includes。上面的 manage 明确包含查看、新增、修改、删除四项,审核不在其中。

这里没有“看到 manage 这个名字就自动拥有全部权限”的规则。它是一个 preset,也就是批量选择的组合。运行时仍然逐项检查真实权限:

php
// 删除操作检查这一项。
$checker->has($userId, 'filemanagement.file.delete');

不要检查 filemanagement.file.manage,这个 preset 不会作为独立权限写入数据库。给 actions 新加一个 export,也不会自动把它加进 manage。审核、批准、下载、导出等操作应单独授权。

注意现有配置中的另一个名字:controlled-file.actions.manage。它是受控文件“管理申请/修订”的真实权限,保留了该业务的原有含义。判断时看它写在 actions 还是 presets 下,不能只看名字。

2. 把定义同步到数据库

确认当前环境已执行 RBAC 相关数据库迁移,然后运行:

bash
php artisan lims:rbac-sync --fzx-id=1

1 是示例分中心 ID,换成目标分中心。不传 --fzx-id 会同步全部分中心;权限目录本身是共享的,这个参数主要限制角色、用户快照等同步范围。

这个命令同时处理模块权限目录和老系统的角色权限、用户快照、菜单等兼容数据。可以重复执行;它不会把 Beta 角色页面保存的 grant/deny 覆盖掉,也不会重新分配 Beta 的角色成员。

新增的操作会登记到 rbac_permissions。承接老权限的记录可能保留老 code,将新权限码写在 std_code;纯 Beta 权限可以直接以新权限码作为 code。业务代码统一使用三段权限码即可。

定义并同步,只表示系统认识这个权限,不代表普通用户已经拥有它。 新增的 create/update/delete 等权限还要在角色页面分配,或通过下面的一次性迁移承接老授权。

当前 lims:rbac-sync 没有 --dry-run 参数。需要预览老权限如何展开时,用第 6 节的 lims:permission-cutover --dry-run

3. 在路由上使用

普通增删改查

标准 CRUD 路由使用 lims.permission.auto,不需要再传 filemanagement.file 参数。它从路由名和 Controller 方法推导权限。

例如,普通文件的新增接口:

php
use App\Modules\FileManagement\Http\Controllers\FileController;
use Illuminate\Support\Facades\Route;

Route::middleware(['web', 'legacy.auth'])
    ->prefix('filemanagement')
    ->name('filemanagement.')
    ->group(function (): void {
        Route::post('/files', [FileController::class, 'store'])
            ->name('file.store')
            ->middleware('lims.permission.auto');
    });

这段是现有路由的独立示意,不要重复注册。放进已有路由组时,沿用外层的登录中间件和名称前缀。

最终路由名是 filemanagement.file.store,Controller 方法是 store,实际检查的是 filemanagement.file.create

页面或操作Controller 方法与路由名末段检查的权限
列表、详情indexshowfilemanagement.file.view
新增表单、提交新增createstorefilemanagement.file.create
编辑表单、提交修改editupdatefilemanagement.file.update
删除destroyfilemanagement.file.delete

接入时检查三件事:最终路由名恰好三段,最后一段与 Controller 方法相同,推导出的权限已经同步入库且处于启用状态。缺少任意一项,自动中间件都会拒绝请求。

例如,filemanagement.file.deletedestroy() 就不对;路由名应当以 destroy 结尾,权限码才以 delete 结尾。也不要额外加成 admin.filemanagement.file.store 四段。

路由名字取对还不够,必须挂上 lims.permission.auto 才会执行自动鉴权。它也不处理闭包路由。

审核、下载等特殊操作

特殊操作在 actions 下单独定义,路由显式写出权限码。例如新增一个审核入口时:

php
// 放在已有的登录路由组中;review 是需要自行实现的业务方法。
Route::post('/files/{file}/review', [FileController::class, 'review'])
    ->whereNumber('file')
    ->name('filemanagement.file.review')
    ->middleware('lims.permission:filemanagement.file.review');

如果外层已经有 ->name('filemanagement.'),这里就写 ->name('file.review')

listdataexportdownloadreview 等方法都不在自动映射表里。例如列表数据接口可以显式检查 filemanagement.file.view。不要为了套用自动鉴权,把“下载”改名成 show

自动中间件只挂在标准 CRUD 路由或专门的 CRUD 路由组上。把它挂到整个模块后,审核、下载也会继承它,最终因为方法不匹配而被拒绝。

按钮和业务代码

Blade 用同一个权限码控制按钮显示:

blade
@can('filemanagement.file.create')
    <a class="layui-btn" href="{{ route('filemanagement.file.create') }}">新增文件</a>
@endcan

按钮隐藏后,用户仍可能直接请求接口,所以路由鉴权也要保留。

Service 中需要判断权限时,注入 App\Foundation\Rbac\Contracts\PermissionChecker,调用 $this->permissions->has($userId, 'filemanagement.file.update')。用户 ID 来自可信的当前登录用户,不能使用前端传来的 ID。

开发规范分工:Controller 接收请求、调用 Service、返回响应;FormRequest 验证参数;Service 处理业务权限、分中心范围和状态规则。比如有“修改文件”权限,也仍要检查文件是否属于当前分中心、是否允许修改。

4. 给角色分配权限

进入 Beta 的 RBAC 页面,找到当前分中心的角色,打开“配置权限”,勾选需要的操作并保存。再到角色成员页面,把测试用户加入该角色。

相关页面对应的路由名:

页面路由名
RBAC 首页rbac.index
角色权限rbac.roles.permissions
角色成员rbac.roles.users

保存角色权限需要 system.rbac.assign,超级管理员也可以操作。角色页的 manage 勾选框用于一起选中或取消四项 CRUD,保存的仍是每一项真实权限。

Beta 保存授权时会与老角色权限比较:需要补充的记录为 grant,需要撤销老授权的记录为 deny;选择与老角色权限一致时,恢复为继承老权限。普通同步不会改写这些 Beta 记录。

用户的最终权限,按“老用户权限快照,加上所属角色的 grant,再扣掉 deny”计算。多角色时 deny 优先:A 角色允许删除,B 角色明确禁止删除,这个用户仍不能删除。

取消勾选并不总会产生 deny。例如某角色的老权限本来没有删除,取消后可能只是移除该角色的 grant;用户还可以从另一个角色或老用户快照获得删除权限。角色页显示的是这个角色的选择,验收时还要看具体用户的实际访问结果。

5. 验证是否生效

本地测试鉴权时,在 .env 中启用:

dotenv
RBAC_ENFORCE=true

当前读取的是 config/lims_rbac.php,默认解析器为 EffectivePermissionResolver,负责合并老权限和 Beta 授权。如果 .env 还指定着旧的 RBAC_RESOLVER,需要改回当前解析器,或移除该覆盖以使用默认值。

修改环境配置后,清除旧配置缓存,再检查路由:

bash
php artisan config:clear
php artisan route:list --path=filemanagement -v
php artisan lims:permission-audit

审计命令只读路由和配置,不写数据库。它目前只检查已声明资源下符合三段命名的路由,无法替代全部接口测试,也不能证明数据库已完成同步。

用普通测试账号验证,不要只用超级管理员:

  1. 只给查看权限,确认能打开列表和详情,不能新增、修改、删除。
  2. 加上新增权限,确认新增表单和提交接口都能访问。
  3. 保留查看、新增、修改,取消删除,确认直接请求删除接口也被拒绝。
  4. 检查审核、下载等特殊操作,确认它们需要各自的权限。
  5. 再运行一次普通同步,确认 Beta 保存的授权结果仍然有效。

RBAC_ENFORCE=false 时,自动中间件跳过用户权限检查,但仍会检查路由格式和数据库中的权限是否存在。配置错误返回 403,不会因为关闭强制鉴权就放行。

6. 承接老系统权限时,多做一次初始化

纯 Beta 新权限直接在角色页授权即可。普通文件这种已有老权限、现在要拆细的资源,需要把老角色授权按配置展开一次,这一步叫 cutover。

先完成目录和老角色权限同步,再预览:

bash
php artisan lims:rbac-sync --fzx-id=1
php artisan lims:permission-cutover filemanagement.file --fzx-id=1 --dry-run

按本文的定义,老角色有 wenjian_chakan,就初始化查看;有 wenjian_manage,就初始化查看、新增、修改、删除。审核等其他映射按各自的 legacy_codes 处理。manage 本身不会变成一条新权限。

检查输出中的 rolesplanned_grantsexisting_decisions_skipped 是否符合预期,再正式执行:

bash
php artisan lims:permission-cutover filemanagement.file --fzx-id=1

这会为指定分中心、指定资源写入缺少的角色授权,并记录已完成状态。已有 grant/deny 会保留;同一资源、同一分中心完成后,再执行不会重新初始化或覆盖授权。看到 already_completed 为真,就到 Beta 角色页维护后续变化。

cutover 读取的是同步后的老角色权限。老系统直接给某个用户单独增加的权限,不会由这个命令自动转换成角色授权;迁移时要检查这些用户,为他们配置合适的 Beta 角色。

部署时先准备表结构、同步目录、完成目标分中心的授权初始化,再让业务路由使用新权限。否则老用户可能因为尚未获得拆分后的权限而被拒绝。

7. 以后加权限,怎么改

例如普通文件增加“导出”:

  1. file.actions 下增加 'export' => ['name' => '导出']
  2. 执行 php artisan lims:rbac-sync --fzx-id=1
  3. 导出路由加 lims.permission:filemanagement.file.export
  4. 导出按钮用 @can('filemanagement.file.export') 控制显示。
  5. 在角色页面勾选导出,分别测试有权限和无权限的用户。

已经完成 cutover 的资源,不需要因为新增操作再做初始化。manage 的四项 CRUD 也不会因此改变。

只改中文显示名时,改 name 后同步。修改权限编码要同时检查路由、Blade、Service 和已有授权数据,不能当成普通重命名处理。从配置中删除权限后,同步目前只报告停用候选,不会自动清掉数据库记录和历史授权。

常见问题

现象先检查什么
新权限没有出现在角色页模块 Provider 是否已注册,目录同步是否成功,权限是否启用
返回“自动权限路由配置无效”最终路由名是否三段、末段是否匹配方法、权限是否已入库;查看日志中的 Auto permission route rejected
角色已勾选,用户仍被拒绝当前用户是否属于该分中心的有效角色,其他角色是否有 deny,是否仍在使用旧解析器
取消勾选后仍能访问是否用了超管账号、是否关闭强制鉴权、是否仍从其他角色或老用户快照获得权限
新权限生效,仍不能操作某条文件Service 的分中心、文件归属或状态校验是否拒绝了操作
页面配置已改,行为没变是否有旧配置缓存;是否已通过角色保存或正常同步更新权限缓存

实际代码可对照普通文件权限定义普通文件路由自动鉴权中间件。旧版使用指南中的两段权限码与旧解析器示例主要供兼容代码参考,新接入按本文的三段命名处理。