4.0 KiB
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')暂不迁移,作为后续渐进迁移的对照。