17 KiB
用户与地区权限完整设计方案
本文档为
leaudit-platform当前阶段的正式权限设计稿,已经按实际业务收口。
核心结论:只做单地区归属 + 角色权限 + 数据范围隔离,不再做多地区用户模型,不再通过前端端口推断地区。
1. 这份设计最终解决什么问题
当前新系统要解决的,不是一个抽象的“大而全权限平台”,而是一个非常明确的业务问题:
- 用户登录后,系统知道“这个人是谁”
- 系统知道“这个人属于哪个地区”
- 系统知道“这个人是什么角色”
- 接口查询时,系统自动限制他能看到哪些数据
- 前端菜单、按钮、接口能力,按角色和权限点控制
也就是说,新系统最终采用的是:
RBAC + 单地区数据隔离
而不是:
- 多地区用户模型
- 自定义地区集合授权
- 通过前端端口识别地区
- 复杂租户树 / 组织树权限
2. 先说最终结论
2.1 当前真实业务模型
当前业务可以收敛为下面四件事:
- 一个用户只挂一个主地区
- 数据隔离主要按这个地区做
- 角色只有少量几类,不需要发明新角色
- 权限判断分两层:
- 功能权限:能不能访问某接口 / 菜单 / 动作
- 数据权限:即使能访问,也不一定能看所有地区的数据
2.2 当前应保留的角色
按你最新确认,当前只保留下面这几类业务角色:
provincial_adminadmincommon
技术上可以保留一个可选的:
super_admin
但它只建议作为系统级运维 / 超级初始化账号使用,不建议作为日常业务角色依赖。
2.3 明确不要的东西
这次设计里,明确不做下面这些复杂化模型:
city_adminreview_managerreview_userrule_adminhome_region_codeaccessible_regionsuser_regionsuser_region_scopeCUSTOM_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 权限判断分层
每次请求都分两步:
第一步:功能权限判断
判断用户有没有权访问:
- 某个页面
- 某个接口
- 某个动作
这个由:
rolesrole_routerole_permissions
决定。
第二步:数据范围判断
即使用户有接口权限,也要再判断他能看哪些数据。
这个由:
- 用户角色对应的
data_scope - 用户自身的
sso_users.area - 业务数据的
region
共同决定。
5. 地区模型正式收口
5.1 用户地区字段只保留一个主字段
当前阶段,用户只保留一个地区归属字段:
sso_users.area
它的语义正式定义为:
- 用户主归属地区
- 用户默认数据隔离地区
- 非省级角色的数据过滤依据
它不再表示:
- 前端访问入口地区
- 临时切换地区
- 多地区集合
5.2 业务数据地区字段
业务表保留自己的地区字段,例如:
leaudit_documents.regionleaudit_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
为了兼容老系统思路,并保持简单,正式只保留三种:
ALLDEPTSELF
7.2 含义定义
ALL
- 查看全部数据
- 不做地区过滤
DEPT
- 实际表示“同地区”
- 查询自动加条件:
region = 当前用户.area
SELF
- 只能查看自己的数据
- 查询自动加条件,例如:
created_by = 当前用户.id- 或
uploaded_by = 当前用户.id - 或
owner_id = 当前用户.id
7.3 推荐生效顺序
如果一个用户有多个角色、多条权限记录,建议按以下规则收敛:
- 先确认功能权限是否命中
- 再收集该权限点对应的所有
data_scope - 取范围最大的那个作为最终可见范围
范围大小建议定义为:
ALL > DEPT > SELF
例如:
- 用户既是
common,又临时带一个admin角色 - 对某个接口命中了两条权限:
SELF和DEPT - 那最终按
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_type:oauth/local/importedsource_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.listdocuments.uploaddocuments.detaildocuments.deleterules.binding.manageusers.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_usersuser_rolerolesarea
明确禁止
禁止再按下面方式确定地区:
- 前端端口
- 前端域名
- 页面参数
- 浏览器本地缓存里的地区值
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:通常不开放
建议查询条件
usernamenickNamearearoleNamestatus
10.4 文档列表
GET /api/documents/list
查询规则:
ALL:不加地区过滤DEPT:自动加region = 当前用户.areaSELF:自动加uploaded_by = 当前用户.id
注意:前端传了 region 也不能绕过后端数据权限。
11. 后端权限判定顺序
建议每个受保护接口按下面顺序执行。
11.1 第一步:解析 JWT
拿到:
user_idarearolespermissions
11.2 第二步:校验功能权限
例如访问:
/api/documents/list
要求权限码:
documents.list
如果没有这个权限,直接返回 403。
11.3 第三步:确定最终数据范围
按顺序取值:
- 当前权限点对应的
role_permissions.data_scope - 角色默认
roles.data_scope
多角色时,取范围最大的那个。
11.4 第四步:注入数据过滤条件
例如文档列表:
ALL→ 不加过滤DEPT→WHERE d.region = :current_user_areaSELF→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 登录、账密登录都统一返回:
- 用户基本信息
arearolespermissions
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 - 不再做前端端口识别地区
- 不再做多地区用户模型
这是当前最符合你们真实业务、最接近老系统有效逻辑、同时又足够轻量可落地的一版方案。