2026-08-02-post-role-permission-design.md 7.3 KB

职位纳入权限载体(职位关联角色 · 并集模型)

日期: 2026-08-02
范围: sys_post / sys_post_role / 用户有效角色解析 / 职位管理「角色配置」/ 用户管理职位多选
方案: 运行时并集(方案 1),不物化复制到 sys_user_role

已确认决策

项 结论
权限来源 并集:用户直接角色 ∪ 职位继承角色
继承内容 菜单权限 + 数据权限(与现有多角色并集语义一致)
配置入口 职位管理增加「角色配置」;用户管理补上职位多选
非目标 角色页反向关联职位;权限来源可视化;OA OaRole / 审批人解析;职位自带菜单树

背景与现状

  • 角色(sys_role)是权限载体:sys_role_menu(菜单)、data_scope + sys_role_data_scope_detail(数据范围)。
  • 用户通过 sys_user_role 绑定角色;登录后 iter_user_menu_perm_codes、apply_data_scope(_resolve_user_role_scopes)仅查直接角色。
  • 职位(sys_post)仅有 sys_user_post 关联;不参与 RBAC。OA 表单「职位」控件另用,本设计不改动。
  • 用户 API 已支持 postIds,用户管理前端未暴露职位多选。

目标模型

用户 ──sys_user_role──────────────▶ 角色 ──▶ 菜单 / 数据权限
 │                                    ▲
 └──sys_user_post──▶ 职位 ──sys_post_role──┘

有效角色 = 启用直接角色 ∪(启用职位所关联的启用角色)
有效菜单码 / 数据范围 = 对有效角色取并集(与现规范一致)

数据模型

新表 sys_post_role

对齐 sys_user_role / sys_user_post 结构:

字段 说明
id VARCHAR(64) UUID PK
post_id 职位 ID
role_id 角色 ID
公共字段 tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
  • 软删除:del_flag=1,查询默认排除。
  • 业务唯一:同一租户下同一 (post_id, role_id) 仅一条有效关联(写入时软删旧行再插,或应用层去重;与现有 sys_user_role 替换策略一致)。
  • SQL 增量:docs/sql/sys_post_role.sql;模型:SysPostRole(backend/app/models/models.py)。

既有表不变:sys_post、sys_user_post、sys_user_role、sys_role*。

后端权限解析

统一入口

在 backend/app/services/web_auth_roles.py(或抽出共享 helper,供 data_scope 复用)提供:

  1. get_effective_role_ids(db, user_id) -> List[str]

    • 来源 A:sys_user_role → sys_role(del_flag=0, status='1', 租户一致)
    • 来源 B:sys_user_post → sys_post(del_flag=0, status='1')→ sys_post_role → sys_role(同上)
    • 去重后返回 role id 列表。
  2. 基于有效角色 ID 改造:

    • user_has_active_roles:有效角色非空即为真(仅职位带角色、无直接角色也通过登录门禁)。
    • get_user_role_names / _active_role_name_rows / resolve_web_role_for_user:按有效角色名称映射 admin / super_admin。
    • iter_user_menu_perm_codes:SysRoleMenu 的 role_id 属于有效角色集合(可用 role_id IN (...) 或等价 join,避免只 join SysUserRole)。
  3. data_scope._resolve_user_role_scopes:角色查询改为基于有效角色 ID,不再仅 join SysUserRole。

停用语义(显式)

状态 是否参与并集
职位 status != '1' 或已删 否(其下角色关联全部不计入)
角色 status != '1' 或已删 否
sys_post_role / sys_user_post / sys_user_role del_flag=1 否

API

方法 路径 说明
GET /web/admin/posts/{postId}/roles 返回该职位已关联 roleIds(及可选角色摘要)
PUT /web/admin/posts/{postId}/roles body:{ roleIds: string[] },全量替换关联(对齐 PUT .../users)
  • 权限/租户:与现有 posts CRUD 一致(当前用户租户,不信任客户端改租户)。
  • 用户创建/更新:继续使用已有 postIds;确保 _replace_user_posts 与角色替换互不影响(已有行为保留)。
  • 删除职位:现有软删职位逻辑下,有效角色解析因职位 del_flag/status 自动失效;可选同步软删 sys_post_role(与删职位时处理 sys_user_post 的方式对齐即可)。

路由注册:沿用 admin 路由模块,无需新 router 文件。

前端

职位管理 web-admin/src/views/system/post/index.vue

  • 操作列增加 「角色配置」(与现有「关联用户」并列)。
  • 弹窗:角色多选列表(el-select multiple 或勾选表格),加载全部启用角色 + 当前 roleIds,确定调用 PUT .../roles。
  • UI 对齐 Fresh Mint / 现有职位用户配置弹窗;按钮带图标。

用户管理 web-admin/src/views/system/user/index.vue

  • 新增/编辑表单:在「角色」旁增加 「职位」 多选(postIds),数据源 fetchPostList(或现有 posts API)。
  • 提交时带上 postIds;详情回填;查看弹窗展示职位名称。
  • 不移除用户直接绑角色。

API 模块

  • web-admin/src/api/ 增加 fetchPostRoleIds / fetchUpdatePostRoles(命名与 posts users API 对称)。

列表展示(可选但建议)

  • 职位列表可增加「关联角色数」或角色名摘要列(若实现成本低);非必须。
  • 首期不做「有效权限预览」。

文档与规范同步

实现时同步更新(最小必要):

  1. docs/sql/sys_post_role.sql(及视情况合并说明到基线 SQL 的维护方式与仓库惯例一致)。
  2. docs/中科兴皖需求V0.3.md §2.1.8:补充职位可关联角色;用户权限 = 直接角色 ∪ 职位角色。
  3. OpenSpec permission-conventions:多角色并集扩展为「直接角色 ∪ 经职位继承的角色」;菜单与数据权限均适用。

测试要点

  1. 用户仅绑职位、职位绑角色 A → 拥有角色 A 的菜单码与数据范围;可登录(非 guest)。
  2. 用户直接角色 B + 职位角色 A → 菜单/数据为 A∪B。
  3. 停用职位或角色后,对应继承立即失效。
  4. 职位角色配置全量替换;用户改 postIds 不影响 roleIds,反之亦然。
  5. super_admin 映射:若职位关联的角色名命中超级管理员规则,JWT/web role 应为 super_admin。
  6. OA 职位控件与审批流程不受影响。

风险与注意

  • 接入点遗漏:任何仍只 join SysUserRole 的权限路径都要改为有效角色;以 web_auth_roles + data_scope._resolve_user_role_scopes 为必改清单,实现前再 grep SysUserRole 防漏。
  • 性能:有效角色 ID 一次查出后复用;列表页高频 apply_data_scope 保持与现网同级查询次数(一次角色查询 + 明细)。
  • 与 OA 角色混淆:本设计仅扩展 sys_role;不触及 oa_role / oa_role_user。

实现顺序(建议)

  1. SQL + Model SysPostRole
  2. CRUD/API:GET/PUT posts/{id}/roles
  3. get_effective_role_ids + 改造 web_auth_roles / data_scope
  4. 前端职位「角色配置」
  5. 前端用户「职位」多选
  6. 文档 / OpenSpec 同步
  7. ./scripts/update-graph.sh backend(及改前端时 frontend)