# 多租户功能
多租户功能基于“同一套系统服务 + 租户数据隔离”实现。项目中既有共库共表按 tenant_id 隔离的场景,也有按租户分库、分表的场景,因此租户ID不仅影响 MyBatis-Plus 的多租户过滤,也会影响分库分表、业务数据路由和跨服务调用。
如果对 MyBatis-Plus 多租户拦截器不熟悉,可以先看官方文档:
https://mybatis.plus/guide/interceptor-tenant-line.html
1. 功能说明
- 一个租户通常对应一个独立的业务主体,例如一个商城。
- 同一租户下可以有多个展示端,例如 App、小程序、H5,但读取的是同一份租户业务数据。
- 有租户约束的表,各租户数据默认互相隔离。
- 无租户约束的表,属于平台公共数据,不按租户隔离。
- 不要在业务代码中手工维护
tenant_id,系统会自动维护当前请求租户上下文。
2. 租户表配置
需要做租户隔离的业务表,需要:
- 表结构增加
tenant_id字段 - 在对应服务的 Nacos 配置中声明该表启用租户拦截
示例:
base:
tenant:
column: tenant_id
tables:
- wx_app
- wx_menu
- wx_user
- wx_auto_reply
- wx_msg
- wx_mass_msg
上面的配置表示这些表会自动追加 tenant_id 过滤条件。
3. 后台租户ID自动维护
后台请求的租户ID自动维护,核心逻辑在:
TenantContextHolderFilter.java
该过滤器是最高优先级执行的过滤器,主要职责是:
- 从请求中识别当前租户
- 将租户ID写入
TenantContextHolder - 让后续 MyBatis 多租户、分库分表、Feign 透传统一使用同一个租户上下文
- 请求完成后清理
ThreadLocal,避免串租户
3.1 处理总流程
后台请求进入后,过滤器按下面的顺序处理:
- 先读取
Authorization请求头 - 如果有 Bearer Token,则到 Redis 中查出当前 token 对应的登录用户
- 从登录用户中取出默认租户ID
- 如果满足平台管理员切换租户条件,再尝试使用
switch-tenant-id - 将最终租户ID写入
TenantContextHolder - 请求执行完成后调用
TenantContextHolder.clear()
涉及的关键请求头:
Authorizationswitch-tenant-idtenant-id
涉及的关键常量:
CommonConstants.SYSTEM_TENANT_ID = "-1"CommonConstants.USER_TYPE_S = "-1"SecurityConstants.SWITCH_TENANT_ID = "switch-tenant-id"CommonConstants.TENANT_ID = "tenant-id"
3.2 情况1:租户账号登录
租户账号登录后,token 中的用户信息本身就带有所属租户ID。
处理逻辑:
TenantContextHolderFilter从 Bearer Token 对应的登录用户中取出baseUser.getTenantId()- 该租户ID直接写入
TenantContextHolder - 后续数据库查询、分库路由、Feign 调用都使用这个租户ID
也就是说:
- 租户账号不需要传
switch-tenant-id - 系统会自动 set 成该账号所属的租户ID
- 该账号只能管理自己租户的数据
3.3 情况2:平台管理员账号登录
平台管理员是多租户管理模式,不适合一次查出所有租户数据统一处理,原因是:
- 系统本身是多租户机制
- 数据库根据实际业务可能做了分库、分表
- 不同业务模块的数据量和路由规则都可能不同
因此平台管理员管理租户数据的方式不是“一次查所有租户”,而是“先切换租户,再按当前租户上下文处理”。
平台管理员切换租户生效需要同时满足以下条件:
- 当前请求有 Bearer Token
- 当前登录用户类型是平台管理员,即
user.type = -1 - 当前登录用户默认租户ID是系统租户,即
tenantId = -1 - 请求头传入了
switch-tenant-id switch-tenant-id在当前平台管理员被授权可管理的租户列表中
满足后,过滤器会把本次请求租户切换为 switch-tenant-id。
也就是说:
- 平台管理员登录后,默认不是直接查“所有租户数据”
- 平台管理员需要先切换到目标租户,再管理该租户的数据
- 切换后的租户ID由
TenantContextHolderFilter自动维护
4. 哪些后台页面会传 switch-tenant-id
前端是否传 switch-tenant-id,核心逻辑在:
和:
4.1 普通租户业务页面
在普通租户业务页面中,如果浏览器当前已经保存了切换租户ID,并且当前页面不是平台页面,请求拦截器会自动带上:
switch-tenant-id: 当前切换的租户ID
这样后台就能在 TenantContextHolderFilter 中识别出当前要管理哪个租户的数据。
4.2 平台功能页面不传 switch-tenant-id
后台并不是所有菜单都区分租户。
例如:
- 微信、商城这类业务功能,通常区分租户
- 平台管理、平台供应商这类平台功能,通常不区分租户
前端通过 judgePlatformPage() 判断当前是否属于平台页面。当前代码中,以下路径会被判定为平台类页面,不自动附带 switch-tenant-id:
#/platform/#/platform-supplier/#/info/index#/authredirect#/tool#/login
这类页面下的接口请求,不会在通用请求拦截器中自动带 switch-tenant-id。
这样设计的目的,是避免平台公共功能误带租户上下文,导致把本应查平台公共数据的请求错误地路由到某个租户数据中。
4.3 特殊接口说明
通用请求拦截器里还有两个特殊点:
/upms/phone/code这类验证码接口,不传switch-tenant-id,而是传tenant-id- 如果明确需要跨租户请求数据,可以使用
requestTenantData()手工指定switch-tenant-id
5. 后台自动维护与分库分表的关系
后台自动维护租户ID不是只为了给 SQL 拼 tenant_id 条件,它还会影响:
- MyBatis-Plus 多租户 SQL 拦截
- 分库分表路由
- 动态数据源
- Feign 调用时的租户透传
例如:
- 后台当前请求被识别为租户
1001 TenantContextHolder中保存的是1001- 本地查询会自动按
tenant_id = 1001过滤 - 如有分库规则,也会按
1001路由到对应租户库 - Feign 调用其他服务时,会通过
BaseFeignTenantInterceptor自动透传tenant-id - 下游服务再继续按相同租户上下文处理
因此后台租户管理必须走“切换租户 -> 自动维护上下文 -> 正常查询”的方式,不能简单理解为只是前端传一个参数。
6. 前端用户端租户ID自动维护
前端用户端不是走后台登录 token 这一套,而是通过:
ThirdSessionInterceptor
自动维护租户上下文。
典型逻辑是:
- 从
thirdSession关联的用户信息中识别租户ID - 必要时从请求头
tenant-id中读取租户 - 写入
TenantContextHolder
因此后台管理端和用户端的租户识别机制不同:
- 后台管理端:
TenantContextHolderFilter - 用户端:
ThirdSessionInterceptor
7. 无租户ID场景
7.1 平台公共表或公共查询
如果某个 Mapper 方法不需要租户隔离,可以在方法上加:
@InterceptorIgnore(tenantLine = "true")
SysUser getByNoTenant(SysUser sysUser);
加上后,MyBatis-Plus 不会自动追加租户条件。
7.2 没有 token 但仍需要租户
有些请求没有后台 token,也没有用户端 thirdSession,但仍需要租户上下文。
例如:
- 某些回调接口
- 某些特殊开放接口
这类场景可以先按业务标识查出租户ID,再手工设置:
TenantContextHolder.setTenantId(tenantId);
7.3 完全不区分租户
平台公共功能、本身不受租户隔离的表或接口,可以不设置租户,或者明确把租户切回系统租户。
8. 常用方法
获取当前租户:
TenantContextHolder.getTenantId()
手工设置租户:
TenantContextHolder.setTenantId(tenantId)
清理租户:
TenantContextHolder.clear()
注意:
- 正常增删改查过程中,不要手工给
tenant_id赋值 - 也不要绕过
TenantContextHolderFilter自己拼租户参数 - 否则容易出现串租户、分库路由错误或 SQL 结果异常
9. 新增租户
- 在后台【平台管理】->【租户管理】中新增租户
- 为租户配置租户管理员账号
- 给租户管理员分配对应菜单、角色和数据权限
- 租户管理员登录后,系统会自动带入所属租户ID
租户表:
sys_tenant
10. 平台用户管理指定租户
新增租户后,可以把租户分配给指定的平台管理员。
分配后,平台管理员可以在后台左上角切换租户,再管理该租户的数据。
典型流程:
- 平台管理员登录
- 左上角切换到目标租户
- 前端请求自动带
switch-tenant-id TenantContextHolderFilter自动把当前请求租户切换为目标租户- 后续查询、分库、跨服务调用都按该租户执行
如果当前访问的是平台功能页面,则不会自动带 switch-tenant-id,接口将按平台公共逻辑处理。