外观
角色与授权
一次授权由“角色 → 菜单操作权限”和“用户 → 角色”两类关系组成。先维护角色及其完整权限集合,再给用户追加角色;调用格式、分页和 ID 约定见菜单管理。
维护角色和权限集合
以下路径加前缀 /api/admin/system/role,权限模块为 system-role。
| 方法与路径 | 请求体 | 返回业务数据 | 操作权限 |
|---|---|---|---|
POST /all | SystemRoleQuery | SystemRoleVo[] | 查询 |
POST /page | SystemRoleQuery | PageModel<SystemRoleVo> | 查询 |
POST /map | SystemRoleQuery | SystemRoleMapVo[] | 查询 |
GET /{roleId} | 无 | SystemRoleVo,补充 permissions | 查看详情 |
POST / | SystemRoleDto | SystemRoleVo | 新增 |
PUT /{roleId} | SystemRoleDto | SystemRoleVo | 修改 |
DELETE /{roleId} | 无 | true,需要二次确认 | 删除 |
PUT /disable/{roleId}/{disable} | 无 | 无 | 修改 |
Query 支持 name 模糊匹配、clientId 精确匹配和 isDisable 精确匹配,按创建时间及 ID 倒序。缺少 clientId 不会限制到当前会话客户端。
角色表单示例:
json
{
"name": "报表查看者",
"clientId": "web",
"isDisable": 0,
"permissions": [
{ "menuId": "10001", "permission": "查询,查看详情" }
]
}name 必填且最多 16 字符,clientId 必填且最多 32 字符。isDisable 缺省为 0,按 % 2 处理,只应提交 0、1。角色名称未做唯一性检查。可提交 memo,但与菜单一样,当前 DTO 新增转换不复制备注,修改已有实体才复制。
permissions 中每项包含 roleId、menuId、permission;通过角色表单保存时,项中的 roleId 会被外层角色 ID 覆盖,因此可省略。menuId 应为同客户端已有菜单 ID,permission 是逗号分隔的实际授权操作;本模块不检查菜单是否存在、客户端是否一致,也不验证授权是否属于菜单声明的操作集合,应用需补齐这些规则并使用非空权限字符串。
角色修改会先删除旧权限,再插入本次列表。 permissions 省略、传 null 或空数组都会清空原授权,即使只想修改名称也必须提交要保留的完整权限集合。空字符串权限与空列表含义不同:前者仍保存关系记录。
角色详情通过后处理填充 permissions;all、page 和新增、修改响应不会额外回查该列表,不能据此判断权限已被清空。SystemRoleMapVo 只包含 id、name、clientId、isDisable;完整 VO 另含备注、审计、版本及详情权限。
角色禁用接口会更新状态和缓存时间戳,但当前按用户查询角色、App 权限查询及默认权限 Hook 没有过滤角色的 isDisable。需要可靠撤权时使用解绑或移除角色权限,不能只依赖禁用状态。
绑定与解绑用户
路径前缀为 /api/admin/system/user/role,权限模块为 system-user-role。
| 方法与路径 | 请求体 | 返回业务数据 | 操作权限 |
|---|---|---|---|
GET /bind/{userId} | 无 | Long[],用户绑定的全部角色 ID | 查询 |
POST /bind | UserRoleBindForm | 无 | 修改 |
POST /unbind | UserRoleUnbindForm | 无 | 修改 |
绑定表单:
json
{
"userIdList": ["20001", "20002"],
"roleIdList": ["30001"],
"bindType": "append-update"
}两组 ID 均为非空集合,角色会应用于每一个目标用户。HTTP 表单不允许空角色集合,bindType 仅接受以下编码:
| 模式 | 实际效果 |
|---|---|
append-update | 保留已有角色,仅插入未查询到的用户/角色组合 |
force-update | 先删除每个目标用户的全部角色绑定,再按本次角色集合重新绑定,删除范围不区分客户端 |
服务只绑定能查询到的角色 ID,不会因为部分角色不存在而整体拒绝。强制模式在删除旧绑定后若查不到任何目标角色,会留下空绑定并刷新用户缓存;不要把它当成“校验失败则保留原数据”。绑定未检查目标用户是否存在、角色是否禁用或角色的客户端归属。
解绑请求同样包含非空的 userIdList 和 roleIdList,不含 bindType,删除这些用户与角色组合的关系。管理端绑定和解绑均拒绝目标用户集合包含当前登录用户;Service 不执行这项身份校验,自定义入口需要自己执行。
GET /bind/{userId} 不按客户端过滤,也不回查角色状态;它返回绑定关系中的角色 ID,不是完整角色模型。本模块没有预置用户角色关系的通用 CRUD 或 App 绑定接口。
单独维护权限明细
需要编辑单条角色权限记录时,引入同一个管理端 JAR 即可使用 /api/admin/system/role/permission,权限模块为 system-role-permission。
| 方法与路径 | 请求体 | 返回业务数据 | 操作权限 |
|---|---|---|---|
POST /all | SystemRolePermissionQuery | SystemRolePermissionVo[] | 查询 |
POST /page | SystemRolePermissionQuery | PageModel<SystemRolePermissionVo> | 查询 |
POST /map | SystemRolePermissionQuery | SystemRolePermissionMapVo[] | 查询 |
GET /{systemRolePermissionId} | 无 | SystemRolePermissionVo | 查看详情 |
POST / | SystemRolePermissionDto | SystemRolePermissionVo | 新增 |
PUT /{systemRolePermissionId} | SystemRolePermissionDto | SystemRolePermissionVo | 修改 |
DELETE /{systemRolePermissionId} | 无 | true,需要二次确认 | 删除 |
路径 ID 是权限关系 ID,不是角色 ID。DTO 字段为 roleId、menuId、permission,VO 增加 id、createTime,MapVo 增加 id。这套 CRUD 没有补充必填、关系存在性或权限字符串格式校验。
当前 Query 虽声明 roleId、menuId、permission,Service 没有实现这些字段的查询条件,因此 /all、/page、/map 不会按它们筛选,也没有显式排序。按角色读取权限应使用角色详情,或在可信应用服务中调用 listByRoleId(roleId)。不能把权限明细的分页接口当成角色数据隔离入口。
独立明细 CRUD 不刷新权限缓存;角色整体保存会刷新,但存在下述时序限制。两种编辑方式不能视为缓存语义等价。
对接认证组件
默认 SystemMenuRoleHook 已随 Business 扫描注册,实现 Component 的 RoleHook:
| 读取内容 | 当前过程 |
|---|---|
| 角色名称 | 按用户 ID 获取绑定角色,再按登录 client() 过滤角色,返回去重名称;不过滤角色禁用状态 |
| 模块操作权限 | 获取用户的全部绑定角色 ID,以 code=module、clientId=client()、isDisable=0 查菜单,再查询这些角色在该菜单上的权限,按逗号拆分并去重;没有菜单则返回空集合 |
RoleHook 需要应用或 Business 登录模块聚合并接入权限用户;Authentication 本身不自动执行该 Hook,Security 才负责接口注解拦截。三者的衔接见 Authentication 权限扩展。
模块操作权限这一条路径没有再按角色客户端或禁用状态筛选角色,与 App 权限接口的角色客户端过滤不同。写入时必须确保角色和菜单属于同一客户端;需要更严格隔离时,应用应调整授权服务或 Hook,不能仅依赖 App 返回数据。
管理端使用的模块名称是 system-menu、system-role、system-role-permission、system-user-role,对应常量 SYSTEM_MENU、SYSTEM_ROLE、SYSTEM_ROLE_PERMISSION、SYSTEM_USER_ROLE。操作字符串是 查询、查看详情、新增、修改、删除,菜单排序另有 高级排序;不能把常量名 SEARCH、ADD 当作数据库中的操作值。
要让应用业务接口使用此数据,菜单 code 必须与接口 @PermissionModule 的值一致,角色操作字符串必须与 @PermissionOperation 的值一致,并启用应用认证权限检查。多个操作声明要求同时满足;管理端注解默认要求管理员账号类型。菜单路径与显示状态不会自动保护 HTTP 接口。
一致性与缓存刷新
| 变更入口 | 缓存处理与关联数据 |
|---|---|
| 菜单新增、修改、删除、禁用和显隐 | 刷新全局权限缓存时间戳;删除不清理子菜单或权限关系 |
| 菜单高级排序 | 不刷新权限缓存 |
| 角色新增、修改 | 基础角色写入后刷新全局时间戳,随后才替换权限集合 |
| 角色禁用 | 刷新全局时间戳,但权限计算不据此排除角色 |
| 角色按 ID 删除 | 先物理删除角色权限和用户绑定,再逻辑删除角色,刷新全局时间戳 |
| 用户绑定、解绑、按用户清空绑定 | 刷新对应用户的角色缓存时间戳 |
| 按角色清空用户绑定 | 刷新全局时间戳 |
独立权限 CRUD、save(roleId, permissions)、deleteByRoleId(roleId) | 本身不刷新权限缓存 |
用户绑定和解绑方法有 Spring 事务;角色与权限整体保存、角色删除、菜单批量排序没有声明覆盖全部步骤的事务。追加绑定是先查后插,关系表也没有组合唯一索引,不保证并发防重。
角色保存的缓存刷新发生在权限替换之前,缓存写入也不随数据库事务回滚,不能承诺严格的提交后即时失效。要求一致性时,在应用服务中用事务编排数据库写入,并在成功提交后调用 PermissionCacheService.setGlobalTimestamp();按用户调整角色可在提交后调用 setUserRoleTimestamp(userId)。直接写 Mapper 或 SQL 同样需要补齐缓存刷新。
权限失效时间戳使用固定的 authentication-permission Store 命名空间,不随默认模板的 own.store.namespace 改变。多实例部署应共享对应后端,多应用隔离可覆盖专用模板,见 Authentication 时间戳服务。
当前 Business 登录模块的角色名缓存键不包含用户角色时间戳,因此单独调用 setUserRoleTimestamp 不保证角色名同步刷新;操作权限与角色名的缓存键差异见缓存消费者边界。