角色管理
角色管理
业务用途
角色是 RBAC 权限模型的中间层:管理员维护「角色-菜单」关系,用户通过关联角色间接获得菜单与按钮权限。一个用户可拥有多个角色,权限取并集。
本平台角色表不内置 dataScope 字段(与若依 RuoYi 不同),数据范围由独立的通过 SysRoleDataPermission 表配置。角色本身的职责聚焦在「能访问哪些菜单」上。
角色-菜单的分配通过两个入口:
SysRoleController的/{roleId}/permissions端点(常规角色编辑页使用);PermissionDesignerController的/role/{roleId}/menus端点(权限设计器矩阵页使用,参见 )。
涉及文件
| 层 | 文件路径 |
|---|---|
| 控制器 | backend/src/main/java/com/lowcode/controller/system/SysRoleController.java |
| 服务实现 | backend/src/main/java/com/lowcode/service/impl/SysRoleServiceImpl.java |
| 实体 | backend/src/main/java/com/lowcode/entity/SysRole.java |
| 关联实体 | backend/src/main/java/com/lowcode/entity/SysRoleMenu.java、SysUserRole.java |
| Mapper | backend/src/main/java/com/lowcode/mapper/SysRoleMenuMapper.java、SysMenuMapper.java |
| 前端 API | frontend/src/api/role.ts |
| 前端页面 | frontend/src/views/system/role/index.vue |
数据库表
sys_role(角色表)
| 字段 | 类型 | 说明 |
|---|---|---|
role_name | String | 角色名称(如「系统管理员」) |
role_code | String | 角色编码(如 admin,权限匹配用) |
status | String | 状态(0正常 1禁用) |
remark | String | 备注 |
没有 dataScope 字段
SysRole 实体只有 roleName、roleCode、status、remark 四个业务字段,没有 dataScope 字段。数据权限规则请到 的 SysRoleDataPermission 表配置(ruleType 1~6)。
sys_role_menu(角色-菜单关联表)
| 字段 | 类型 | 说明 |
|---|---|---|
role_id | Long | 角色 ID |
menu_id | Long | 菜单 ID |
sys_user_role(用户-角色关联表)
| 字段 | 类型 | 说明 |
|---|---|---|
user_id | Long | 用户 ID |
role_id | Long | 角色 ID |
后端实现
端点一览
SysRoleController 基础路径:/api/system/role
| HTTP 方法 | 路径 | 方法名 | 说明 |
|---|---|---|---|
| GET | /api/system/role/list | list | 分页查询角色(支持 roleName/status 过滤) |
| GET | /api/system/role/all | all | 获取当前租户全部角色(下拉选项用) |
| GET | /api/system/role/{id} | getById | 获取角色详情 |
| POST | /api/system/role | add | 新增角色 |
| PUT | /api/system/role | update | 修改角色 |
| DELETE | /api/system/role/{id} | delete | 删除角色(同时清理 sys_role_menu 关联) |
| PUT | /api/system/role/changeStatus | changeStatus | 修改角色状态 |
| GET | /api/system/role/{roleId}/permissions | getRolePermissions | 获取角色已分配的菜单 ID 列表 |
| PUT | /api/system/role/{roleId}/permissions | saveRolePermissions | 保存角色菜单权限(先删后插) |
关键代码
获取角色已分配的菜单 ID 列表--查 sys_role_menu 抽取 menuId,供前端菜单树回显勾选:
@GetMapping("/{roleId}/permissions")
public Result<Map<String, Object>> getRolePermissions(@PathVariable Long roleId) {
List<SysRoleMenu> roleMenus = roleMenuMapper.selectList(
new LambdaQueryWrapper<SysRoleMenu>()
.eq(SysRoleMenu::getTenantId, currentTenantId())
.eq(SysRoleMenu::getRoleId, roleId));
List<Long> menuIds = roleMenus.stream()
.map(SysRoleMenu::getMenuId).collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("menuIds", menuIds);
return Result.success(result);
}保存角色菜单权限采用「先全删后全插」的覆盖策略,保证最终状态与勾选一致:
@PutMapping("/{roleId}/permissions")
public Result<Void> saveRolePermissions(@PathVariable Long roleId,
@RequestBody Map<String, Object> params) {
Long tenantId = currentTenantId();
// 校验角色存在
Long roleCount = roleService.count(new LambdaQueryWrapper<SysRole>()
.eq(SysRole::getTenantId, tenantId).eq(SysRole::getId, roleId));
if (roleCount == null || roleCount == 0) {
return Result.error("角色不存在");
}
@SuppressWarnings("unchecked")
List<Number> menuIds = (List<Number>) params.get("menuIds");
// 先删除该角色全部菜单关联
roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>()
.eq(SysRoleMenu::getTenantId, tenantId).eq(SysRoleMenu::getRoleId, roleId));
// 再批量插入新勾选的菜单
if (menuIds != null) {
for (Number menuId : menuIds) {
SysRoleMenu rm = new SysRoleMenu();
rm.setTenantId(tenantId);
rm.setRoleId(roleId);
rm.setMenuId(menuId.longValue());
roleMenuMapper.insert(rm);
}
}
return Result.success("保存成功");
}删除角色时联动清理 sys_role_menu,避免产生孤儿关联数据:
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
roleService.remove(new LambdaQueryWrapper<SysRole>()
.eq(SysRole::getTenantId, currentTenantId()).eq(SysRole::getId, id));
roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>()
.eq(SysRoleMenu::getTenantId, currentTenantId()).eq(SysRoleMenu::getRoleId, id));
return Result.success("删除成功");
}角色与权限设计器的关系
role.ts 中除了上述 /system/role/* 接口,还包含一组 /permission/* 接口(getPermissionMatrix、saveDesignerRoleMenus、getFieldPermissions 等),它们对应 ,用于角色×菜单矩阵、按钮权限、字段权限、数据权限的精细化配置。常规角色页只用 /{roleId}/permissions,权限设计器页用 /permission/*。
前端实现
- API 层
frontend/src/api/role.ts:包含角色 CRUD(getRoleList/addRole/updateRole/deleteRole)、状态切换(changeRoleStatus)、菜单权限读写(getRolePermissions/saveRolePermissions),以及权限设计器相关方法。 - 页面
frontend/src/views/system/role/index.vue:角色列表 + 编辑弹窗。分配菜单时弹出菜单树(el-tree),调用getRolePermissions回显已勾选节点,保存时调用saveRolePermissions提交menuIds数组。

// 角色菜单权限读写
export function getRolePermissions(roleId: number) {
return request.get(`/system/role/${roleId}/permissions`)
}
export function saveRolePermissions(roleId: number, menuIds: number[]) {
return request.put(`/system/role/${roleId}/permissions`, { menuIds })
}操作步骤
- 进入「系统管理 -> 角色管理」,点击「新增」填写角色名称、角色编码、备注。

- 在角色列表点击「分配权限」(或「菜单权限」),弹出菜单树。
- 勾选该角色可访问的目录/菜单/按钮节点,点击「保存」。
4. 如需精细化数据范围,进入[权限设计器](./permission-designer.html)配置数据权限规则。 5. 在[用户管理](./user.html)中给用户关联该角色即生效。 常见问题
角色 roleCode 有什么用?
roleCode 是角色的业务编码(如 admin、common)。SSO 自动开户时会按 defaultRoleCodes 匹配 roleCode 分配默认角色;前端鉴权也常用 roleCode 做路由守卫。建议编码用英文小写加下划线。
为什么保存菜单权限是「先删后插」而不是增量更新?
菜单树勾选状态可能频繁变化,增量 diff 比较复杂。覆盖式写入逻辑简单且能保证最终一致性,因角色权限保存频率低,性能不是瓶颈。
删除角色后,已关联该角色的用户会怎样?
删除角色只清理 sys_role_menu,不会自动清理 sys_user_role。已关联用户会「失去」该角色对应的权限(因角色已不存在),但关联记录残留。建议删角色前先解除用户绑定。
