# 用户与地区权限完整设计方案 > 本文档为 `leaudit-platform` 当前阶段的正式权限设计稿,已经按实际业务收口。 > 核心结论:**只做单地区归属 + 角色权限 + 数据范围隔离**,不再做多地区用户模型,不再通过前端端口推断地区。 --- ## 1. 这份设计最终解决什么问题 当前新系统要解决的,不是一个抽象的“大而全权限平台”,而是一个非常明确的业务问题: - 用户登录后,系统知道“这个人是谁” - 系统知道“这个人属于哪个地区” - 系统知道“这个人是什么角色” - 接口查询时,系统自动限制他能看到哪些数据 - 前端菜单、按钮、接口能力,按角色和权限点控制 也就是说,新系统最终采用的是: - `RBAC + 单地区数据隔离` 而不是: - 多地区用户模型 - 自定义地区集合授权 - 通过前端端口识别地区 - 复杂租户树 / 组织树权限 --- ## 2. 先说最终结论 ### 2.1 当前真实业务模型 当前业务可以收敛为下面四件事: 1. 一个用户只挂一个主地区 2. 数据隔离主要按这个地区做 3. 角色只有少量几类,不需要发明新角色 4. 权限判断分两层: - 功能权限:能不能访问某接口 / 菜单 / 动作 - 数据权限:即使能访问,也不一定能看所有地区的数据 ### 2.2 当前应保留的角色 按你最新确认,当前只保留下面这几类业务角色: - `provincial_admin` - `admin` - `common` 技术上可以保留一个可选的: - `super_admin` 但它只建议作为系统级运维 / 超级初始化账号使用,不建议作为日常业务角色依赖。 ### 2.3 明确不要的东西 这次设计里,明确不做下面这些复杂化模型: - `city_admin` - `review_manager` - `review_user` - `rule_admin` - `home_region_code` - `accessible_regions` - `user_regions` - `user_region_scope` - `CUSTOM_REGIONS` - 前端端口映射地区 - 一个用户多地区授权集合 这些概念要么不是当前真实业务需要,要么会把系统越做越重。 --- ## 3. 老系统真实逻辑,应该继承什么 结合对老项目 `/home/wren-dev/Porject/docauditai` 的代码分析,老系统的真实权限结构不是“只有 6 张表”,而是: - 用户主表:`sso_users` - 角色表:`roles` - 用户角色关系:`user_role` - 路由菜单:`sys_routes` - 角色路由关系:`role_route` - 权限点:`permissions` - 角色权限点关系:`role_permissions` - 数据范围:`roles.data_scope`、`role_permissions.data_scope` - 用户地区:`sso_users.area` 所以新系统最合理的做法不是推翻重来,而是: - 继承老系统的 `RBAC + area 数据隔离` - 去掉老系统里不够清晰、靠约定隐式生效的部分 - 在新系统里把“地区”“角色”“权限点”“数据范围”写清楚 --- ## 4. 新系统最终权限模型 ## 4.1 总体结构 新系统建议正式采用下面这套结构: - `sso_users`:用户主表 - `roles`:角色表 - `user_role`:用户角色关系 - `sys_routes`:菜单 / 路由定义 - `role_route`:角色菜单关系 - `permissions`:接口 / 动作权限点 - `role_permissions`:角色权限点关系 数据隔离不单独建“用户地区关系表”,而是直接依赖: - `sso_users.area` - 业务表中的 `region` ### 4.2 权限判断分层 每次请求都分两步: #### 第一步:功能权限判断 判断用户有没有权访问: - 某个页面 - 某个接口 - 某个动作 这个由: - `roles` - `role_route` - `role_permissions` 决定。 #### 第二步:数据范围判断 即使用户有接口权限,也要再判断他能看哪些数据。 这个由: - 用户角色对应的 `data_scope` - 用户自身的 `sso_users.area` - 业务数据的 `region` 共同决定。 --- ## 5. 地区模型正式收口 ## 5.1 用户地区字段只保留一个主字段 当前阶段,用户只保留一个地区归属字段: - `sso_users.area` 它的语义正式定义为: - 用户主归属地区 - 用户默认数据隔离地区 - 非省级角色的数据过滤依据 它**不再表示**: - 前端访问入口地区 - 临时切换地区 - 多地区集合 ### 5.2 业务数据地区字段 业务表保留自己的地区字段,例如: - `leaudit_documents.region` - `leaudit_runs.region`(如果后续补) - `leaudit_rule_bindings.region` 这些字段的语义是: - 该业务数据归属哪个地区 ### 5.3 地区过滤规则 - 如果角色数据范围是 `ALL`,则不按地区过滤 - 如果角色数据范围是 `DEPT`,则只能看 `region = sso_users.area` 的数据 - 如果角色数据范围是 `SELF`,则只能看自己创建 / 上传 / 归属自己的数据 这里的 `DEPT` 虽然沿用老系统命名,但在新系统里应明确理解为: - **同地区** - 不是传统组织架构里的“同部门” 也就是说: - `DEPT = same area` --- ## 6. 角色定义 ## 6.1 `provincial_admin` 建议语义: - 省级管理员 - 可看全省数据 - 可管理地区级业务配置 - 可查看全局文档、任务、评查结果 建议默认数据范围: - `ALL` ## 6.2 `admin` 建议语义: - 地区管理员 - 只能看自己地区数据 - 可管理本地区用户日常业务、文档、规则绑定、评查结果 建议默认数据范围: - `DEPT` ## 6.3 `common` 建议语义: - 普通业务用户 - 通常只能处理自己提交或自己负责的数据 - 也可以视业务需要,局部开放为“看本地区” 建议默认数据范围: - 默认 `SELF` - 某些只读类权限点可单独放宽为 `DEPT` ## 6.4 `super_admin`(可选) 建议语义: - 系统超级管理员 / 初始化管理员 - 不参与日常地区业务流程 - 仅用于系统维护、初始化、紧急排障 建议默认数据范围: - `ALL` --- ## 7. 数据范围模型 ## 7.1 建议保留三种 scope 为了兼容老系统思路,并保持简单,正式只保留三种: - `ALL` - `DEPT` - `SELF` ### 7.2 含义定义 #### `ALL` - 查看全部数据 - 不做地区过滤 #### `DEPT` - 实际表示“同地区” - 查询自动加条件:`region = 当前用户.area` #### `SELF` - 只能查看自己的数据 - 查询自动加条件,例如: - `created_by = 当前用户.id` - 或 `uploaded_by = 当前用户.id` - 或 `owner_id = 当前用户.id` ### 7.3 推荐生效顺序 如果一个用户有多个角色、多条权限记录,建议按以下规则收敛: 1. 先确认功能权限是否命中 2. 再收集该权限点对应的所有 `data_scope` 3. 取范围最大的那个作为最终可见范围 范围大小建议定义为: - `ALL > DEPT > SELF` 例如: - 用户既是 `common`,又临时带一个 `admin` 角色 - 对某个接口命中了两条权限:`SELF` 和 `DEPT` - 那最终按 `DEPT` 生效 --- ## 8. 建议保留 / 改造的表结构 以下不是要求把库推倒重建,而是“新系统正式语义定义”。 ## 8.1 `sso_users` 建议核心字段至少明确为: ```sql CREATE TABLE sso_users ( id BIGSERIAL PRIMARY KEY, sub VARCHAR(128) NOT NULL UNIQUE, username VARCHAR(128), nick_name VARCHAR(128), email VARCHAR(256), phone_number VARCHAR(64), password_hash VARCHAR(255), status SMALLINT NOT NULL DEFAULT 0, is_leader BOOLEAN NOT NULL DEFAULT FALSE, area VARCHAR(64), tenant_id BIGINT NULL, department_id BIGINT NULL, source_type VARCHAR(32) NOT NULL DEFAULT 'oauth', source_payload JSONB NULL, try_count INT NOT NULL DEFAULT 0, try_login_time TIMESTAMPTZ NULL, last_login_at TIMESTAMPTZ NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL ); ``` ### `sso_users` 字段解释 - `sub`:统一认证唯一标识,OAuth 用户核心身份键 - `username`:登录名 / 工号 / 本地账号名 - `nick_name`:姓名 - `area`:用户归属地区,当前阶段最关键业务字段 - `source_type`:`oauth` / `local` / `imported` - `source_payload`:保留外部登录原始字段快照 - `status`:是否启用 ### 关键约束 - `area` 必须是后台可信字段 - `area` 不能再由前端端口决定 - OAuth 登录后,如果需要更新地区,也必须来自后端组织同步或后台管理维护 ## 8.2 `roles` ```sql CREATE TABLE roles ( id BIGSERIAL PRIMARY KEY, role_name VARCHAR(64) NOT NULL UNIQUE, description TEXT, data_scope VARCHAR(16) NOT NULL DEFAULT 'SELF', status SMALLINT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` 建议初始化: ```sql INSERT INTO roles (role_name, description, data_scope) VALUES ('provincial_admin', '省级管理员', 'ALL'), ('admin', '地区管理员', 'DEPT'), ('common', '普通用户', 'SELF'), ('super_admin', '系统超级管理员', 'ALL'); ``` ## 8.3 `user_role` ```sql CREATE TABLE user_role ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES sso_users(id), role_id BIGINT NOT NULL REFERENCES roles(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, role_id) ); ``` ## 8.4 `sys_routes` ```sql CREATE TABLE sys_routes ( id BIGSERIAL PRIMARY KEY, path VARCHAR(255) NOT NULL, name VARCHAR(128) NOT NULL, component VARCHAR(255), parent_id BIGINT NULL, sort_order INT NOT NULL DEFAULT 0, visible BOOLEAN NOT NULL DEFAULT TRUE, enabled BOOLEAN NOT NULL DEFAULT TRUE, meta JSONB NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` ## 8.5 `role_route` ```sql CREATE TABLE role_route ( id BIGSERIAL PRIMARY KEY, role_id BIGINT NOT NULL REFERENCES roles(id), route_id BIGINT NOT NULL REFERENCES sys_routes(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (role_id, route_id) ); ``` ## 8.6 `permissions` ```sql CREATE TABLE permissions ( id BIGSERIAL PRIMARY KEY, permission_code VARCHAR(128) NOT NULL UNIQUE, permission_name VARCHAR(128) NOT NULL, resource_type VARCHAR(64) NOT NULL, method VARCHAR(16) NULL, path VARCHAR(255) NULL, description TEXT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` 建议权限编码风格: - `documents.list` - `documents.upload` - `documents.detail` - `documents.delete` - `rules.binding.manage` - `users.manage` ## 8.7 `role_permissions` ```sql CREATE TABLE role_permissions ( id BIGSERIAL PRIMARY KEY, role_id BIGINT NOT NULL REFERENCES roles(id), permission_id BIGINT NOT NULL REFERENCES permissions(id), data_scope VARCHAR(16) NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (role_id, permission_id) ); ``` 这里的 `data_scope` 允许覆盖 `roles.data_scope`。 推荐生效规则: - 优先取 `role_permissions.data_scope` - 如果为空,再回退到 `roles.data_scope` 这样可以实现: - 角色整体是 `SELF` - 但对某个只读接口单独放宽为 `DEPT` --- ## 9. JWT 与登录态设计 ## 9.1 JWT 应携带什么 JWT 不要继续塞一堆不稳定字段,但至少应携带: ```json { "user_id": 1001, "sub": "oauth-sub-xxx", "username": "zhangsan", "nick_name": "张三", "area": "潮州", "roles": ["admin"], "permissions": ["documents.list", "documents.upload"], "is_super_admin": false, "exp": 1770000000 } ``` ### 核心要求 - `area` 要进 JWT,便于接口层直接做地区过滤 - `roles` 要进 JWT,便于快速做角色识别 - `permissions` 可按系统实现选择是否进 token - 若放入 JWT,接口判断更快 - 若不放入 JWT,则请求时查库 ## 9.2 登录来源 统一登录支持: - OAuth 登录 - 账号密码登录 但两种登录最后都必须落到同一套用户主数据: - `sso_users` - `user_role` - `roles` - `area` ### 明确禁止 禁止再按下面方式确定地区: - 前端端口 - 前端域名 - 页面参数 - 浏览器本地缓存里的地区值 --- ## 10. 接口契约建议 下面给的是新系统正式推荐接口契约。 ## 10.1 登录返回 ### `POST /api/auth/login` #### 返回示例 ```json { "code": 200, "message": "ok", "data": { "accessToken": "jwt-token", "tokenType": "bearer", "expiresIn": 7200, "userInfo": { "userId": 1001, "sub": "oauth-sub-xxx", "username": "zhangsan", "nickName": "张三", "area": "潮州", "roles": ["admin"], "permissions": [ "documents.list", "documents.upload", "documents.detail" ] } } } ``` ## 10.2 当前用户信息 ### `GET /api/auth/me` #### 返回示例 ```json { "code": 200, "message": "ok", "data": { "userId": 1001, "username": "zhangsan", "nickName": "张三", "area": "潮州", "roles": ["admin"], "permissions": ["documents.list", "documents.upload"] } } ``` ## 10.3 用户列表 ### `GET /api/users/list` 查询规则: - `provincial_admin` / `super_admin`:可查全部用户 - `admin`:只能看本地区用户 - `common`:通常不开放 #### 建议查询条件 - `username` - `nickName` - `area` - `roleName` - `status` ## 10.4 文档列表 ### `GET /api/documents/list` 查询规则: - `ALL`:不加地区过滤 - `DEPT`:自动加 `region = 当前用户.area` - `SELF`:自动加 `uploaded_by = 当前用户.id` **注意:前端传了 `region` 也不能绕过后端数据权限。** --- ## 11. 后端权限判定顺序 建议每个受保护接口按下面顺序执行。 ### 11.1 第一步:解析 JWT 拿到: - `user_id` - `area` - `roles` - `permissions` ### 11.2 第二步:校验功能权限 例如访问: - `/api/documents/list` 要求权限码: - `documents.list` 如果没有这个权限,直接返回 403。 ### 11.3 第三步:确定最终数据范围 按顺序取值: 1. 当前权限点对应的 `role_permissions.data_scope` 2. 角色默认 `roles.data_scope` 多角色时,取范围最大的那个。 ### 11.4 第四步:注入数据过滤条件 例如文档列表: - `ALL` → 不加过滤 - `DEPT` → `WHERE d.region = :current_user_area` - `SELF` → `WHERE d.uploaded_by = :current_user_id` ### 11.5 第五步:执行查询 这样做可以避免: - 前端绕过地区限制 - 前端伪造地区参数 - 页面隐藏了但接口仍可越权访问 --- ## 12. 与文档业务表的关系 ## 12.1 为什么地区过滤只靠 `sso_users.area` 因为你已经确认,当前业务不是“一个用户可看多个地区”。 所以没必要引入: - 用户地区关系表 - 多地区授权集合 - 区域切换态 当前最稳的做法就是: - 用户一个 `area` - 文档一个 `region` - 查询时直接比对 ## 12.2 文档上传时怎么处理地区 上传时建议规则: - 如果前端未传 `region`,则默认取当前用户 `area` - 如果前端传了 `region`: - `provincial_admin` 可指定任意合法地区 - `admin` 只能传与自己 `area` 相同的值 - `common` 通常不允许跨地区指定 这样能保证: - 数据归属清晰 - 省级管理员可代地区上传 - 地区管理员不能越区写数据 --- ## 13. 现在不建议做的事 以下内容当前都不建议提前上: - 多地区切换 - 自定义用户地区集合 - 按前端入口决定地区 - 复杂组织树联动到每个业务接口 - 为每个业务再单独造一套权限表 - 设计很多当前不存在的角色 原因很简单: - 真实业务还没要求到这一步 - 这些模型一旦上了,后续维护复杂度会明显增加 - 现阶段最重要的是把“登录 -> 用户 -> 地区 -> 角色 -> 数据过滤”这条链路先做稳定 --- ## 14. 推荐落地顺序 ## 14.1 第一阶段:先把模型定义收口 确认并冻结: - 用户地区字段就是 `sso_users.area` - 角色就是 `provincial_admin / admin / common` - 数据范围就是 `ALL / DEPT / SELF` ## 14.2 第二阶段:统一登录返回结构 确保 OAuth 登录、账密登录都统一返回: - 用户基本信息 - `area` - `roles` - `permissions` ## 14.3 第三阶段:把接口权限检查和数据范围注入补齐 优先补以下接口: - `/api/auth/me` - `/api/documents/list` - `/api/documents/detail` - `/api/documents/upload` - `/api/users/list` ## 14.4 第四阶段:补文档与前端说明 把以下内容写成开发规范: - 哪些接口需要什么权限码 - 哪些接口如何按 `area` 过滤 - 前端不允许自己决定地区 - 上传 / 列表 / 详情的地区行为规则 --- ## 15. 最终版设计结论 新系统最终建议正式采用: - `RBAC + 单地区隔离` 具体落地为: - 用户主表核心字段:`sso_users.area` - 角色:`provincial_admin / admin / common`,可选 `super_admin` - 数据范围:`ALL / DEPT / SELF` - 地区过滤:按 `业务表.region` 对比 `sso_users.area` - 功能权限:`roles + role_route + permissions + role_permissions` - 不再做前端端口识别地区 - 不再做多地区用户模型 这是当前最符合你们真实业务、最接近老系统有效逻辑、同时又足够轻量可落地的一版方案。