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

> 本文档为 `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 数据隔离`
- 去掉老系统里不够清晰、靠约定隐式生效的部分
- 在新系统里把“地区”“角色”“权限点”“数据范围”写清楚

### 3.1 老系统真正值得继承的 4 个点

1. 登录入口虽然支持 OAuth 和账密两种模式，但最终都统一落到：
   - `sso_users`
   - `user_role`
   - `roles`
   - JWT
2. 菜单权限和动作权限始终分层：
   - 菜单 / 页面：`sys_routes + role_route`
   - 接口 / 动作：`permissions + role_permissions`
3. 数据范围虽然分散，但真实主轴一直是：
   - `sso_users.area`
   - 业务表 `area/region`
   - `data_scope`
4. 老系统本质上就是：
   - `RBAC + area-based data scope`

也就是说，新系统不需要重新发明一个“全新权限平台”，而是要把这条老主线收口得更清楚。

### 3.2 老系统不再继续继承的部分

这些老逻辑在新系统里不建议继续保留：

- 靠前端端口、部署环境、隐式约定推断地区
- 多套登录入口分别维护不同用户模型
- 模糊的多地区授权集合
- 没写清楚边界的隐藏 data scope 规则

新系统应改成：

- 一个用户一个主地区
- 一个统一用户主表
- 一套明确 RBAC 结构
- 一套明确的数据范围规则

---

## 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`
- 不再做前端端口识别地区
- 不再做多地区用户模型

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