2026-08-02-post-role-permission.md 8.7 KB

职位关联角色(权限并集)Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 职位可关联角色;用户权限 = 直接角色 ∪ 职位继承角色(菜单 + 数据权限);职位管理可配置角色,用户管理可多选职位。

Architecture: 新增 sys_post_role;抽 get_effective_role_ids 供 web_auth_roles 与 data_scope 共用;API 对齐现有 GET/PUT /posts/{id}/users;前端职位页「角色配置」、用户页补 postIds。

Tech Stack: FastAPI + SQLAlchemy + MySQL;Vue 3 + Element Plus;OpenSpec permission-conventions。

Spec: docs/superpowers/specs/2026-08-02-post-role-permission-design.md


File map

文件 职责
docs/sql/sys_post_role.sql 建表
backend/app/models/models.py SysPostRole
backend/app/crud/post.py 职位角色 list/sync;软删职位时软删关联
backend/app/services/web_auth_roles.py get_effective_role_ids + 改造菜单/角色名
backend/app/core/data_scope.py _resolve_user_role_scopes 用有效角色
backend/app/api/admin.py GET/PUT /posts/{post_id}/roles
backend/tests/test_effective_roles.py 有效角色解析单测(可 mock Session 或轻量集成)
web-admin/src/api/admin.ts fetchPostBoundRoleIds / fetchUpdatePostBoundRoles
web-admin/src/views/system/post/index.vue 「角色配置」弹窗
web-admin/src/views/system/user/index.vue 职位多选 + 查看展示
docs/中科兴皖需求V0.3.md §2.1.8 需求补充
openspec/.../permission-conventions/spec.md 并集语义扩展

Task 1: SQL + Model

Files:

  • Create: docs/sql/sys_post_role.sql
  • Modify: backend/app/models/models.py(在 SysUserPost 后插入 SysPostRole)

  • [ ] Step 1: 写增量 SQL

-- docs/sql/sys_post_role.sql
CREATE TABLE IF NOT EXISTS `sys_post_role` (
  `id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID',
  `post_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位ID',
  `role_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色ID',
  `tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
  `order_num` int(11) DEFAULT NULL COMMENT '排序号',
  `remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人id',
  `create_dept_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '创建人部门ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人id',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_spr_post` (`tenant_no`,`post_id`,`del_flag`) USING BTREE,
  KEY `idx_spr_role` (`tenant_no`,`role_id`,`del_flag`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职位-角色关联';
  • [ ] Step 2: 增加 SQLAlchemy 模型(字段与 SysUserPost 同风格,表名 sys_post_role,字段 post_id/role_id)

  • [ ] Step 3: 本地执行 SQL(对当前开发库)

# 使用项目既有 DB 连接方式执行 docs/sql/sys_post_role.sql

Task 2: 职位角色 CRUD

Files:

  • Modify: backend/app/crud/post.py
  • Modify: backend/app/api/admin.py(紧挨 posts users 路由之后)

  • [ ] Step 1: 在 post.py 增加

def list_role_ids_for_post(db, post_id: str, tenant_no: str) -> List[str]:
    ...

def sync_post_role_bindings(db, post_id, tenant_no, role_ids, updater_id) -> None:
    """全量替换:目标外软删,目标内恢复或新建(对齐 user 侧 set_user_* 模式)。"""
    ...

校验:每个 role_id 存在、同租户、del_flag=0(可选要求 status=='1' 才允许绑定——推荐允许绑停用角色但运行时不算有效,与用户绑停用角色一致即可)。

  • [ ] Step 2: soft_delete_post 时同步软删该职位下 SysPostRole(del_flag=0 → 1)

  • [ ] Step 3: API

class PostRolesUpdateBody(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    role_ids: List[str] = Field(default_factory=list, alias="roleIds")

@router.get("/posts/{post_id}/roles")
# require_menu_code("system:post:list") → {"roleIds": [...]}

@router.put("/posts/{post_id}/roles")
# require_admin_or_super_admin → sync_post_role_bindings

Task 3: 有效角色解析

Files:

  • Modify: backend/app/services/web_auth_roles.py
  • Modify: backend/app/core/data_scope.py
  • Create: backend/tests/test_effective_roles.py(纯函数级:若难起库,可用 SQLAlchemy 内存或对查询逻辑做小型集成;最低限度用注释说明手动验收场景)

  • [ ] Step 1: 实现 get_effective_role_ids(db, user_id) -> list[str]

逻辑:

  1. 直接:SysUserRole ⋈ SysRole(role status=1, del_flag=0)
  2. 职位:SysUserPost ⋈ SysPost(status=1) ⋈ SysPostRole ⋈ SysRole(status=1)
  3. 去重保序
  • [ ] Step 2: 改造 _active_role_name_rows / iter_user_menu_perm_codes
    用有效 role_id 查 SysRole.role_name / SysRoleMenu→SysMenu,不再仅 join SysUserRole。

  • [ ] Step 3: 改造 data_scope._resolve_user_role_scopes
    from app.services.web_auth_roles import get_effective_role_ids,按 ids 查 SysRole 与 detail。

  • [ ] Step 4: grep 防漏

rg -n "SysUserRole" backend/app --glob '*.py'

凡「解析当前用户权限」路径必须改为有效角色;配置管理类(给角色绑用户等)保持只动 sys_user_role。

  • Step 5: 跑测试
cd backend && source venv/bin/activate && pytest tests/test_effective_roles.py tests/test_api_crypto.py -v

Task 4: 前端 API + 职位「角色配置」

Files:

  • Modify: web-admin/src/api/admin.ts
  • Modify: web-admin/src/views/system/post/index.vue

  • [ ] Step 1: API

export function fetchPostBoundRoleIds(postId: string) {
  return request.get<{ roleIds: string[] }>({ url: `/web/admin/posts/${postId}/roles` })
}
export function fetchUpdatePostBoundRoles(postId: string, roleIds: string[]) {
  return request.put({
    url: `/web/admin/posts/${postId}/roles`,
    params: { roleIds },
    showSuccessMessage: true
  })
}
  • Step 2: 职位页
    操作列在「关联用户」旁加「角色配置」;弹窗用 el-select multiple + fetchRoleList({ pageSize: 100 }) 加载选项;打开时 fetchPostBoundRoleIds;保存 fetchUpdatePostBoundRoles。
    查看详情可增加「已关联角色」字段(可选)。
    按钮须带 el-icon(如 Key / Setting)。

Task 5: 用户管理职位多选

Files:

  • Modify: web-admin/src/views/system/user/index.vue

  • [ ] Step 1: formData 增加 postIds: string[];表单项「职位」多选(fetchPostList);详情回填 detail.postIds;submit 带 postIds;查看 sections 展示职位名(可用 postOptions 映射)。

  • [ ] Step 2: 确认后端 create/update 已传 postIds(admin.py UserCreate/UpdateBody 已有)。


Task 6: 文档与图谱

Files:

  • Modify: docs/中科兴皖需求V0.3.md §2.1.8
  • Modify: openspec/changes/zkxw-engineering-standards/specs/permission-conventions/spec.md
  • Run: ./scripts/update-graph.sh backend 与 frontend(若改了前端)

  • [ ] Step 1: 需求补充:职位可关联角色;用户菜单/数据权限 = 直接角色 ∪ 职位角色。

  • [ ] Step 2: OpenSpec 在「多角色权限合并」下增加职位继承 Scenario。

  • [ ] Step 3: 更新 graphify。


Task 7: 手工验收清单

  • 仅职位绑角色、无直接角色 → 侧栏有菜单、非 guest
  • 直接角色 + 职位角色 → 菜单并集
  • 停用职位 → 继承失效
  • 用户改职位不影响直接角色;职位改角色不影响用户直接角色列表
  • OA 职位控件仍可用

Spec coverage check

Spec 项 Task
sys_post_role T1
GET/PUT roles API T2
get_effective_role_ids + web_auth + data_scope T3
职位角色配置 UI T4
用户 postIds UI T5
文档/OpenSpec T6
停用语义 / 并集 T3 + T7
非目标 OA 不改

Commits: 按用户要求再提交;实现过程默认不自动 commit。