角色管理与权限矩阵方案
需求来源:prompts.md(用户管理模块增加角色管理)
状态:已确认(2026-08-20);2026-08-31 修订:移除"可访问部门"维度(见文末修订记录)
需求重申
在用户管理模块中新增角色管理:
- 角色 CRUD:新建/编辑/删除角色;可设置(或取消)一个默认角色——未选择角色的用户自动套用默认角色权限
- 角色关联用户:角色可关联多个用户;用户编辑时可单选角色
- 表格式权限矩阵:
- 横向表头(操作列,7 项):查看 / 下载 / 编辑 / 删除 / 授权 / 挂载附件 / 解除挂载(与
AllowedAction 对齐)
- 纵向表头(两级):第一级 = 五个密级(公开/内部/秘密/机密/绝密),第二级 = 所属部门方案 / 其他部门方案 → 共 5×2 = 10 行
- 表头复选框:每个行头与每个列头均有复选框,勾选/取消即批量切换整行或整列
- 权限叠加:矩阵与现有单文档 ACL 为 OR 叠加(矩阵为主权限源;现有 ACL 保留,后续将演进为限时跨部门借阅,本次不实现限时)
关键设计决策(已确认)
| 决策点 |
结论 |
| ADMIN 与自定义角色关系 |
保留内置超管:role_code(ADMIN/USER)不变,ADMIN 恒为超管(绕过矩阵,密级限制仍适用);自定义角色仅挂 USER 体系 |
| 矩阵与文档 ACL 关系 |
OR 叠加;后续 ACL 迁移为限时借阅权限(本次不实现) |
| 部门两分类判定 |
所属部门 = 文档归属机构是用户机构或其下级(org_path 前缀);其余(含文档无归属)= 其他部门 |
数据模型(迁移 0012_role_management,0021 修订)
| 表 |
结构 |
sys_role |
id、role_name、sort_no、status(ENABLED/DISABLED)、remark、is_default、default_marker(计算列:is_default=1→1 否则 NULL)+ 唯一约束保证全局至多一个默认角色(复用 active_marker 模式)+ BusinessTableMixin |
sys_role_permission |
role_id、security_level、dept_scope(OWN/OTHER)、can_view / can_download / can_edit / can_delete / can_config_permission / can_bind_attachment / can_unbind_attachment(布尔列,每角色固定 10 行)+ 唯一约束 (role_id, security_level, dept_scope, active_marker) + BusinessTableMixin |
sys_user |
新增 role_id FK → sys_role 可空(NULL → 默认角色) |
权限判定融合(authorization_service.py)
evaluate_plan_access / plan_allowed_actions 新增矩阵判定:
- 密级前置检查不变(密级不足一律拒绝,ADMIN 也不绕过)
- 部门归属分类:文档 org_path 以用户机构 org_path 为前缀 → OWN;否则(含文档无归属)→ OTHER
- 取用户角色(无 → 默认角色;无默认 → 矩阵贡献为空)→ 查
(security_level, dept_scope) 行 → 与 ACL 结果 OR 叠加
- 角色矩阵变更不动 auth_version(权限实时查库);用户换角色时 auth_version+1
API 设计
| 接口 |
说明 |
| GET /roles(分页+keyword+status) |
角色列表(含成员数、isDefault) |
| POST /roles |
新建(roleName/sortNo/status/remark/isDefault) |
| PUT /roles/
| 编辑(rowVersion 乐观锁) |
| DELETE /roles/?rowVersion=
| 删除(有关联用户时拒绝,ROLE_IN_USE) |
| GET /roles//permissions
| 10×7 矩阵 |
| PUT /roles//permissions
| 矩阵整存整取(rowVersion) |
| PUT /roles//users
| 关联用户集合(rowVersion) |
| GET /users 扩展 |
User DTO 增加 roleId/roleName |
- 全部 admin_required + business_audit 审计
- 新 AuditAction:CREATE_ROLE / EDIT_ROLE / DELETE_ROLE / CHANGE_ROLE_PERMISSION / CHANGE_ROLE_USER;AuditTarget.ROLE
- 新错误:RoleInUseError(ROLE_IN_USE)、RoleNotFound、默认角色/矩阵参数校验错误
前端(DirectoryPage)
- 第三个 Tab "角色管理":角色列表(名称/默认/成员数/状态/操作)
- 角色编辑弹窗:名称、排序、状态、是否默认
- 角色详情:基本信息 + 权限矩阵表格(行头两级嵌套,行头/列头复选框全选切换)
- UserEditorModal 增加"角色"下拉(仅 USER 显示)
实现阶段
| 阶段 |
内容 |
验证 |
| 1 |
数据模型 + 迁移 0012 |
test_migration.py |
| 2 |
角色 CRUD/矩阵/用户关联 API + 审计 + OpenAPI |
新增 pytest |
| 3 |
authorization_service 矩阵判定融合 |
判定单测 + 既有 90 测试守底 |
| 4 |
前端角色 Tab + 矩阵 + 用户角色下拉 |
tsc + vitest |
| 5 |
全量回归 |
前后端全量测试 |
风险
- 高:evaluate_plan_access 为核心访问路径,融合必须零回归(既有测试守底)
- 中:默认角色唯一性并发(default_marker 计算列 + 唯一约束 DB 层兜底)
- 中:矩阵整存整取与乐观锁交互
- 低:文档 organization_id 为空的历史数据 → 归入 OTHER
修订记录
- 2026-08-31(迁移 0021 / Flyway V6):移除"可访问部门"维度——
sys_role_dept 表删除、GET/PUT /roles/<id>/departments 接口下线、DeptScope 收敛为 OWN/OTHER(OTHER 涵盖所属部门子树外全部部门,含无归属文档)、矩阵由 15 行收敛为 10 行、存量 ASSIGNED 权限行物理删除、UI 字典 deptScopes 同步收敛。原"已配置部门"语义取消,跨部门访问由"其他部门"行承载。