# 用户权限开发 TaskList > 目标:在 `leaudit-platform` 里,基于当前真实业务落地一套 **单地区隔离 + 角色权限 + 数据范围控制** 的用户体系,并能把老系统用户数据平滑迁移过来。 这份文档不是泛泛设计稿,而是后续开发的执行清单。 --- ## 1. 当前结论先说清楚 当前这条线,应该按下面原则做,不要再发散: - 用户地区只认 `sso_users.area` - 数据隔离只做单地区,不做多地区授权 - 当前业务角色只保留: - `provincial_admin` - `admin` - `common` - `super_admin` 只作为可选系统维护角色 - 数据范围只保留: - `ALL` - `DEPT` - `SELF` - 新系统要兼容老系统已有数据结构,不要为了“理论优雅”把老数据迁移难度抬高 --- ## 2. 当前新系统现状盘点 ## 2.0 本次实际检查结果(2026-04-29) 我已经实际检查了当前 `leaudit_platform` 新库和老库 `docauditai`,结论如下: - 新库 `leaudit_platform` 原先 **完全没有** 这 7 张核心 RBAC 表: - `sso_users` - `roles` - `user_role` - `permissions` - `role_permissions` - `sys_routes` - `role_route` - 新库现阶段只有 `leaudit_*` 业务表,所以原有认证/权限代码其实处于“代码已写、库表未落地”的状态 - 我已经补了可执行 SQL: - `scripts/user_rbac_schema_patch.sql` - `scripts/user_rbac_seed.sql` - `scripts/user_rbac_migration_audit.sql` - 我已经把 `schema_patch + seed` 执行进当前新库 - 当前新库 RBAC 初始化结果: - `roles = 4` - `permissions = 30` - `role_permissions = 90` - `sys_routes = 9` - `role_route = 30` - `sso_users = 0` - `user_role = 0` 老库 `docauditai` 的迁移审计关键结果: - `sso_users = 4106` - `roles = 3` - `user_role = 11` - `permissions = 133` - `role_permissions = 222` - `sys_routes = 34` - `role_route = 95` - 老库 **没有空地区、没有重复 sub、没有重复 username** - 老库 **4106 个用户里有 4098 个没有角色** - 老库真实角色分布非常集中: - `common = 6` - `admin = 4` - `provincial_admin = 1` 这说明: - 老系统用户登录主数据是可迁的 - 但角色并不是“每个用户都显式分配了 user_role” - 新系统迁移时必须补一轮“默认角色落地策略”,不能只机械复制 `user_role` ## 2.1 已有能力 当前新系统已经有这些基础: - 已有 `sso_users` 登录查询逻辑 - 已有 JWT 签发与鉴权 - 已有 `user_role / roles / role_permissions / permissions` 的查询代码雏形 - 已有受保护路由统一挂载 JWT 依赖:`fastapi_modules/fastapi_leaudit/controllers/__init__.py` - 已有认证入口:`/auth/login`、`/auth/password_login` ## 2.2 当前实际代码路径 当前这条线的核心代码在: - `fastapi_modules/fastapi_leaudit/services/impl/authServiceImpl.py` - `fastapi_modules/fastapi_leaudit/services/impl/permissionServiceImpl.py` - `fastapi_common/fastapi_common_security/jwtService.py` - `fastapi_common/fastapi_common_security/security.py` - `fastapi_modules/fastapi_leaudit/controllers/auth/authController.py` ## 2.3 当前最关键的问题 ### 问题 1:权限设计文档和实际代码字段名不一致 当前代码查的是: - `roles.role_key` - `permissions.permission_key` - `role_permissions.grant_type` 但我前面新写的初始化 SQL 用的是: - `roles.role_name` - `permissions.permission_code` 这说明: - **现阶段不能直接执行那份初始化 SQL 到现有库** - 必须先按当前实际数据库字段重新收一版正式建表/初始化方案 ### 问题 2:登录后只取“一个角色” 当前 `authServiceImpl.py` 里: ```python SELECT r.role_key ... LIMIT 1 ``` 这意味着: - 多角色用户会被截断成单角色 - JWT 中的 `roles` 没真正使用起来 - 后续 `ALL > DEPT > SELF` 的数据范围收敛逻辑无法成立 ### 问题 3:JWT 里没有真正放入完整权限集 当前 JWT 虽然支持 `roles` 字段,但登录时只传了: - `userRole` - `area` 没有把: - 全量角色列表 - 权限点列表 - 最终数据范围摘要 稳定打进去。 这会导致: - `/me` 接口还不完整 - 前端拿不到统一用户权限视图 - 后端每次都要重新查库,且现有逻辑并未闭环 ### 问题 4:当前没有统一的数据范围注入层 现在只是“能查角色 / 权限”,但还没有真正统一实现: - 文档列表自动按 `area` 过滤 - 用户列表自动按 `area` 过滤 - 评查 run / result 自动按文档归属校验 也就是说: - **功能权限雏形有了,数据权限还没有真正落地** ### 问题 5:OAuth 登录仍然可能把 area 当外部输入覆盖 当前 OAuth 登录里: - `Area=requestData.get("area")` - 然后直接 `UPDATE sso_users SET ... area = :area` 这是很危险的。 因为这意味着: - 前端或调用方传什么 area,就可能把用户地区改掉 - 数据隔离基础字段可能被外部请求污染 后续必须改成: - area 只能来自后台可信源 - 登录请求不能直接覆盖 area ### 问题 6:新用户自动创建后没有默认角色闭环 当前 OAuth 首登自动插入 `sso_users`,但代码里没有看到紧接着分配默认 `common` 角色的闭环。 风险: - 新用户建出来了,但没有角色 - 没角色则权限查询为空 - 前端表现为“登录成功但啥也看不到” ### 问题 7:密码仍然是明文比对 当前 `PasswordLogin()` 里: - 直接 `user.get("password") != Password` 这是明显安全风险。 即使当前先沿用旧系统,也至少要在 tasklist 里明确: - 第一阶段兼容旧明文 - 第二阶段升级为哈希存储与校验 ### 问题 8:缺少统一的 `/api/auth/me` 当前文档里已经定义了 `/api/auth/me`,但现有 controller 里还没有真正落地。 这会影响: - 前端初始化用户态 - 角色/权限/地区统一回显 - 登录后刷新恢复 --- ## 3. 老系统可迁移数据结论 基于老项目 `docauditai` 的数据库快照和代码分析,当前可直接继承的核心表是: - `sso_users` - `roles` - `user_role` - `permissions` - `role_permissions` - `sys_routes` - `role_route` ## 3.1 老系统 `sso_users` 关键字段 老系统 `sso_users` 目前至少包含: - `id` - `sub` - `username` - `nick_name` - `phone_number` - `email` - `ou_id` - `ou_name` - `status` - `is_leader` - `password` - `try_count` - `try_login_time` - `area` - `tenant_name` - `dep_short_name` - `dep_name` - `mq_person_uuid` - `mq_account_uuid` - `mq_synced_at` - `created_at` - `updated_at` - `deleted_at` ### 迁移判断 这说明: - 新系统**完全没必要重新发明用户主表** - 最优方案是: - 继续沿用 `sso_users` - 补必要索引/约束/注释 - 修正其业务语义 ## 3.2 老系统 `roles` 关键字段 老系统 `roles` 目前至少包含: - `id` - `role_key` - `role_name` - `data_scope` - `description` - `parent_role_id` - `priority` - `is_system_role` - `permissions_cache` - `metadata` - `created_at` - `updated_at` ### 迁移判断 建议: - 保留 `role_key` 作为机器标识 - 保留 `role_name` 作为中文/展示名 - 继续使用 `data_scope` - `parent_role_id / permissions_cache / metadata` 可以先保留但不作为第一阶段核心依赖 ## 3.3 老系统 `permissions` 关键字段 老系统 `permissions` 目前至少包含: - `id` - `permission_key` - `module` - `resource` - `action` - `description` - `display_name` - `permission_type` - `is_system` - `metadata` - `parent_id` - `sort_order` - `route_id` - `api_path` - `api_method` - `created_at` - `updated_at` ### 迁移判断 建议: - 不要再新造 `permission_code` - 直接沿用 `permission_key` - 后续新系统全部按 `permission_key` 统一 ## 3.4 老系统 `role_permissions` 关键字段 老系统 `role_permissions` 目前至少包含: - `role_id` - `permission_id` - `grant_type` - `data_scope` - `condition_filter` - `metadata` - `created_at` - `updated_at` ### 迁移判断 建议: - 继续保留 `grant_type` - 第一阶段只用 `GRANT` - `DENY` 能力先兼容不扩展 - `condition_filter` 暂不作为第一阶段核心能力 --- ## 4. 最终推荐表结构策略 这里不是从零建库,而是: - **以老系统实际表结构为准** - 对新系统补齐缺失字段、索引、语义约束 - 避免再造第二套平行用户权限模型 ## 4.1 `sso_users` 目标策略 ### 保留字段 - 保留老系统现有字段不动 - 核心业务字段继续认:`area` ### 必须加的约束 / 索引 建议检查并补: - `UNIQUE(sub)` - `INDEX(area)` - `INDEX(status)` - `INDEX(deleted_at)` - `INDEX(username)` ### 字段语义正式化 - `sub`:统一身份唯一标识 - `username`:登录名 / 工号 / 展示账号 - `nick_name`:真实姓名 - `area`:用户主地区,也是默认数据隔离依据 - `password`:短期兼容旧值,后续迁移为哈希 ## 4.2 `roles` 目标策略 继续沿用: - `role_key`:机器标识,例如 `provincial_admin` - `role_name`:中文展示名,例如“省级管理员” - `data_scope`:默认数据范围 建议正式保留的角色: - `provincial_admin` - `admin` - `common` - `super_admin`(可选) ## 4.3 `permissions` 目标策略 继续沿用: - `permission_key` - `api_path` - `api_method` - `route_id` 建议后续所有新权限都按下面格式命名: - `auth:me:read` - `documents:list:read` - `documents:upload:write` - `audit:run:execute` - `rules:binding:update` 不要混用: - 一部分 `documents.list` - 一部分 `document:read:all` 必须统一成一种风格。 ### 我建议 直接收口成老系统已有风格: - `module:resource:action` 这样能和老数据完全对齐。 ## 4.4 `role_permissions` 目标策略 继续沿用: - `grant_type` - `data_scope` 建议唯一约束明确为: - `(role_id, permission_id, grant_type, data_scope)` 但在第一阶段实际配置时: - 尽量只放 `GRANT` - `data_scope` 按权限点是否涉及数据决定 ## 4.5 `user_role` 目标策略 继续沿用现有结构即可。 关键点不是表结构,而是后端逻辑必须支持: - 一个用户可以挂多个角色 - 登录时不能 `LIMIT 1` - 权限汇总和数据范围要支持多角色收敛 ## 4.6 `sys_routes / role_route` 目标策略 继续沿用旧系统结构即可。 重点是: - 前端菜单权限可以继续从这里出 - 但 **接口权限不能只依赖菜单权限** - API 权限必须还是走 `permissions / role_permissions` --- ## 5. 当前逻辑漏洞清单(必须按优先级修) ## P0:必须先修 ### 5.1 角色/权限字段命名统一 要统一到底使用: - `role_key` 还是 `role_name` - `permission_key` 还是 `permission_code` 当前结论: - 统一用 `role_key` - 统一用 `permission_key` ### 5.2 登录角色查询改成多角色 当前: - `LIMIT 1` 必须改成: - 查询全部角色 - JWT 带 `roles` - 兼容保留一个 `user_role` 主角色字段给旧前端 ### 5.3 OAuth 登录禁止直接覆盖 `area` 必须改成: - 登录入参中的 `area` 不再直接写库 - area 只能来自: - 老库已存在值 - 后台管理维护 - 后续可信组织同步 ### 5.4 新用户自动分配默认角色 OAuth 首登新用户创建后,必须补: - 自动赋 `common` ### 5.5 `/api/auth/me` 落地 要把当前用户的: - 基本信息 - `area` - `roles` - `permissions` 统一返回。 ## P1:第二阶段紧跟着修 ### 5.6 数据范围注入 至少要先覆盖: - 文档列表 - 文档详情 - 用户列表 - 评查状态 / 结果 ### 5.7 密码校验升级 短期兼容旧明文,长期迁移到哈希: - 登录时支持“旧明文兼容 + 新哈希优先” - 用户下次修改密码时写入哈希 ### 5.8 权限缓存 / 权限聚合 后续可以考虑: - JWT 轻载 - 服务端缓存权限聚合结果 但这不是 P0。 --- ## 6. 老系统用户数据迁移策略 ## 6.1 迁移原则 不是“把老用户复制一份到新表”,而是: - **尽可能复用原表结构** - 用增量清洗 + 角色校正 + 地区校正的方式迁移 ## 6.2 迁移对象 第一阶段只迁这几类数据: 1. `sso_users` 2. `roles` 3. `user_role` 4. `permissions` 5. `role_permissions` 6. `sys_routes` 7. `role_route` ## 6.3 `sso_users` 迁移规则 ### 必迁字段 - `id` - `sub` - `username` - `nick_name` - `phone_number` - `email` - `ou_id` - `ou_name` - `status` - `is_leader` - `password` - `area` - `tenant_name` - `dep_name` - `dep_short_name` - `created_at` - `updated_at` - `deleted_at` ### 可选迁字段 - `mq_person_uuid` - `mq_account_uuid` - `mq_synced_at` - `try_count` - `try_login_time` ### 迁移前校验 要先出统计: - `sub` 是否有重复 - `username` 是否有重复 - `area` 是否为空 - `status != 0` 的禁用用户数量 - `deleted_at is not null` 的软删除用户数量 ## 6.4 角色映射规则 要先梳理老库中真实 `role_key` 分布。 预期映射: - `provincial_admin` → 保留 - `admin` → 保留 - `common` → 保留 - 其他历史角色: - 先做映射表 - 无法归类的先落到 `common` - 再人工复核 ### 明确不再延续的角色 - `city_admin` - `review_manager` - `review_user` - `rule_admin` 如果老库真的还残留这些历史角色,不建议直接带入新系统业务角色定义,应该做映射清洗。 ## 6.5 area 清洗规则 迁移前要先把 `sso_users.area` 跑一遍质量检查: - 是否为空 - 是否存在历史别名 - 是否存在前后空格 - 是否存在同义不同写法 最后要统一成一套稳定值,例如: - `省局` - `梅州` - `云浮` - `揭阳` - `潮州` --- ## 7. 开发分阶段 TaskList 下面是正式开发顺序。 ## 阶段 A:数据库基线收口 ### A1. 先核对当前库真实表结构 目标: - 以当前数据库真实字段为准 - 不再拿“理想 SQL”直接执行 动作: - 检查 `sso_users` - 检查 `roles` - 检查 `user_role` - 检查 `permissions` - 检查 `role_permissions` - 检查 `sys_routes` - 检查 `role_route` 产出: - 一份“现网真实表结构快照” - 一份“目标结构差异清单” ### A2. 出正式迁移 SQL 目标: - 不是重建表 - 是补字段 / 补索引 / 补约束 / 补初始化数据 动作: - 修正角色初始化 SQL - 修正权限初始化 SQL - 修正菜单初始化 SQL - 统一全部字段名到现有真实库 产出: - `用户权限_schema_patch.sql` - `用户权限_seed.sql` ## 阶段 B:登录与 JWT 收口 ### B1. 修 `AuthServiceImpl` 必须改: - 多角色查询 - 新用户默认角色分配 - OAuth 登录禁止外部覆盖 `area` - 登录响应统一返回 `roles` - 兼容返回单个 `user_role` ### B2. 修 `JwtService` 必须改: - token 中写入 `roles` - 保留 `user_role` - 写入 `area` - 视情况写入精简 `permissions` ### B3. 新增 `/api/auth/me` 返回: - 用户基本信息 - area - roles - permissions ## 阶段 C:权限聚合与数据范围引擎 ### C1. 做统一权限聚合服务 目标: - 给定 `user_id` - 返回: - 角色列表 - 权限列表 - 每个权限点最终 data_scope ### C2. 做统一数据范围判定器 目标: - 输入:用户 + 权限点 + 业务资源 - 输出: - `ALL` - `DEPT` - `SELF` ### C3. 先接到核心接口 优先改: - `/api/documents/list` - `/api/audit/run/{RunId}` - `/api/audit/result/{RunId}` - `/api/users/list` ## 阶段 D:老用户数据迁移 ### D1. 出迁移前检查 SQL 统计: - 用户数 - 角色分布 - area 分布 - 无角色用户 - 空 area 用户 - 重复 sub / username ### D2. 出迁移脚本 做法: - 先备份 - 再清洗 area - 再导入 / 映射角色 - 再补默认角色 - 再跑校验 ### D3. 迁移后验收 验收项: - 登录正常 - `/api/auth/me` 正常 - 文档列表按地区隔离正常 - admin 不能跨区 - provincial_admin 能看全局 - common 只能看自己 ## 阶段 E:安全与运维收口 ### E1. 密码升级方案 - 兼容旧明文 - 新密码写哈希 - 后续批量升级 ### E2. 审计日志 至少记录: - 登录成功/失败 - 角色变更 - 用户地区变更 - 权限分配变更 ### E3. 文档留档 要把以下文档保持最新: - `docs/用户与地区权限完整设计方案.md` - `docs/接口/用户权限与权限点清单.md` - `docs/用户权限开发TaskList.md` --- ## 8. 推荐的实际开发顺序 如果按“马上开始干”的节奏,建议严格按下面顺序: 1. 先检查当前库真实 RBAC 表结构 2. 再把初始化 SQL 改成真实可执行版 3. 再改登录与 JWT 4. 再补 `/api/auth/me` 5. 再做权限聚合与数据范围注入 6. 再做老用户迁移脚本 7. 最后再做密码升级和审计收口 这个顺序的原因很简单: - 不先确认表结构,后面代码全会写偏 - 不先收口登录态,前端无法稳定接入 - 不先做数据范围,地区隔离只是纸面方案 - 不最后做迁移,容易把脏数据带着跑 --- ## 9. 我建议下一步立刻做什么 下一步最应该做的不是直接改 controller,而是: ### 第一步 先把**当前数据库里 7 张核心表的真实结构**完整导出来: - `sso_users` - `roles` - `user_role` - `permissions` - `role_permissions` - `sys_routes` - `role_route` ### 第二步 基于真实结构,产出两份正式 SQL: - schema patch SQL - seed SQL ### 第三步 再开始改: - `authServiceImpl.py` - `jwtService.py` - `/api/auth/me` 也就是说,**下一阶段开发入口就是“数据库真相收口”**。