You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
77 lines
4.1 KiB
77 lines
4.1 KiB
|
1 month ago
|
# ADR-0011: 统一权限资源树(菜单/权限点管理)
|
||
|
|
|
||
|
|
## Status
|
||
|
|
|
||
|
|
Accepted
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
现有菜单管理(`SysMenu` + `SystemController` 的 `/api/system/menus/*`)是一个初级 CRUD 脚手架,与 PRD `0002-menu-permission-management` 的「统一权限资源树」差距巨大:
|
||
|
|
|
||
|
|
1. **按钮权限码体系缺失**:`SysMenu` 无 `perms`/`denyBehavior`/`apiUrl`/`status`/`description` 字段,按钮节点只是"菜单子节点",无法支撑按钮级 RBAC 鉴权。
|
||
|
|
2. **零层级约束**:`saveMenu` 直接 `saveOrUpdate`,不校验父子类型组合、不锁编辑 type、不查字段必填。
|
||
|
|
3. **删除无防护**:`deleteMenu` 直接 `removeById`,不检查子节点、不级联清理 `sys_role_menu` 中的授权引用,留孤儿授权。
|
||
|
|
4. **无全量资源树接口**:只有按当前用户角色过滤的可见菜单树(`/menus/tree`),管理员无法拿到全量树维护。
|
||
|
|
5. **无图标上传**。
|
||
|
|
6. **无权限码并集**:`PermissionServiceImpl` 只建菜单树,JWT 只填 `roleCode`,按钮级 `hasAuthority` 不可用。
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
### 1. 数据模型:同表补字段 + 枚举映射
|
||
|
|
|
||
|
|
`SysMenu` 同表新增 5 个字段(按 type 区分适用字段,空值表示不适用):
|
||
|
|
|
||
|
|
| 字段 | 类型 | 适用节点 |
|
||
|
|
|---|---|---|
|
||
|
|
| `description` | varchar(100) | 全部 |
|
||
|
|
| `perms` | varchar(100) | button |
|
||
|
|
| `denyBehavior` | varchar(16) | button(hide/disable) |
|
||
|
|
| `status` | varchar(16) | button(enabled/disabled,缺省 enabled) |
|
||
|
|
| `apiUrl` | varchar(200) | button |
|
||
|
|
|
||
|
|
DB `menuType` 保持 `tinyint`(1/2/3)不变,兼容现有数据;代码层引入 `MenuType` 枚举(`CATALOG(1)`/`MENU(2)`/`BUTTON(3)`)映射 PRD 的 catalog/menu/button。
|
||
|
|
|
||
|
|
### 2. API 路径:新建 /api/resources + 保留 /menus/tree
|
||
|
|
|
||
|
|
- **管理端**(全量资源树 CRUD + 图标上传):新建 `ResourceController`,路径 `/api/resources`,入参 JSON body(`ResourceNode` DTO)。
|
||
|
|
- **用户端**(当前用户可见菜单树):保留现有 `GET /api/system/menus/tree`,登录后渲染侧边栏用,不破坏现有链路。
|
||
|
|
|
||
|
|
两者职责分离:管理端给管理员维护全量资源,用户端给登录用户取可见菜单。
|
||
|
|
|
||
|
|
### 3. 层级约束(服务端强制校验)
|
||
|
|
|
||
|
|
| 父节点类型 | 允许子节点类型 |
|
||
|
|
|---|---|
|
||
|
|
| 根(parentId = null/0) | catalog、menu |
|
||
|
|
| catalog | menu |
|
||
|
|
| menu | button |
|
||
|
|
| button | 无(叶子) |
|
||
|
|
|
||
|
|
- 创建/编辑时校验父子类型组合(6 种组合正反例)。
|
||
|
|
- 编辑时 `type` 不可更改(编辑态锁定)。
|
||
|
|
- 字段必填:menu 必填 `route`;button 必填 `perms`(非空)、`denyBehavior`、`apiUrl`、`status`。
|
||
|
|
- `perms` 格式不强制校验(只校验非空)——格式错只影响该按钮自己的鉴权匹配,不污染其他权限码。
|
||
|
|
|
||
|
|
### 4. 删除防护 + 级联清理
|
||
|
|
|
||
|
|
- 删除前检查子节点:有子节点则拒绝。
|
||
|
|
- 删除时级联清理 `sys_role_menu` 中对该节点的授权引用。
|
||
|
|
|
||
|
|
### 5. 权限码并集注入(按钮级鉴权)
|
||
|
|
|
||
|
|
在 `PermissionServiceImpl.initDataScopeContext`(每请求算数据范围时)顺带算权限码并集:
|
||
|
|
- 查用户所有角色 → 查角色授权的菜单 → 过滤 `type=button 且 status=enabled` → 收集 `perms` 字符串。
|
||
|
|
- 与 `roleCode` 一起填 Spring Security authority,使 `@PreAuthorize("hasAuthority('crm:xxx:yyy')")` 生效。
|
||
|
|
- 停用的权限点(status=disabled)不进并集——全局断路,无需逐角色取消授权。
|
||
|
|
|
||
|
|
### 6. 图标上传:复用现有 FileApi
|
||
|
|
|
||
|
|
`POST /api/resources/icon/upload`(multipart)调 crm-file 的 `FileApi` 上传到 MinIO,返回 URL 存入节点 `icon` 字段。校验 png/jpg/gif/svg/webp + ≤500KB。
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
- 现有 `sys_menu` 表需加 5 列(`ddl-auto: update` 自动加)。
|
||
|
|
- 现有 `/api/system/menus/save` 和 `/delete` 保留向后兼容(或逐步迁移到 `/api/resources`),不破坏前端现有调用。
|
||
|
|
- 按钮级鉴权启用后,业务接口可逐步从 `hasRole` 迁移到 `hasAuthority('crm:xxx:yyy')`,但迁移非阻断性,可渐进。
|
||
|
|
- `perms` 格式不强制校验,依赖管理员保证格式正确——错了该按钮鉴权永远拒绝,联调时暴露。
|