状态:待用户确认(未开始编码) 创建日期:2026-08-14 关联:基于 v2.0 技术方案,叠加多角色与权限工作流
| # | 歧义点 | 我的默认解读(如不符请告知) |
|---|---|---|
| Q1 | "本部门管理的所有文档"——文档当前不直接挂部门,只有 category_id 与 created_by |
默认引入"文档所属部门 = 创建者所在部门"语义;同时保留文档级 ACL(doc_permission)覆盖细粒度授权 |
| Q2 | 部门管理员能否管理子部门文档?(如机关 → 作战部,作战部管理员能否看机关文档?反之呢?) | 默认 部门管理员 = 所管理部门及其所有后代部门(与现有"组织祖先链"语义一致)。部门管理员不能管理祖先部门 |
| Q3 | "权限申请"流程:用户向谁申请?申请什么? | 默认指普通用户向上级/管理员申请具体文档或文档分类的额外权限,由部门管理员/超管审批;审批通过后落 doc_permission 表 |
| Q4 | 部门管理员之间是否平级?一个部门能否有多个管理员? | 默认一对多(一个部门可有多位管理员,权限等价),与现有"用户 × 组织"模型契合 |
| Q5 | "细粒度权限划分"的粒度:模块级 / 文档级 / 字段级? | 默认文档级(沿用现有 doc_permission 表的 5 个 canXxx),不引入字段级 |
| Q6 | 部门管理员能否创建/删除本部门用户? | 默认可以管理本部门及下级部门的用户(增删改、重置密码),但不能改其他部门用户 |
| Q7 | 部门管理员能否创建子部门? | 默认不能(组织结构由超管维护),仅可对所辖部门的"文档/用户/权限"做管理 |
⚠️ 请逐项确认("Q1✓ Q2 改为..."等),歧义点会影响后续表结构与 API 设计。
role_code 列,新增"部门管理员授权表"为什么不引入完整 RBAC(Role/Permission/RolePermission/UserRole 多对多)?
RoleCode 是写死枚举、allowedModules 静态映射,已被 JWT、装饰器、前端门控、f6Scope 静态测试等多处依赖;改为多对多会引发大爆炸。doc_permission 完整覆盖,不需要再做"角色 × 权限点矩阵"。结论:在 sys_user.role_code 枚举中加一档 DEPT_ADMIN,用一张 sys_dept_admin(用户 × 组织)关联表表达"谁管哪些部门"。
organization_id 列Document 当前没有组织归属字段,要实现"本部门文档"必须新增:
doc_document.organization_id:默认取创建者所属组织(上传时自动填充)created_by → user.organization_id 推导| 角色 | 文档范围 | 用户管理范围 |
|---|---|---|
SUPER_ADMIN(即 ADMIN) |
全部 | 全部 |
| DEPT_ADMIN | 所管理部门及后代的文档 + 显式 ACL 授权 | 所管理部门及后代的用户 |
| USER | 仅显式 ACL 授权 + visibility_type=ALL_AUTHENTICATED |
仅自己 |
"本部门文档"对普通用户可见 = 文档
organization_id∈ 用户所属组织的祖先链时,普通用户享有 VIEW 权限。
普通用户/部门管理员 ──提交申请──► 待审批
│ │
│ ├── 部门管理员审批(仅本部门申请)
│ └── 超级管理员审批(任意申请)
│
▼
通过 → 落 doc_permission 表
驳回 → 标记驳回理由
申请单要素:申请人、目标文档(或分类)、申请动作(view/download/edit 等)、申请理由、审批人、状态、审批意见。
| 维度 | 现状 |
|---|---|
| 组织树 | Organization 已是层级模型(parent_id + org_path),CRUD + 软删 + 乐观锁齐全,已有"祖先链"和"后代遍历"工具函数 |
| 用户生命周期 | 建改删/重置密码/禁用/自我保护、最后管理员保护、auth_version 主动失效机制完备 |
| 二角色体系 | RoleCode.USER / RoleCode.ADMIN(backend/dms/common/enums.py:8-10),CHECK 约束(backend/dms/models/user.py:18-21) |
| 文档级 ACL | doc_permission 表 + evaluate_plan_access 统一判定入口(backend/dms/services/authorization_service.py:101-158)+ 全量差异保存 + 子方案动态继承主案 + 附件通过主案传递 |
| 三种可见性策略 | ALL_AUTHENTICATED / ORGANIZATION / CUSTOM(backend/dms/models/document.py:149-153) |
| 审计基础设施 | 22 种动作 + 8 种目标类型,business_audit 辅助函数统一落表 |
role_code CHECK 限制为 ('USER', 'ADMIN')Document 无组织归属字段:无法表达"本部门文档"ASSIGN_DEPT_ADMIN / GRANT_DEPT_ADMIN 等动作,需扩展 CHECK(0004 已有先例)| 维度 | 现状 |
|---|---|
| 认证 | 完整的登录/退出/会话恢复,tokenStore 区分 sessionStorage/localStorage |
| 组织管理 UI | 完整 CRUD(树形、关键词、排序、状态、parent 校验防环)DirectoryPage.tsx:217-487 |
| 用户管理 UI | 完整 CRUD + 重置密码 + 组织树筛选(含下级)+ 分页 DirectoryPage.tsx:763-1271 |
| 文档级 ACL UI | 完整的 F6PermissionModal:三档 visibility + 组织/人员主体 + 5 个布尔动作 + 子案继承 |
| 模块级导航门控 | allowedModules 驱动导航条 + isPageAllowed 拦截跳转 |
| 角色枚举 | 仅 USER / ADMIN 两档(frontend/src/shared/api/enums.ts:1-5) |
RoleCode 仅 USER/ADMINOrganizationNode 无 managerId、UserSummary 无 managedOrgIdsfeatures/requests/ 或 features/approvals/App.tsx:2986-3014 三处 roleCode === RoleCode.ADMIN 硬编码allowedModules、路由按 roleCode,必须同步f6Scope.test.ts:23 静态断言脆弱:用 readFileSync 做字符串匹配,新增角色门控时需更新0006_rbac_dept_admin新建文件:backend/migrations/versions/0006_rbac_dept_admin.py
变更项:
sys_user.role_code CHECK 扩展为 ('USER', 'ADMIN', 'DEPT_ADMIN')新建 sys_dept_admin 表:
CREATE TABLE sys_dept_admin (
id BIGINT PK,
user_id BIGINT NOT NULL, -- FK sys_user
organization_id BIGINT NOT NULL, -- FK sys_organization
granted_by BIGINT NOT NULL, -- FK sys_user (授予人)
granted_at DATETIME NOT NULL,
row_version INT NOT NULL DEFAULT 0,
created_at/updated_at/created_by/updated_by + is_deleted/deleted_at (mixin),
UNIQUE(user_id, organization_id, active_marker),
CHECK (user_id <> organization_id) -- 防呆
)
doc_document 加列 organization_id BIGINT NULL + 冗余 organization_name + 索引 ix_doc_document_org_active
sys_audit_log.action_type CHECK 扩展:ASSIGN_DEPT_ADMIN / REVOKE_DEPT_ADMIN / SUBMIT_ACCESS_REQUEST / APPROVE_ACCESS_REQUEST / REJECT_ACCESS_REQUEST
sys_audit_log.target_type CHECK 扩展:DEPT_ADMIN_GRANT / ACCESS_REQUEST
新建 doc_access_request 表(权限申请单):
CREATE TABLE doc_access_request (
id BIGINT PK,
requester_id BIGINT NOT NULL, -- FK sys_user
document_id BIGINT NULL, -- 申请具体文档
category_id BIGINT NULL, -- 申请某分类下所有文档(与 document_id 互斥)
requested_actions VARCHAR(64) NOT NULL, -- "VIEW,DOWNLOAD" 逗号分隔
reason TEXT NOT NULL,
status VARCHAR(16) NOT NULL, -- PENDING/APPROVED/REJECTED/CANCELLED
approver_id BIGINT NULL,
approver_comment TEXT NULL,
approved_actions VARCHAR(64) NULL, -- 审批通过的实际动作(可缩减)
submitted_at DATETIME NOT NULL,
decided_at DATETIME NULL,
row_version INT NOT NULL DEFAULT 0,
... (mixin)
CHECK (status IN ('PENDING','APPROVED','REJECTED','CANCELLED')),
CHECK ((document_id IS NULL) <> (category_id IS NULL)) -- 二选一
)
历史数据回填:UPDATE doc_document d JOIN sys_user u ON u.id = d.created_by SET d.organization_id = u.organization_id WHERE d.organization_id IS NULL
验证:alembic upgrade head + 回滚脚本 downgrade() 完整。
复杂度:中(约束较多但都是 ADD COLUMN / CREATE TABLE)
新建 backend/dms/models/dept_admin.py:
DeptAdminGrant ORM 模型(继承 BusinessTableMixin)新建 backend/dms/models/access_request.py:
AccessRequest ORM 模型,含 requested_actions / approved_actions 的 list 序列化辅助方法修改 backend/dms/models/document.py:
organization_id + organization_name 列 + relationship修改 backend/dms/models/user.py + backend/dms/common/enums.py:8-10:
RoleCode 加 DEPT_ADMIN新建 backend/dms/services/dept_admin_service.py:
list_grants(user_id=None, org_id=None) — 查询授权grant(user_id, org_id, granted_by) — 新增授权(含自环检测、组织存在性、用户角色升级到 DEPT_ADMIN)revoke(grant_id, row_version, revoked_by) — 撤销授权(撤销后若用户无其他授权则降级回 USER)managed_org_ids(user_id) — 返回用户管理的全部 org_id(含后代)is_dept_admin_of(user_id, org_id) — 判定新建 backend/dms/services/access_request_service.py:
submit(requester_id, payload) — 提交申请(不允许申请自己已有权限的)list_for_approver(approver_id, status, page) — 待我审批列表(按数据范围过滤)list_for_requester(requester_id, status, page) — 我的申请approve(request_id, approver_id, payload) — 通过(落 doc_permission,发审计)reject(request_id, approver_id, payload) — 驳回修改 backend/dms/services/authorization_service.py(核心改造):
evaluate_plan_access 新增分支:
organization_id ∈ 用户的 managed_org_ids(含后代)→ 放行organization_id == 用户 organization_id 且 status=PUBLISHED → VIEW 放行("本部门文档默认可见")plan_allowed_actions 同步扩展返回值修改 backend/dms/services/document_query_service.py:318:
list_documents 增加 DEPT_ADMIN 分支:WHERE doc.organization_id IN (managed_org_ids ∪ descendants)修改 backend/dms/services/user_service.py:142:
list_users 增加 DEPT_ADMIN 分支:仅能查询所辖部门的用户修改 backend/dms/services/user_service.py:35-43:
ALLOWED_MODULES 字典:DEPT_ADMIN 看到 DOCUMENT_BROWSER / ORGANIZATION_USER_MANAGEMENT / AUDIT_LOG(不含 BACKEND_MANAGEMENT)修改 backend/dms/services/document_mutation_service.py:
organization_id = creator.organization_id复杂度:高(authorization_service 是核心路径,改造需配套单测)
新建 backend/dms/api/v1/dept_admin.py:
GET /api/v1/dept-admins?userId=&orgId= — 列表(仅 ADMIN)POST /api/v1/dept-admins — 授予(仅 ADMIN)DELETE /api/v1/dept-admins/<id>?rowVersion= — 撤销(仅 ADMIN)新建 backend/dms/api/v1/access_requests.py:
POST /api/v1/access-requests — 提交(任意已认证用户)GET /api/v1/access-requests/mine?status=&page= — 我的申请GET /api/v1/access-requests/pending?page= — 待我审批POST /api/v1/access-requests/<id>/approve — 通过POST /api/v1/access-requests/<id>/reject — 驳回POST /api/v1/access-requests/<id>/cancel — 取消(仅本人 + PENDING)修改 backend/dms/security/decorators.py:
@dept_admin_or_admin_required 装饰器@require_data_scope(resource_type) 检查修改 backend/dms/api/v1/organizations.py 与 users.py:
@admin_required 调整为:列表查询允许 DEPT_ADMIN 但结果按 scope 过滤;写操作仍限 ADMIN修改 backend/dms/api/v1/users.py:68-77 /users/me:
user_summary 中增加 managedOrgIds: Id[] 字段(仅 DEPT_ADMIN 非空)修改 backend/dms/security/jwt_tokens.py:
managedOrgIds(避免每次请求都查表),或仅在 /me 返回复杂度:中
修改 frontend/src/shared/api/enums.ts:
RoleCode 加 DEPT_ADMINAccessRequestStatus(PENDING/APPROVED/REJECTED/CANCELLED)AuditActionType 加 5 项(前端枚举与后端同步)修改 frontend/src/features/auth/authTypes.ts:
UserSummary 加 managedOrgIds: Id[](默认空数组)修改 frontend/src/features/auth/authApi.test.ts + authSession.test.ts + directoryApi.test.ts:
managedOrgIds: []新建 frontend/src/features/dept-admin/deptAdminTypes.ts:
DeptAdminGrant / DeptAdminGrantListQuery / DeptAdminGrantRequest新建 frontend/src/features/dept-admin/deptAdminApi.ts:
新建 frontend/src/features/access-requests/requestTypes.ts:
AccessRequest / AccessRequestCreateRequest / AccessRequestApproveRequest / AccessRequestStatus新建 frontend/src/features/access-requests/requestApi.ts:
修改 frontend/src/features/auth/authSession.ts:
AuthenticatedPage 加 "requests"AllowedModule 加 ACCESS_REQUESTMODULE_PAGES / MODULE_ORDER 同步映射复杂度:中
修改 frontend/src/app/App.tsx:2986-3014:
roleCode === RoleCode.ADMIN 抽象为 <RequireRole roles={[ADMIN, DEPT_ADMIN]}> 组件f6Scope.test.ts:23 静态断言(改为允许 DEPT_ADMIN 但仍禁止与 BIND_ATTACHMENT 共现)修改 frontend/src/features/directory/DirectoryPage.tsx:
Object.values(RoleCode) 改为字典枚举 + 中文标签修改 frontend/src/features/directory/DirectoryPage.tsx:1430-1435(UserEditorModal):
organizationId 下拉限制为所辖部门新建 frontend/src/features/access-requests/RequestsPage.tsx:
新建 frontend/src/features/access-requests/RequestEditorModal.tsx:
修改 frontend/src/app/App.tsx 路由:
{page === "requests" && <RequestsPage .../>}修改 frontend/src/shared/api/enums.ts:60-69 AllowedAction保持不变(申请单用同一套动作)
复杂度:高(DirectoryPage 1700 行,需小心改造;新增 RequestsPage 全新页面)
后端:
backend/tests/dms/test_dept_admin.py — 授权/撤销/角色升级降级/自环backend/tests/dms/test_access_request_workflow.py — 提交/审批/落表/状态机backend/tests/dms/test_authorization_dept_admin.py — 数据范围三层模型backend/tests/dms/test_user_scope_dept_admin.py — 用户列表 scope 过滤前端:
deptAdminApi.test.ts / requestApi.test.ts — 类型守卫 + 端点契约directoryTypes.test.ts / authSession.test.ts — 已有测试补字段appScope.test.ts 扩展断言:requests 页面门控按 RBAC 三档角色手动验收(用户参与):
复杂度:中
| 风险 | 等级 | 缓解 |
|---|---|---|
R1: evaluate_plan_access 改造引入性能回归(每条文档都查 managed_org_ids) |
高 | 在 dept_admin_service 加内存缓存(请求级),或把 managed_org_ids 拍平成 IN 子句一次查出 |
| R2: 文档归属语义变更影响历史数据 | 高 | 迁移脚本必须回填 organization_id;回填逻辑:SELECT d.id, u.organization_id FROM doc_document d JOIN sys_user u ON u.id = d.created_by WHERE d.organization_id IS NULL |
R3: 普通用户"本部门可见"语义与现有 visibility_type=ORGANIZATION 冲突(语义重叠) |
中 | 保留 visibility_type 做显式授权;新增的"本部门默认可见"独立判定,两者取并集。文档落库时统一两者语义 |
| R4: JWT claims 加 managedOrgIds 后撤销授权无法实时生效 | 中 | 撤销时 User.auth_version += 1 强制 token 失效(已有机制) |
| R5: 前端 f6Scope.test.ts:23 静态断言脆弱 | 中 | 同步更新断言内容,增加 DEPT_ADMIN 白名单 |
| R6: 已有 mock 数据 265 个测试用例可能因类型新增字段大面积破坏 | 中 | 阶段 2 完成后立即跑全量测试,预期破坏点 5-8 处,按文件批量补字段 |
| R7: 部门管理员与"挂载附件 / 配置文档权限"功能交叉 | 中 | DEPT_ADMIN 在所辖范围内享有 BIND_ATTACHMENT / CONFIG_PERMISSION(沿用文档级 allowed_actions 动态计算,无需新增字段) |
R8: App.tsx 3000+ 行 + DirectoryPage 1700 行,改造易引入回归 |
中 | 拆分 <RequireRole> 抽象 + 不动既有功能分支,仅扩展角色判定集 |
| 阶段 | 后端 | 前端 | 测试 | 合计 |
|---|---|---|---|---|
| 1. 迁移 | 2h | - | 0.5h | 2.5h |
| 2. 模型与服务 | 5h | - | 2h | 7h |
| 3. API | 3h | - | 1h | 4h |
| 4. 前端类型与 API | - | 2h | 1h | 3h |
| 5. 前端页面 | - | 6h | 1.5h | 7.5h |
| 6. 测试与验收 | - | - | 3h | 3h |
| 总计 | 10h | 8h | 9h | 27h |
整体复杂度:高(核心授权路径改造 + 新增完整工作流)
请逐项回复,确认后才开始编码:
role_code 列 + 新增 sys_dept_admin 关联表"还是希望走完整 RBAC(自定义角色 + 权限点矩阵)?App.tsx 拆分:是否同意在改造同时把 <RequireRole> 抽出来(小幅重构)?还是希望本次保持最小改动不重构?| 路径 | 说明 |
|---|---|
backend/dms/common/enums.py:8-10 |
RoleCode 枚举(加 DEPT_ADMIN) |
backend/dms/common/enums.py:13-17 |
AllowedModule 静态枚举 |
backend/dms/common/enums.py:68-75 |
AllowedAction 文档级动作 |
backend/dms/models/user.py:15-78 |
User 模型 |
backend/dms/models/organization.py:22-65 |
Organization 模型 |
backend/dms/models/document.py:25-215 |
Document 模型(加 organization_id) |
backend/dms/models/permission.py:23-87 |
doc_permission 表 |
backend/dms/services/authorization_service.py:101-158 |
evaluate_plan_access 核心授权逻辑 |
backend/dms/services/authorization_service.py:161-217 |
plan_allowed_actions / attachment_allowed_actions |
backend/dms/services/document_query_service.py:318-389 |
list_documents Python 层过滤 |
backend/dms/services/user_service.py:35-43 |
ALLOWED_MODULES 字典 |
backend/dms/services/user_service.py:142-181 |
list_users |
backend/dms/services/permission_service.py:286-420 |
save_permissions 全量差异保存 |
backend/dms/api/v1/users.py:68-77 |
/users/me 端点 |
backend/dms/api/v1/organizations.py |
组织端点 |
backend/dms/security/decorators.py:77-84 |
admin_required 装饰器 |
backend/dms/security/jwt_tokens.py:34-43 |
issue_access_token claims |
backend/migrations/versions/0005_category_flat_mode.py |
最新迁移版本(下一版应为 0006) |
| 路径 | 说明 |
|---|---|
frontend/src/shared/api/enums.ts:1-5 |
RoleCode |
frontend/src/shared/api/enums.ts:7-13 |
AllowedModule |
frontend/src/shared/api/enums.ts:60-69 |
AllowedAction |
frontend/src/features/auth/authTypes.ts:21-35 |
UserSummary |
frontend/src/features/auth/authSession.ts:9-55 |
AuthenticatedPage / MODULE_PAGES / getNavigationItems |
frontend/src/features/auth/__tests__/authApi.test.ts:10-28 |
user mock(需补字段) |
frontend/src/features/auth/__tests__/authSession.test.ts:29-43 |
user mock(需补字段) |
frontend/src/features/directory/directoryTypes.ts:13-23 |
OrganizationNode |
frontend/src/features/directory/DirectoryPage.tsx:113-115 |
角色下拉硬编码(需扩展) |
frontend/src/features/directory/DirectoryPage.tsx:1430-1435 |
UserEditorModal 角色选择 |
frontend/src/features/permissions/permissionTypes.ts:9-44 |
文档级 ACL 类型 |
frontend/src/features/permissions/PermissionModal.tsx:138-533 |
F6 文档权限弹窗 |
frontend/src/app/App.tsx:107 |
Page 类型 |
frontend/src/app/App.tsx:2986-3014 |
角色门控三处(需抽象) |
frontend/src/features/permissions/__tests__/f6Scope.test.ts:23 |
静态断言(需更新) |
文档结束。等待用户确认后再启动阶段 1 编码。