Files
leaudit-platform-backend/docs/用户与地区权限完整设计方案.md
T
2026-04-29 15:23:19 +08:00

17 KiB
Raw Blame History

用户与地区权限完整设计方案

本文档为 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_scoperole_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 角色
  • 对某个接口命中了两条权限:SELFDEPT
  • 那最终按 DEPT 生效

8. 建议保留 / 改造的表结构

以下不是要求把库推倒重建,而是“新系统正式语义定义”。

8.1 sso_users

建议核心字段至少明确为:

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_typeoauth / local / imported
  • source_payload:保留外部登录原始字段快照
  • status:是否启用

关键约束

  • area 必须是后台可信字段
  • area 不能再由前端端口决定
  • OAuth 登录后,如果需要更新地区,也必须来自后端组织同步或后台管理维护

8.2 roles

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()
);

建议初始化:

INSERT INTO roles (role_name, description, data_scope) VALUES
('provincial_admin', '省级管理员', 'ALL'),
('admin', '地区管理员', 'DEPT'),
('common', '普通用户', 'SELF'),
('super_admin', '系统超级管理员', 'ALL');

8.3 user_role

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

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

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

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

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 不要继续塞一堆不稳定字段,但至少应携带:

{
  "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

返回示例

{
  "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

返回示例

{
  "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 → 不加过滤
  • DEPTWHERE d.region = :current_user_area
  • SELFWHERE 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
  • 不再做前端端口识别地区
  • 不再做多地区用户模型

这是当前最符合你们真实业务、最接近老系统有效逻辑、同时又足够轻量可落地的一版方案。