系统多租户使用
flowchart LR
Start[用户登录] --> QueryUser[查询sys_user表]
QueryUser --> CheckUser{验证用户信息}
CheckUser -->|验证失败| Failed[登录失败]
CheckUser -->|验证成功| CheckTenant{用户是否属于多个租户}
CheckTenant -->|否,单个租户| GetTenantId[获取用户所属租户ID]
CheckTenant -->|是,多个租户| SelectTenant[选择默认租户 sys_user.tenant_id]
GetTenantId --> LoginSuccess[登录成功]
SelectTenant --> LoginSuccess
LoginSuccess --> CheckOperation[用户启动操作]
CheckOperation --> DataFilter[数据库操作]
DataFilter --> AutoFilter[自动添加租户ID过滤条件]
AutoFilter --> OpType{操作类型}
OpType --> Query[查询操作]
OpType --> Add[新增操作]
OpType --> Modify[修改操作]
OpType --> Delete[删除操作]
租户操作
flowchart LR
A[Admin登录] --> B[默认租户1]
B --> C[租户管理]
C --> D[创建租户B]
D --> E[租户B【用户】</br> 邀请admin </br>(同时属于 1、B两个租户)]
E --> F[切换租户B<br/>右上角个人中心]
F --> G[初始化租户B</br>组织架构]
G --> H[配置部门 <br/> 数据自动关联租户B]
G -.切换回租户1.-> B
G --> I[配置角色 <br/>数据自动关联租户B]
G --> J[配置人员<br/>数据自动关联租户B]
新增租户
通过系统管理 > 租户管理功能可以新增租户。新增租户后,需要维护租户人员信息,并进行强刷操作切换到目标租户。
💡操作流程
新增租户后需要完成人员配置和系统强刷,才能正常使用新租户功能。
- 新增租户:在租户管理页面点击"新增"按钮,填写租户基本信息
- 维护租户人员:为新增的租户配置管理员和相关用户
- 强刷系统:重新加载租户配置信息
- 切换租户:在前端界面切换到目标租户进行操作
使用方法
⚠多租户实现方式
多个租户共享同一个数据库(Database)和模式(Schema
通过表中的 TenantID 字段区分不同租户的数据
系统会自动维护租户 ID,开发时无需手动处理
数据表配置
在需要支持多租户的表中添加 tenant_id 字段:
租户隔离配置
有两种配置方式:
高级用法
手动切换租户
⛔禁止直接设置
禁止在实体对象中直接设置 tenant 字段,这会导致错误。正确的切换方式是使用 TenantContextHolder。
// 手动设置租户ID(在查询前调用)
TenantContextHolder.setTenantId(1); // 切换到租户ID为1的数据
// 执行查询操作,此时强制查询到租户ID为1的用户数据
List<User> users = baseMapper.selectList(null);
跳过租户过滤
在需要查询所有租户数据时:
// 跳过租户过滤,查询所有租户的数据
TenantContextHolder.setTenantSkip();
// 执行查询操作,此时会查询所有租户的用户数据
List<User> allUsers = baseMapper.selectList(null);
示例:
关闭多租户功能
在 PIGX 中,多租户是一个"按需使用"的能力,如果业务不需要租户,默认也不会影响系统运行。
系统的多租户逻辑依赖三部分:前端的租户选择、HTTP Header 中的租户标识、数据库字段 tenant_id。因此,只要关闭前端入口或数据库不包含 tenant 字段,就不会触发多租户隔离行为。
前端关闭(推荐)
✓推荐方式
适用于:业务只有一个租户,但未来可能会扩展,保留扩展空间。
当前端不展示"租户管理""租户切换"等菜单时:
- 用户不会主动切换租户
- 前端不会传递 TENANT_ID
- 服务端自动使用默认租户(一般为租户 ID 1)
前端屏蔽方式
- 在权限管理菜单中隐藏租户管理页面
- 在系统顶部导航中隐藏租户切换入口
- 删除或隐藏相关按钮即可,无需改动服务端代码
技术实现原理
系统基于 MyBatis-Plus 的多租户 SQL 解析器实现,主要包含:
租户 ID 传递
通过全局过滤器获取并存储租户 ID:
public class TenantContextHolderFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
// 从请求头获取租户ID
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
// 设置租户上下文
if (StrUtil.isNotBlank(tenantId)) {
TenantContextHolder.setTenantId(Integer.parseInt(tenantId));
} else {
TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
}
filterChain.doFilter(request, response);
TenantContextHolder.clear();
}
}
跨线程租户信息传递
使用阿里巴巴的 TransmittableThreadLocal 实现:
public class TenantContextHolder {
private final ThreadLocal<Integer> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();
}