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.
69 lines
4.0 KiB
69 lines
4.0 KiB
|
1 month ago
|
# ADR-0012: 角色管理模块架构
|
||
|
|
|
||
|
|
## Status
|
||
|
|
|
||
|
|
Accepted
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
ADR-0005(原型侧)决定角色功能权限保存时由后端补全祖先节点。当前后端的角色管理散落在 `SystemController` 的 `/api/system/roles/*` 端点中,仅实现了基础 CRUD + 菜单分配,缺少:祖先补全、校验规则、权限控制、角色详情接口、内置角色保护。需要设计完整的角色管理模块架构。
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
### 1. 独立 RoleController,路径 /api/roles/*
|
||
|
|
|
||
|
|
从 `SystemController` 中移除全部角色端点,新建 `RoleController`,路径 `/api/roles/*`。与 `ResourceController` 的独立模式一致。
|
||
|
|
|
||
|
|
### 2. 分步保存:基本信息 + 权限分配分离
|
||
|
|
|
||
|
|
- `POST /api/roles/saveOrUpdate` — 保存角色基本信息(name, code, dataScope, sort, remark)
|
||
|
|
- `POST /api/roles/assign-resources` — 分配功能权限(resourceIds,带祖先补全)
|
||
|
|
- `GET /api/roles/detail` — 返回角色基本信息 + resourceIds(已补全的完整集合)
|
||
|
|
|
||
|
|
前端角色编辑页 Tab 分页各自保存,互不耦合。
|
||
|
|
|
||
|
|
### 3. 术语统一:resourceIds
|
||
|
|
|
||
|
|
API 层统一使用 `resourceIds`(与 ADR-0011 的 resource 术语体系一致),底层表名 `sys_role_menu` 不变。`menuIds` 是泄漏抽象——catalog 和 button 不是"菜单"。
|
||
|
|
|
||
|
|
### 4. 祖先补全逻辑放在 ISysMenuService
|
||
|
|
|
||
|
|
在 `ISysMenuService` 新增 `getAncestorIds(Collection<Long> menuIds)` 方法。沿 parentId 向上遍历是 sys_menu 表的固有操作,属于 menu service 的领域职责。`SysRoleServiceImpl.assignResources` 调用它拿到补全后的集合再写库。
|
||
|
|
|
||
|
|
实现方式:批量查全表构建 ID→parentId 映射,内存中遍历,避免逐节点查 DB。
|
||
|
|
|
||
|
|
### 5. 权限控制:纯 hasAuthority + 数据种子
|
||
|
|
|
||
|
|
角色管理端点使用 `@PreAuthorize("hasAuthority('crm:role:xxx')")` 而非 `hasRole('ADMIN')`。权限码通过 button 节点的 perms 字段注入(ADR-0011 权限码并集机制)。
|
||
|
|
|
||
|
|
引导问题(ADMIN 需要持有 crm:role:xxx 才能管理角色)通过数据初始化器解决:在 sys_menu 中创建角色管理的 button 权限点,并通过幂等初始化器分配给 ADMIN 角色。
|
||
|
|
|
||
|
|
与 `ResourceController` 的 `hasRole('ADMIN')` 不同——角色管理是细粒度权限的首个实践场景,为后续模块渐进迁移到 hasAuthority 模式树立模式。
|
||
|
|
|
||
|
|
### 6. 内置角色保护:SysRole.builtin 字段
|
||
|
|
|
||
|
|
`SysRole` 新增 `Boolean builtin` 字段。`builtin=true` 的角色禁止删除、禁止改 roleCode。ADMIN 是典型内置角色。`builtin` 字段由数据初始化器设置,创建角色接口不允许设 `builtin=true`。
|
||
|
|
|
||
|
|
### 7. 校验规则
|
||
|
|
|
||
|
|
- **roleCode 唯一性**:保存时查 DB,roleCode 已存在且 id 非当前记录则拒绝。
|
||
|
|
- **内置角色保护**:`builtin=true` 的角色拒绝删除、拒绝改 roleCode。
|
||
|
|
- **dataScope 范围校验**:dataScope 必须在 1-4 范围内(DataScopeEnum),非法值拒绝。
|
||
|
|
|
||
|
|
### 8. 删除级联:sys_role_menu + sys_user_role
|
||
|
|
|
||
|
|
`deleteRoleCascade` 同时清理 `sys_role_menu` 和 `sys_user_role`。事务保护,三表操作原子性。内置角色保护已前置防止 ADMIN 误删。
|
||
|
|
|
||
|
|
### 9. 数据初始化器幂等化
|
||
|
|
|
||
|
|
将 `DataInitializer` 的"整体跳过"改为"逐项检查":ADMIN 角色已存在就只补 `builtin=true`,button 权限点不存在就创建,角色-权限绑定不存在就补建。每次启动都跑,只补缺的。
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
- `SystemController` 不再持有角色端点,前端需将 `/api/system/roles/*` 调用迁移到 `/api/roles/*`。
|
||
|
|
- `SysRole` 表新增 `builtin` 列(ddl-auto: update 自动加)。
|
||
|
|
- `ISysMenuService` 新增 `getAncestorIds` 方法,`SysRoleServiceImpl` 新增对它的依赖。
|
||
|
|
- `DataInitializer` 重构为幂等模式,每次启动执行增量检查。
|
||
|
|
- 角色管理权限码(`crm:role:list`/`detail`/`save`/`delete`/`assign`)作为 button 节点写入 sys_menu,通过初始化器分配给 ADMIN 角色。
|
||
|
|
- `ResourceController` 的 `hasRole('ADMIN')` 暂不迁移,作为后续渐进迁移的对照。
|