外观
权限定义与接入
开发一个带权限的页面,通常要做四件事:在模块里定义权限,执行同步,在路由上加鉴权,再把权限分给角色。本文用普通文件举例,路径都相对于 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 下的键 | 资源编码,例如 file、controlled-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=11 是示例分中心 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 方法与路由名末段 | 检查的权限 |
|---|---|---|
| 列表、详情 | index、show | filemanagement.file.view |
| 新增表单、提交新增 | create、store | filemanagement.file.create |
| 编辑表单、提交修改 | edit、update | filemanagement.file.update |
| 删除 | destroy | filemanagement.file.delete |
接入时检查三件事:最终路由名恰好三段,最后一段与 Controller 方法相同,推导出的权限已经同步入库且处于启用状态。缺少任意一项,自动中间件都会拒绝请求。
例如,filemanagement.file.delete 配 destroy() 就不对;路由名应当以 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')。
list、data、export、download、review 等方法都不在自动映射表里。例如列表数据接口可以显式检查 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审计命令只读路由和配置,不写数据库。它目前只检查已声明资源下符合三段命名的路由,无法替代全部接口测试,也不能证明数据库已完成同步。
用普通测试账号验证,不要只用超级管理员:
- 只给查看权限,确认能打开列表和详情,不能新增、修改、删除。
- 加上新增权限,确认新增表单和提交接口都能访问。
- 保留查看、新增、修改,取消删除,确认直接请求删除接口也被拒绝。
- 检查审核、下载等特殊操作,确认它们需要各自的权限。
- 再运行一次普通同步,确认 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 本身不会变成一条新权限。
检查输出中的 roles、planned_grants 和 existing_decisions_skipped 是否符合预期,再正式执行:
bash
php artisan lims:permission-cutover filemanagement.file --fzx-id=1这会为指定分中心、指定资源写入缺少的角色授权,并记录已完成状态。已有 grant/deny 会保留;同一资源、同一分中心完成后,再执行不会重新初始化或覆盖授权。看到 already_completed 为真,就到 Beta 角色页维护后续变化。
cutover 读取的是同步后的老角色权限。老系统直接给某个用户单独增加的权限,不会由这个命令自动转换成角色授权;迁移时要检查这些用户,为他们配置合适的 Beta 角色。
部署时先准备表结构、同步目录、完成目标分中心的授权初始化,再让业务路由使用新权限。否则老用户可能因为尚未获得拆分后的权限而被拒绝。
7. 以后加权限,怎么改
例如普通文件增加“导出”:
- 在
file.actions下增加'export' => ['name' => '导出']。 - 执行
php artisan lims:rbac-sync --fzx-id=1。 - 导出路由加
lims.permission:filemanagement.file.export。 - 导出按钮用
@can('filemanagement.file.export')控制显示。 - 在角色页面勾选导出,分别测试有权限和无权限的用户。
已经完成 cutover 的资源,不需要因为新增操作再做初始化。manage 的四项 CRUD 也不会因此改变。
只改中文显示名时,改 name 后同步。修改权限编码要同时检查路由、Blade、Service 和已有授权数据,不能当成普通重命名处理。从配置中删除权限后,同步目前只报告停用候选,不会自动清掉数据库记录和历史授权。
常见问题
| 现象 | 先检查什么 |
|---|---|
| 新权限没有出现在角色页 | 模块 Provider 是否已注册,目录同步是否成功,权限是否启用 |
| 返回“自动权限路由配置无效” | 最终路由名是否三段、末段是否匹配方法、权限是否已入库;查看日志中的 Auto permission route rejected |
| 角色已勾选,用户仍被拒绝 | 当前用户是否属于该分中心的有效角色,其他角色是否有 deny,是否仍在使用旧解析器 |
| 取消勾选后仍能访问 | 是否用了超管账号、是否关闭强制鉴权、是否仍从其他角色或老用户快照获得权限 |
| 新权限生效,仍不能操作某条文件 | Service 的分中心、文件归属或状态校验是否拒绝了操作 |
| 页面配置已改,行为没变 | 是否有旧配置缓存;是否已通过角色保存或正常同步更新权限缓存 |
实际代码可对照普通文件权限定义、普通文件路由和自动鉴权中间件。旧版使用指南中的两段权限码与旧解析器示例主要供兼容代码参考,新接入按本文的三段命名处理。