企业权限系统技术方案
- 企业权限系统技术方案
- 一、核心设计思路
- 二、表结构设计
- 2.1 权限点表
sys_permission - 2.2 数据资源点表
sys_data_resource - 2.3 数据资源点可选范围表
sys_data_resource_scope - 2.4 ⭐ 范围数据字典表
sys_scope_data - 2.5 角色表
sys_role - 2.6 用户-角色关系表
sys_user_role - 2.7 角色-权限点关联表
sys_role_permission - 2.8 ⭐ 用户-权限点授权表
sys_user_permission_grant - 2.9 授权实体明细表
sys_grant_scope_entity - 2.10 权限操作审计日志表
sys_permission_audit_log
- 2.1 权限点表
- 三、关键设计决策说明
- 四、授权流程
- 五、运行时校验流程
- 六、缓存设计
- 七、系统架构图
- 八、ER 图
- 九、接口设计
- 十、多租户扩展方案
企业权限系统技术方案
核心调整:Role 是 Permission 的打包模板,真正的授权单元是 Permission,过期时间挂在用户-权限点授权上。数据范围通过 sys_scope_data 统一管理,外部系统只需对接同步任务即可接入新的数据维度。
一、核心设计思路
Role
└─ 只是权限点的集合模板,方便批量操作,本身不是授权单元
Permission(权限点)
└─ 才是真正的授权单元
└─ 关联一个 DataResource(数据资源点)
用户授权流程:
方式一:选角色 → 展开角色下所有权限点 → 逐个配置数据范围 + 过期时间 → 批量写入授权记录
方式二:直接选权限点 → 配置数据范围 + 过期时间 → 写入单条授权记录
授权记录(sys_user_permission_grant)
└─ 一条记录 = 用户拥有某个权限点的一次授权
└─ 记录来源(通过哪个角色批量授予 or 单独授予)
└─ 记录数据范围
└─ 记录过期时间
二、表结构设计
2.1 权限点表 sys_permission
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '租户ID',
perm_code VARCHAR(128) NOT NULL COMMENT '权限编码,如 order:view',
perm_name VARCHAR(128) NOT NULL COMMENT '权限名称',
type TINYINT NOT NULL DEFAULT 2 COMMENT '1菜单 2操作按钮 3API',
data_resource_id BIGINT COMMENT '关联的数据资源点,NULL 表示无数据权限',
sort INT DEFAULT 0,
remark VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_tenant_perm_code (tenant_id, perm_code)
);
2.2 数据资源点表 sys_data_resource
CREATE TABLE sys_data_resource (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
resource_code VARCHAR(128) NOT NULL UNIQUE COMMENT '资源编码,如 data:order',
resource_name VARCHAR(128) NOT NULL COMMENT '资源名称,如 订单数据',
description VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
2.3 数据资源点可选范围表 sys_data_resource_scope
-- 元数据:定义某个数据资源点开放哪些范围选项
-- 授权配置时用,运行时不参与请求链路
CREATE TABLE sys_data_resource_scope (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
data_resource_id BIGINT NOT NULL,
scope_code VARCHAR(64) NOT NULL COMMENT '范围编码,如 ALL / DEPT / SELF / CITY / PROJECT',
scope_name VARCHAR(128) NOT NULL COMMENT '展示给管理员的名称',
scope_type TINYINT NOT NULL COMMENT '1全量 2部门树 3本人 4实体列表(对接 sys_scope_data)',
sort INT DEFAULT 0,
UNIQUE KEY uk_resource_scope (data_resource_id, scope_code)
);
scope_type = 4表示该范围的候选值来自sys_scope_data,前端授权页面通过scope_code去sys_scope_data查候选列表,不需要调任何外部接口。
2.4 ⭐ 范围数据字典表 sys_scope_data
-- 统一存储所有数据范围的候选值
-- 外部系统通过同步任务写入,权限系统内部完全自洽
CREATE TABLE sys_scope_data (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
scope_code VARCHAR(64) NOT NULL COMMENT '范围类型,对应 sys_data_resource_scope.scope_code,如 CITY / PROJECT',
entity_id VARCHAR(64) NOT NULL COMMENT '外部实体ID,VARCHAR 兼容各种ID类型',
entity_name VARCHAR(128) NOT NULL COMMENT '实体名称,授权页面展示用',
entity_extra JSON COMMENT '扩展信息,如层级路径、所属上级等',
status TINYINT DEFAULT 1 COMMENT '1有效 0已下线',
synced_at DATETIME COMMENT '最后同步时间',
UNIQUE KEY uk_scope_entity (scope_code, entity_id),
KEY idx_scope_code (scope_code)
);
entity_extra 示例:
// 城市,带省份信息
{ "province": "广东省", "province_id": "44" }
// 项目,带所属业务线
{ "biz_line": "电商", "owner": "张三" }
外部系统接入方式(唯一入口):
INSERT INTO sys_scope_data (scope_code, entity_id, entity_name, entity_extra, synced_at)
VALUES ('CITY', '101', '上海', '{"province":"上海市"}', NOW())
ON DUPLICATE KEY UPDATE
entity_name = VALUES(entity_name),
entity_extra = VALUES(entity_extra),
synced_at = VALUES(synced_at);
新接入一种数据维度(如"项目"、"区域"),只需要:
- 在
sys_data_resource_scope新增一条 scope_type=4 的配置记录- 跑一个同步任务往
sys_scope_data写数据权限系统本身零改动。
2.5 角色表 sys_role
-- Role 只是权限点的打包模板,不是授权单元
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '租户ID',
role_code VARCHAR(64) NOT NULL COMMENT '角色编码',
role_name VARCHAR(128) NOT NULL,
is_super TINYINT DEFAULT 0 COMMENT '1超管角色,鉴权时直接放行,无需配置权限点',
status TINYINT DEFAULT 1,
remark VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_tenant_role_code (tenant_id, role_code)
);
2.6 用户-角色关系表 sys_user_role
-- 记录"这个人被分配了哪些角色"这件事本身
-- 职责:角色列表展示、角色级撤销/续期操作
-- 不参与鉴权,运行时权限校验只走 sys_user_permission_grant
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
grant_by BIGINT COMMENT '授权操作人 user_id',
start_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生效时间',
expire_time DATETIME COMMENT '过期时间,冗余字段,仅用于展示,鉴权以 sys_user_permission_grant 为准',
status TINYINT DEFAULT 1 COMMENT '1有效 0已撤销',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_role (tenant_id, user_id, role_id),
KEY idx_user_id (user_id),
KEY idx_role_id (role_id)
);
职责边界:
- 写入时机:管理员通过角色授权,同时写入本表(1条)+
sys_user_permission_grant(N条,每个权限点一条)- 读取场景:展示用户持有的角色列表、角色续期、一键撤销整个角色
expire_time冗余存储,方便 UI 展示剩余有效期,但鉴权不依赖此字段
与
sys_user_permission_grant的联动逻辑:
- 给用户分配角色 → 写入
sys_user_role+ 展开权限点批量写入sys_user_permission_grant(source_type=1, source_id=role_id)- 撤销用户角色 → DELETE
sys_user_role+ UPDATEsys_user_permission_grantSET status=0 WHERE source_type=1 AND source_id=role_id- 查看用户拥有哪些角色 → 查
sys_user_roleJOINsys_role
2.7 角色-权限点关联表 sys_role_permission
-- 仅描述角色包含哪些权限点,是模板定义,不涉及授权
CREATE TABLE sys_role_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
UNIQUE KEY uk_role_perm (role_id, permission_id)
);
2.8 ⭐ 用户-权限点授权表 sys_user_permission_grant
-- 核心授权表:真正的授权单元是权限点,过期时间在这里
CREATE TABLE sys_user_permission_grant (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '被授权用户',
permission_id BIGINT NOT NULL COMMENT '授权的权限点',
data_resource_id BIGINT COMMENT '关联的数据资源点(冗余存储,方便查询)',
scope_code VARCHAR(64) COMMENT '数据范围编码,来自资源点可选范围的子集',
-- 具体的实体ID列表已拆出到 sys_grant_scope_entity,不再存 JSON
-- 授权来源
source_type TINYINT NOT NULL COMMENT '1通过角色批量授予 2单独授予 (可扩展:3岗位模板 4用户组等)',
source_id BIGINT COMMENT '来源ID,与 source_type 配合使用,source_type=2 时为 NULL',
grant_by BIGINT COMMENT '授权操作人 user_id',
-- 有效期
start_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生效时间',
expire_time DATETIME COMMENT '过期时间,NULL 表示永久有效',
status TINYINT DEFAULT 1 COMMENT '1有效 0已撤销',
revoked_at DATETIME COMMENT '撤销时间,status 变为 0 时记录,用于审计',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_user_id (user_id),
KEY idx_expire_time (expire_time),
KEY idx_source (source_type, source_id),
KEY idx_user_perm (user_id, permission_id)
);
2.9 授权实体明细表 sys_grant_scope_entity
-- 将原 custom_value JSON 拆成关系表
-- 支持正向查(用户被授权了哪些实体)和反查(某实体被授权给了哪些用户)
CREATE TABLE sys_grant_scope_entity (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
grant_id BIGINT NOT NULL COMMENT '关联 sys_user_permission_grant.id',
scope_code VARCHAR(64) NOT NULL COMMENT '范围类型,如 CITY / PROJECT',
entity_id VARCHAR(64) NOT NULL COMMENT '实体ID,对应 sys_scope_data.entity_id',
KEY idx_grant_id (grant_id),
KEY idx_entity (scope_code, entity_id)
);
查询示例:
-- 正向查:张三在 order:view 权限下被授权了哪些城市
SELECT e.entity_id, e.scope_code
FROM sys_grant_scope_entity e
JOIN sys_user_permission_grant g ON g.id = e.grant_id
JOIN sys_permission p ON p.id = g.permission_id
WHERE g.tenant_id = #{tenantId}
AND g.user_id = #{userId}
AND p.perm_code = 'order:view'
AND g.status = 1
AND (g.expire_time IS NULL OR g.expire_time > NOW());
-- 反查:城市101被授权给了哪些用户
SELECT g.user_id, g.expire_time
FROM sys_grant_scope_entity e
JOIN sys_user_permission_grant g ON g.id = e.grant_id
WHERE e.scope_code = 'CITY'
AND e.entity_id = '101'
AND g.tenant_id = #{tenantId}
AND g.status = 1;
注意:查询时必须 JOIN
sys_user_permission_grant带上tenant_id过滤,sys_grant_scope_entity本身不存 tenant_id,通过 grant 的 tenant_id 完成租户隔离。
2.10 权限操作审计日志表 sys_permission_audit_log
-- 记录所有权限变更操作,用于审计和问题排查
-- 只追加写入,不做 UPDATE/DELETE
CREATE TABLE sys_permission_audit_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT COMMENT '租户ID',
operator_id BIGINT NOT NULL COMMENT '操作人 user_id',
target_user_id BIGINT NOT NULL COMMENT '被操作用户 user_id',
action VARCHAR(32) NOT NULL COMMENT 'GRANT_ROLE / REVOKE_ROLE / GRANT_PERM / REVOKE_PERM / MODIFY_SCOPE / MODIFY_EXPIRE',
source_type TINYINT COMMENT '1角色授权 2单独授权',
source_id BIGINT COMMENT '关联角色ID等',
biz_data JSON COMMENT '操作详情快照,如授权的权限点列表、数据范围等',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
KEY idx_tenant_target (tenant_id, target_user_id),
KEY idx_operator (operator_id),
KEY idx_created_at (created_at)
);
biz_data 示例:
{
"roleId": 10,
"roleName": "销售经理",
"permissions": [
{ "permCode": "order:view", "scopeCode": "CITY", "entityIds": ["101", "102"] }
],
"expireTime": "2027-03-05T00:00:00"
}
审计日志只追加,不更新。所有授权变更操作完成后同步写入一条记录,配合
revoked_at字段可完整还原权限的生命周期。
三、关键设计决策说明
3.1 同一用户同一权限点可以有多条记录
场景:张三通过"销售角色"获得了order:view(永久),管理员又单独给他授予了order:export(30天)。
user_id | permission_id | source_type | source_id | expire_time
张三 | order:view | 1(角色) | 销售角色ID | NULL(永久)
张三 | order:export | 2(单独) | NULL | 2026-04-01
查询时取所有 status=1 AND (expire_time IS NULL OR expire_time > NOW()) 的记录并集。
3.2 角色变更不影响已有授权
Role 只是批量授权的入口,授权完成后,记录独立存在。
如果角色新增了一个权限点,不会自动同步给已授权的用户,需要重新授权(或提供"同步角色权限"的操作)。
这样设计保证了授权记录的稳定性,避免角色变更产生意外的权限扩散。
如果业务需要"角色变更自动同步",可以通过
source_id(配合source_type=1)找到所有关联用户批量更新,但需要明确产品策略。
3.3 撤销授权的两种粒度
-- 撤销单个权限点(同时记录撤销时间)
UPDATE sys_user_permission_grant
SET status = 0, revoked_at = NOW()
WHERE id = #{grantId};
-- 撤销某次角色授权(批量撤销该角色带来的所有权限点)
UPDATE sys_user_permission_grant
SET status = 0, revoked_at = NOW()
WHERE user_id = #{userId} AND source_type = 1 AND source_id = #{roleId};
3.4 超管优先级与判断链路
鉴权链路中,超管判断优先于一切权限点校验,按以下顺序执行:
1. JWT 解析 → 拿到 userId + tenantId
↓
2. 查 sys_user_tenant.is_super
→ is_super = 1:租户内超管,直接放行,跳过所有后续校验
↓
3. 查 sys_user_role JOIN sys_role WHERE is_super = 1
→ 用户持有超管角色:直接放行
↓
4. 普通权限点校验:查 sys_user_permission_grant
↓
5. 数据范围注入:根据 scope_code 注入 WHERE 条件
两种超管的区别:
sys_user_tenant.is_super:平台运营直接在用户-租户关系上打标,不占用角色资源sys_role.is_super:通过分配超管角色实现,可以走正常的角色管理流程授权/撤销,更灵活
四、授权流程
4.1 通过角色批量授权
1. 管理员选择用户 + 角色
2. 系统查询角色下所有权限点(sys_role_permission JOIN sys_permission)
3. 对每个权限点,查出其关联的数据资源点及可选范围
4. 管理员逐一配置:数据范围 + 过期时间(可批量设置默认值)
5. 提交 → 批量写入 sys_user_permission_grant(每个权限点一条记录)
source_type = 1, source_id = 角色ID
4.2 单独授权某个权限点
1. 管理员选择用户 + 权限点
2. 系统展示该权限点关联的数据资源点及可选范围
3. 管理员配置:数据范围 + 过期时间
4. 提交 → 写入一条 sys_user_permission_grant
source_type = 2, source_id = NULL
五、运行时校验流程
5.1 加载用户权限
SELECT
p.perm_code,
g.data_resource_id,
g.scope_code,
g.expire_time
FROM sys_user_permission_grant g
JOIN sys_permission p ON p.id = g.permission_id
WHERE g.user_id = #{userId}
AND g.status = 1
AND (g.expire_time IS NULL OR g.expire_time > NOW())
5.2 操作权限校验(AOP)
@RequiresPermission("order:view")
// AOP 拦截 → 检查用户权限集合中是否包含 "order:view"
5.3 数据权限注入(MyBatis 拦截器)
GrantInfo grant = permissionCache.getGrant(userId, "order:view");
switch (grant.getScopeCode()) {
case "ALL": // 不追加条件
case "DEPT_AND_CHILD": // WHERE dept_id IN (当前部门及子部门)
case "DEPT": // WHERE dept_id = #{deptId}
case "SELF": // WHERE created_by = #{userId}
case "CITY": // WHERE city_id IN (#{entityIds})
case "PROJECT": // WHERE project_id IN (#{entityIds})
}
六、缓存设计
| 缓存 Key | 内容 | TTL |
|---|---|---|
perm:user:{userId}:grants |
用户所有有效授权列表 | 动态计算 |
perm:resource:{resourceId}:scopes |
数据资源点可选范围元数据 | 120分钟 |
sys:dept:tree |
全局组织架构树 | 120分钟 |
TTL 动态计算:
long ttl = grants.stream()
.filter(g -> g.getExpireTime() != null)
.mapToLong(g -> Duration.between(now, g.getExpireTime()).getSeconds())
.min()
.orElse(1800);
ttl = Math.min(ttl + 60, 1800); // 最长不超过30分钟
缓存失效触发点:
- 新增/撤销/修改授权 → 失效
perm:user:{userId}:grants - 授权到期(定时任务扫描)→ 失效
perm:user:{userId}:grants
七、系统架构图

八、ER 图

九、接口设计
接口分两侧:管理侧(权限配置与授权操作)和使用侧(鉴权与权限查询)。
租户隔离说明: 所有接口通过 JWT payload 中的
tenant_id隔离租户,无需在请求参数中显式传递。切换租户需调用/auth/switch-tenant重新签发 JWT。
| 维度 | 管理侧 | 使用侧 |
|---|---|---|
| 调用方 | 管理后台 | 业务前端 / 业务服务 / 网关 |
| 鉴权方式 | 需要管理员角色权限 | 只需登录态(JWT) |
| 操作特征 | 写多读少 | 只读,高频 |
| 缓存策略 | 写完主动失效缓存 | 全走缓存,不直接查库 |
9.1 管理侧接口
权限点管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /permissions |
查询权限点列表 |
| POST | /permissions |
新建权限点 |
| PUT | /permissions/{id} |
修改权限点 |
| DELETE | /permissions/{id} |
删除权限点 |
数据资源点管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /data-resources |
查询数据资源点列表 |
| POST | /data-resources |
新建数据资源点 |
| GET | /data-resources/{id}/scopes |
查询某资源点的可选范围列表 |
| POST | /data-resources/{id}/scopes |
新增可选范围 |
| DELETE | /data-resources/{id}/scopes/{scopeCode} |
删除可选范围 |
范围数据字典(外部同步入口)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /scope-data/sync |
外部系统批量同步范围数据(UPSERT) |
| GET | /scope-data/{scopeCode} |
查询某 scopeCode 下的实体列表 |
角色管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /roles |
查询角色列表 |
| POST | /roles |
新建角色 |
| PUT | /roles/{id} |
修改角色 |
| DELETE | /roles/{id} |
删除角色 |
| GET | /roles/{id}/permissions |
查询角色下的权限点列表 |
| PUT | /roles/{id}/permissions |
更新角色权限点,全量覆盖 |
用户授权管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /users/{userId}/roles |
查询用户持有的角色列表 |
| POST | /users/{userId}/grant/by-role |
通过角色批量授权 |
| POST | /users/{userId}/grant/by-permission |
单独授权某个权限点 |
| PUT | /grants/{grantId} |
修改单条授权 |
| DELETE | /grants/{grantId} |
撤销单条授权 |
| DELETE | /users/{userId}/roles/{roleId}/grants |
撤销某角色带来的所有授权 |
| PUT | /users/{userId}/roles/{roleId}/expire |
续期某角色授权 |
POST /users/{userId}/grant/by-role 请求体示例:
{
"roleId": 10,
"startTime": "2026-03-05T00:00:00",
"expireTime": "2027-03-05T00:00:00",
"scopeConfigs": [
{
"permissionId": 101,
"dataResourceId": 1,
"scopeCode": "CITY",
"entityIds": ["101", "102", "108"]
},
{
"permissionId": 102,
"dataResourceId": 1,
"scopeCode": "SELF"
}
]
}
9.2 使用侧接口
权限加载
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /auth/me/permissions |
返回当前用户权限点、数据范围、菜单树 |
响应体示例:
{
"permCodes": ["order:view", "order:export", "customer:edit"],
"dataScopes": {
"order:view": { "scopeCode": "CITY", "entityIds": ["101", "102"] },
"customer:edit": { "scopeCode": "SELF" }
},
"menus": [
{
"permCode": "menu:order",
"name": "订单管理",
"path": "/order",
"icon": "order-icon",
"sort": 1,
"children": [
{ "permCode": "menu:order:list", "name": "订单列表", "path": "/order/list", "sort": 1 }
]
}
]
}
菜单是权限点中
type=1的数据,不单独提供菜单接口,由本接口统一返回。
鉴权校验
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /auth/check |
校验某 userId 是否拥有某 permCode |
| GET | /auth/data-scope |
查询用户在某权限点上的数据范围 |
| POST | /auth/cache/refresh/{userId} |
主动刷新某用户权限缓存 |
十、多租户扩展方案
适用场景:平台需要支持多个租户(App)完全隔离,每个租户有自己独立的角色、权限点配置,用户可同时属于多个租户并自由切换身份。
10.1 核心设计思路
租户完全隔离 + 用户跨租户共享 + 租户级超管
- sys_role、sys_permission、sys_data_resource 等配置表加 tenant_id,数据完全隔离
- sys_user 不加 tenant_id,用户是平台级共享身份
- 用户与租户的归属关系通过 sys_user_tenant 管理
- 鉴权时 JWT 携带当前 tenant_id,所有查询强制带 tenant_id 过滤
- 超管标记在 sys_user_tenant 上,只在该租户内生效
10.2 新增表
租户表 sys_tenant
CREATE TABLE sys_tenant (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_code VARCHAR(64) NOT NULL UNIQUE COMMENT '租户编码',
tenant_name VARCHAR(128) NOT NULL COMMENT '租户名称',
status TINYINT DEFAULT 1 COMMENT '1正常 0禁用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
用户-租户关系表 sys_user_tenant
CREATE TABLE sys_user_tenant (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
is_super TINYINT DEFAULT 0 COMMENT '1该租户下的超管,跳过权限校验',
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_tenant (user_id, tenant_id),
KEY idx_user_id (user_id),
KEY idx_tenant_id (tenant_id)
);
10.3 现有表字段改动
ALTER TABLE sys_role ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_permission ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_data_resource ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_data_resource_scope ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_user_role ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_user_permission_grant ADD COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID';
ALTER TABLE sys_user_permission_grant ADD INDEX idx_tenant_user (tenant_id, user_id);
ALTER TABLE sys_role ADD INDEX idx_tenant (tenant_id);
ALTER TABLE sys_permission ADD INDEX idx_tenant (tenant_id);
不需要加 tenant_id 的表:
| 表 | 原因 |
|---|---|
sys_user |
用户是平台级共享身份,通过 sys_user_tenant 关联租户 |
sys_scope_data |
范围数据字典全局共享(城市、区域等不属于某个租户) |
sys_grant_scope_entity |
跟随 sys_user_permission_grant,grant 有 tenant_id 即可 |
sys_dept |
部门可按需决定,若需租户隔离则加,否则通过 grant 的 tenant_id 间接隔离 |
10.4 鉴权链路改动
Long userId = token.getUserId();
Long tenantId = token.getTenantId();
boolean isSuperAdmin = userTenantService.isSuper(userId, tenantId);
if (isSuperAdmin) return;
Set<String> permCodes = permissionService.getPermCodes(userId, tenantId);
GrantInfo grant = permissionService.getGrant(userId, tenantId, "order:view");
10.5 用户切换租户
GET /auth/me/tenants 查询当前用户所属的租户列表
POST /auth/switch-tenant 切换租户,返回新 token
GET /auth/me/tenants 响应示例:
{
"tenants": [
{ "tenantId": 1, "tenantName": "华东事业部", "isSuper": false },
{ "tenantId": 2, "tenantName": "华南事业部", "isSuper": true }
],
"current": 1
}
10.6 缓存 Key 加入 tenant_id
| 缓存 Key | TTL |
|---|---|
perm:tenant:{tenantId}:user:{userId}:grants |
动态 |
perm:tenant:{tenantId}:role:{roleId}:perms |
60min |
perm:tenant:{tenantId}:resource:{id}:scopes |
120min |
sys:tenant:{tenantId}:dept:tree |
120min |
10.7 改动量总结
| 改动项 | 内容 |
|---|---|
| 新增表 | sys_tenant、sys_user_tenant |
| 字段改动 | 6 张核心表加 tenant_id |
| 鉴权链路 | JWT 带 tenant_id,所有查询加 tenant_id 过滤 |
| 缓存 Key | 全部加 tenant_id 维度 |
| 接口新增 | /auth/me/tenants、/auth/switch-tenant |
现有权限模型无需推倒重建,多租户是在原有模型上套了一层租户维度的壳,改动最小化。