# 6.2 角色管理 :::tip 概述 角色模块(goframe_role,租户级表)承载 RBAC 分配与数据权限四档:MenuList 返回带勾选状态的全量菜单树,SavePermission 全量重写角色菜单并清理身份缓存,data_scope 经字典驱动展示。 ::: ## 接口清单 | 方法 | 路由 | 权限 | 说明 | |------|------|------|------| | GET | /api/role/page | sys:role:page | 分页(含 dataScope + dataScopeText) | | GET | /api/role/list | sys:role:list | 列表 | | GET | /api/role/detail/:id | sys:role:detail | 详情 | | POST | /api/role/add | sys:role:add | 新增(data_scope 缺省归一 1) | | PUT | /api/role/update | sys:role:update | 修改 | | DELETE | /api/role/delete/:id | sys:role:delete | 软删(存在用户引用则拒绝) | | DELETE | /api/role/batchDelete | sys:role:batchDelete | 批量删除 | | GET | /api/role/menu/list/:roleId | sys:role:getPermission | 查询角色菜单(带勾选状态) | | POST | /api/role/menu/save | sys:role:savePermission | 保存权限(全量重写) | ## 权限勾选(MenuList / SavePermission) ```text MenuList(GET /api/role/menu/list/:roleId): 返回全量菜单树(type 0/1 均含,供勾选界面展示目录+菜单+按钮三层), 每节点带 Checked = role_menu 是否含该 id(checkedSet 预构建) SavePermission(POST /api/role/menu/save)——语义为"全量重写": 1) 先 Delete 该角色全部 role_menu(软删) 2) 逐个 Insert req.MenuIds 3) clearRoleIdentityCache(roleId) 缓存清理链:按 roleId 查受影响 user_id 列表 → 逐个 Del easygoadmin:user:identity:{uid} ``` ## data_scope 数据范围链路 ```text roleToData:Add/Update 均带 data_scope,入参 ≤0 归一为 1(全部), gvalid in:1,2,3,4 校验 roleSpec.SerializeMaps = {"data_scope": "role_data_scope"} → 列表行 dataScopeText 由字典填充 AfterUpdate(roleClearCache):数据范围变更后失效该角色全部用户身份缓存 → 下一次请求 LoadIdentity 重取 min(data_scope) 即生效 ``` 四档语义与安全边界见 [5.9 数据权限](../core/data-scope)。 ## 删除守卫 存在用户引用则拒绝删除(1014/1011 语义)——先给用户换角色,再删空角色。 ## 已知偏差(如实记录) ::: warning 唯一预检范围偏差 role 的 spec 唯一预检当前声明为 `UniqueFields{Field:"code"}`(未带 Group),而索引 `uk_tenant_code` 是"租户内唯一"——预检范围偏宽:超管为不同租户创建相同角色编码时会被误报"已存在"(数据库层不会冲突)。 修复方向:补 `Group:"tenant_id"`,使预检范围等于索引范围。 ::: ## 排障 | 症状 | 原因 | |------|------| | 保存权限后用户仍 403 | 身份缓存未清(SavePermission 应触发清理链);DEL `easygoadmin:user:identity:{uid}` | | 改 data_scope 不生效 | 同上——AfterUpdate 应清该角色全部用户缓存 | | 删除角色报"存在关联" | 有用户挂该角色,先在用户管理换角色 | | 不同租户建同名角色编码被拒 | 上述已知偏差(预检缺 Group) |