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
| 文件 | 职责 |
|---|---|
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 |
并集语义扩展 |
Files:
docs/sql/sys_post_role.sqlModify: 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
Files:
backend/app/crud/post.pyModify: 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
Files:
backend/app/services/web_auth_roles.pybackend/app/core/data_scope.pyCreate: backend/tests/test_effective_roles.py(纯函数级:若难起库,可用 SQLAlchemy 内存或对查询逻辑做小型集成;最低限度用注释说明手动验收场景)
[ ] Step 1: 实现 get_effective_role_ids(db, user_id) -> list[str]
逻辑:
SysUserRole ⋈ SysRole(role status=1, del_flag=0)SysUserPost ⋈ SysPost(status=1) ⋈ SysPostRole ⋈ SysRole(status=1)[ ] 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。
cd backend && source venv/bin/activate && pytest tests/test_effective_roles.py tests/test_api_crypto.py -v
Files:
web-admin/src/api/admin.tsModify: 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
})
}
el-select multiple + fetchRoleList({ pageSize: 100 }) 加载选项;打开时 fetchPostBoundRoleIds;保存 fetchUpdatePostBoundRoles。el-icon(如 Key / Setting)。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 已有)。
Files:
docs/中科兴皖需求V0.3.md §2.1.8openspec/changes/zkxw-engineering-standards/specs/permission-conventions/spec.mdRun: ./scripts/update-graph.sh backend 与 frontend(若改了前端)
[ ] Step 1: 需求补充:职位可关联角色;用户菜单/数据权限 = 直接角色 ∪ 职位角色。
[ ] Step 2: OpenSpec 在「多角色权限合并」下增加职位继承 Scenario。
[ ] Step 3: 更新 graphify。
| 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。