19 KiB
角色管理模块
**本文引用的文件** - [RoleController.java](file://crm-auth/src/main/java/com/crm/auth/controller/RoleController.java) - [ISysRoleService.java](file://crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java) - [SysRoleServiceImpl.java](file://crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java) - [SysRole.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java) - [SysRoleMenu.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysRoleMenu.java) - [SysUserRole.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysUserRole.java) - [RoleDetailVO.java](file://crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDetailVO.java) - [SysRoleMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/SysRoleMapper.java) - [SysRoleMenuMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/SysRoleMenuMapper.java) - [SysUserRoleMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/SysUserRoleMapper.java) - [AuthConstants.java](file://crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java) - [DataInitializer.java](file://crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java) - [BaseEntity.java](file://crm-base/src/main/java/com/crm/base/domain/entity/BaseEntity.java) - [0012-role-management-architecture.md](file://docs/adr/0012-role-management-architecture.md)更新摘要
所做更改
- 更新了核心组件分析,详细描述了完整的RBAC角色管理系统实现
- 新增了详细的API端点说明和权限控制机制
- 完善了数据模型分析,重点突出SysRole.builtin字段的作用
- 增强了服务层业务逻辑分析,包含祖先补全机制和事务保护
- 更新了数据初始化器的幂等性实现细节
- 完善了故障排查指南和性能考虑部分
目录
简介
本模块为 CRM 后端的完整 RBAC(基于角色的访问控制)角色管理系统,提供角色的分页查询、新增/编辑、详情(含授权资源集合)、权限分配(祖先补全+全量替换)与删除(级联清理关联表)。系统遵循 ADR-0012 的架构决策:独立控制器、细粒度 hasAuthority 权限控制、内置角色保护、祖先节点补全、幂等数据初始化。
该实现包含了完整的角色管理功能,支持管理员通过前端界面进行角色的创建、配置和管理,同时确保系统内置角色的安全性和完整性。
项目结构
角色管理模块位于 crm-auth 子模块中,采用标准的 Controller → Service → Mapper 分层架构设计,实体与 DTO 分别放置在 domain/entity 与 domain/dto 包中,常量与配置分别位于 constant 与 config 包。
graph TB
subgraph "认证模块(crm-auth)"
RC["RoleController<br/>/api/roles/*"]
Svc["ISysRoleService / SysRoleServiceImpl"]
Mappers["SysRoleMapper / SysRoleMenuMapper / SysUserRoleMapper"]
Entities["SysRole / SysRoleMenu / SysUserRole"]
VO["RoleDetailVO"]
Cfg["DataInitializer"]
Const["AuthConstants"]
end
subgraph "基础模块(crm-base)"
Base["BaseEntity"]
end
RC --> Svc
Svc --> Mappers
Svc --> Entities
RC --> VO
Cfg --> Entities
Cfg --> Mappers
Svc --> Const
Entities --> Base
图表来源
- RoleController.java:1-98
- ISysRoleService.java:1-43
- SysRoleServiceImpl.java:1-137
- SysRole.java:1-41
- SysRoleMenu.java:1-31
- SysUserRole.java:1-31
- RoleDetailVO.java:1-61
- SysRoleMapper.java:1-10
- SysRoleMenuMapper.java:1-10
- SysUserRoleMapper.java:1-10
- DataInitializer.java:1-159
- BaseEntity.java:1-79
章节来源
- RoleController.java:1-98
- 0012-role-management-architecture.md:1-69
核心组件
- 控制器 RoleController:暴露 /api/roles/* 端点,统一使用 POST + 动作后缀的表单风格接口,并通过 @PreAuthorize 进行细粒度权限校验。
- 服务层 ISysRoleService / SysRoleServiceImpl:封装角色保存、详情查询、权限分配(祖先补全+全量替换)、删除(级联清理)等核心业务逻辑,包含参数校验与事务保护。
- 数据模型 SysRole / SysRoleMenu / SysUserRole:角色主表与角色-菜单、用户-角色关联表,继承 BaseEntity 获得通用审计字段与逻辑删除能力。
- 视图对象 RoleDetailVO:角色详情输出,包含基本信息与已补全祖先的完整 resourceIds。
- 数据初始化 DataInitializer:幂等创建内置角色、系统管理菜单、角色管理按钮权限点,并将全部资源分配给管理员角色。
- 常量 AuthConstants:集中定义错误码与认证相关常量。
章节来源
- RoleController.java:1-98
- ISysRoleService.java:1-43
- SysRoleServiceImpl.java:1-137
- SysRole.java:1-41
- SysRoleMenu.java:1-31
- SysUserRole.java:1-31
- RoleDetailVO.java:1-61
- DataInitializer.java:1-159
- AuthConstants.java:1-64
架构总览
角色管理模块以 REST 控制器为入口,通过服务层完成业务规则与数据一致性保障,持久化层基于 MyBatis-Plus 的 Mapper 访问数据库。权限控制采用 Spring Security 的 hasAuthority,权限码由数据初始化器注入到 sys_menu 的 button 节点并绑定至 ADMIN 角色。
sequenceDiagram
participant FE as "前端"
participant RC as "RoleController"
participant SVC as "SysRoleServiceImpl"
participant MENU as "ISysMenuService"
participant RM as "SysRoleMenuMapper"
participant UR as "SysUserRoleMapper"
participant DB as "数据库"
FE->>RC : POST /api/roles/saveOrUpdate
RC->>SVC : saveRole(role)
SVC->>DB : 校验roleCode唯一性/内置保护/dataScope范围
SVC-->>RC : 返回保存结果
FE->>RC : POST /api/roles/assign-resources
RC->>SVC : assignResources(roleId, resourceIds)
SVC->>MENU : getAncestorIds(resourceIds)
MENU-->>SVC : 补全后的ID集合
SVC->>RM : 先删后插(全量替换sys_role_menu)
SVC-->>RC : 成功
FE->>RC : GET /api/roles/detail?roleId=...
RC->>SVC : getRoleDetail(roleId)
SVC->>RM : 查询sys_role_menu得到resourceIds
SVC-->>RC : 返回RoleDetailVO
FE->>RC : POST /api/roles/delete?id=...
RC->>SVC : deleteRoleCascade(roleId)
SVC->>RM : 清理sys_role_menu
SVC->>UR : 清理sys_user_role
SVC->>DB : 删除角色
SVC-->>RC : 成功
图表来源
- RoleController.java:1-98
- SysRoleServiceImpl.java:1-137
- ISysRoleService.java:1-43
详细组件分析
控制器 RoleController
- 端点设计:
- POST /api/roles/page:分页查询,支持 keyword 模糊匹配角色名称。
- POST /api/roles/saveOrUpdate:新增或编辑角色,接收 roleName、roleCode、dataScope、sort、remark 等表单字段。
- GET /api/roles/detail:获取角色详情及已授权 resourceIds。
- POST /api/roles/assign-resources:分配权限资源,接受逗号分隔的 ID 字符串。
- POST /api/roles/delete:删除角色并级联清理关联。
- 权限控制:每个端点使用 @PreAuthorize("hasAuthority('crm:role:...')") 进行细粒度鉴权。
- 输入处理:对 resourceIds 做空值与分割过滤,避免脏数据进入服务层。
章节来源
- RoleController.java:1-98
服务层 ISysRoleService / SysRoleServiceImpl
- saveRole:
- 非空校验:roleName、roleCode 必填。
- dataScope 范围校验:1-4。
- 内置角色保护:新建时禁止设置 builtin=true;编辑内置角色禁止修改 roleCode。
- 唯一性校验:roleCode 全局唯一(排除自身)。
- getRoleDetail:
- 查询角色基本信息并映射为 RoleDetailVO。
- 从 sys_role_menu 读取已存储的完整 resourceIds(已含祖先)。
- assignResources:
- 调用 ISysMenuService.getAncestorIds 将叶子节点补全为祖先集合。
- 先删除该角色的所有角色-菜单关联,再批量插入补全后的集合,保证全量替换与原子性(@Transactional)。
- deleteRoleCascade:
- 内置角色保护:builtin=true 拒绝删除。
- 级联清理:先清理 sys_role_menu,再清理 sys_user_role,最后删除角色。
flowchart TD
Start(["开始"]) --> Validate["参数校验<br/>非空/范围/唯一性/内置保护"]
Validate --> Valid{"校验通过?"}
Valid -- 否 --> ThrowErr["抛出业务异常"]
Valid -- 是 --> SaveOrUpdate["保存或更新角色"]
SaveOrUpdate --> End(["结束"])
图表来源
- SysRoleServiceImpl.java:36-79
章节来源
- ISysRoleService.java:1-43
- SysRoleServiceImpl.java:1-137
数据模型与映射
- SysRole:角色主实体,继承 BaseEntity,包含 roleName、roleCode、dataScope、sort、remark、builtin 等字段。其中 builtin 字段用于标识内置角色,防止被意外删除或修改编码。
- SysRoleMenu:角色-菜单关联表,唯一约束 (role_id, menu_id)。
- SysUserRole:用户-角色关联表,唯一约束 (user_id, role_id)。
- RoleDetailVO:角色详情输出,包含 resourceIds(已补全祖先的完整授权集合)。
classDiagram
class BaseEntity {
+Long id
+String creatorId
+LocalDateTime createTime
+String updaterId
+LocalDateTime updateTime
+Boolean deleted
}
class SysRole {
+String roleName
+String roleCode
+Integer dataScope
+Integer sort
+String remark
+Boolean builtin
}
class SysRoleMenu {
+Long id
+Long roleId
+Long menuId
}
class SysUserRole {
+Long id
+Long userId
+Long roleId
}
class RoleDetailVO {
+Long id
+String roleName
+String roleCode
+Integer dataScope
+Integer sort
+String remark
+Boolean builtin
+Long[] resourceIds
}
SysRole --|> BaseEntity : "继承"
SysRoleMenu --> SysRole : "关联"
SysUserRole --> SysRole : "关联"
RoleDetailVO ..> SysRole : "映射"
图表来源
- BaseEntity.java:1-79
- SysRole.java:1-41
- SysRoleMenu.java:1-31
- SysUserRole.java:1-31
- RoleDetailVO.java:1-61
章节来源
- SysRole.java:1-41
- SysRoleMenu.java:1-31
- SysUserRole.java:1-31
- RoleDetailVO.java:1-61
- BaseEntity.java:1-79
数据初始化 DataInitializer
- 幂等初始化:每次启动逐项检查,只补缺项。
- 内置角色:确保 ROLE_ADMIN 存在且 builtin=true。
- 菜单树:创建"系统管理"一级目录与"角色管理/菜单管理/部门管理"二级菜单。
- 权限点:为角色管理创建 5 个 button 权限点(list/detail/save/delete/assign),并绑定 API URL。
- 资源绑定:将管理员角色与上述菜单及权限点建立绑定关系。
flowchart TD
InitStart(["启动初始化"]) --> CheckAdmin["检查ADMIN角色是否存在"]
CheckAdmin --> EnsureAdmin["不存在则创建并标记builtin=true"]
EnsureAdmin --> CreateMenus["创建系统管理菜单树"]
CreateMenus --> CreateButtons["创建角色管理button权限点"]
CreateButtons --> BindRoles["绑定ADMIN角色与菜单/权限点"]
BindRoles --> InitEnd(["初始化完成"])
图表来源
- DataInitializer.java:1-159
章节来源
- DataInitializer.java:1-159
依赖关系分析
- 控制器依赖服务层,服务层依赖 Mapper 与菜单服务(用于祖先补全)。
- 实体均继承 BaseEntity,复用通用审计与逻辑删除能力。
- 权限码通过 DataInitializer 注入 sys_menu,并由 Spring Security 在请求阶段进行 hasAuthority 校验。
graph LR
RC["RoleController"] --> SVC["SysRoleServiceImpl"]
SVC --> RM["SysRoleMenuMapper"]
SVC --> UR["SysUserRoleMapper"]
SVC --> MENU["ISysMenuService"]
SVC --> CONST["AuthConstants"]
ENT["SysRole/SysRoleMenu/SysUserRole"] --> BASE["BaseEntity"]
图表来源
- RoleController.java:1-98
- SysRoleServiceImpl.java:1-137
- BaseEntity.java:1-79
章节来源
- RoleController.java:1-98
- SysRoleServiceImpl.java:1-137
- BaseEntity.java:1-79
性能考虑
- 祖先补全:建议 ISysMenuService.getAncestorIds 实现为批量查全表构建 ID→parentId 映射,内存遍历补全,避免逐节点查库。
- 全量替换策略:assignResources 先删后插,适合角色权限变更频率不高但一次性写入量较大的场景;若变更频繁可考虑增量对比减少写放大。
- 分页查询:page 接口使用 MyBatis-Plus 分页插件,注意 keyword 模糊匹配可能影响索引命中,必要时增加合适索引。
- 事务边界:assignResources 与 deleteRoleCascade 使用 @Transactional 保证多表操作的原子性,避免部分失败导致数据不一致。
- 内置角色保护:builtin 字段的引入避免了敏感角色的误操作,提升了系统安全性。
故障排查指南
- 常见错误码(AuthConstants):
- CODE_ROLE_INVALID:角色参数非法(如编码为空、dataScope 越界)。
- CODE_ROLE_CODE_DUPLICATE:角色编码重复。
- CODE_BUILTIN_ROLE_PROTECTED:内置角色保护(禁止删除或改编码)。
- 排查步骤:
- 保存失败:检查 roleName、roleCode 是否非空,dataScope 是否在 1-4,roleCode 是否重复。
- 权限分配无效:确认前端传入的 resourceIds 是否为叶子节点,后端会补全祖先;检查 ISysMenuService.getAncestorIds 是否正确实现。
- 删除失败:确认目标角色是否为内置角色(builtin=true),内置角色不允许删除。
- 权限不足:确认当前用户是否持有对应权限码(如 crm:role:save),可通过 DataInitializer 检查权限点是否已创建并绑定。
- 内置角色问题:检查 SysRole.builtin 字段是否正确设置,确保系统内置角色的完整性。
章节来源
- AuthConstants.java:1-64
- SysRoleServiceImpl.java:36-135
- DataInitializer.java:76-95
结论
角色管理模块以清晰的层次结构与严格的业务校验为核心,结合祖先补全、事务保护与幂等初始化,提供了稳定可靠的 RBAC 角色管理能力。通过细粒度 hasAuthority 权限控制与内置角色保护,既满足安全合规要求,也为后续渐进式迁移树立了模式。
该实现完整覆盖了 RBAC 系统的核心功能,包括角色的生命周期管理、权限分配、数据完整性保护等关键特性,为企业级应用提供了坚实的基础。
附录
- ADR-0012 要点回顾:
- 独立控制器与路径 /api/roles/*。
- 分步保存:基本信息与权限分配分离。
- 术语统一:resourceIds。
- 祖先补全:放在 ISysMenuService。
- 权限控制:纯 hasAuthority,权限码由数据初始化器种子化。
- 内置角色保护:SysRole.builtin。
- 删除级联:sys_role_menu + sys_user_role。
- 数据初始化幂等化。
章节来源
- 0012-role-management-architecture.md:1-69