MybatisPlus多租户插件的使用
【摘要】 实现过程描述实现过程可以总结为:实现TenantLineHandler接口: KevinQTenantLineHandler定义一个类TenantContextHolder,通过TheadLocal存储与获取当前请求线程的租户编号定义Web过滤器TenantContextWebFilter,拦截请求,获取header中的租户编号,并通过步骤2的类来设置值通过MybatisPlus的接口,添...
实现过程描述
实现过程可以总结为:
- 实现TenantLineHandler接口:
KevinQTenantLineHandler
- 定义一个类
TenantContextHolder
,通过TheadLocal存储与获取当前请求线程的租户编号 - 定义Web过滤器
TenantContextWebFilter
,拦截请求,获取header中的租户编号,并通过步骤2的类来设置值 - 通过MybatisPlus的接口,添加拦截器:
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new KevinQTenantLineHandler()));
具体实现
MybatisPlus官方文档列了一个接口,显然,我们需要实现该接口:
/**
* @author qww
* 2023/4/11 18:27 */public class KevinQTenantLineHandler implements TenantLineHandler {
/**
* 获取当前执行上下文中的tenant-id
* @return
*/
@Override
public Expression getTenantId() {
return new LongValue(TenantContextHolder.getRequiredTenantId());
}
/**
* 租户id的列名
* @return
*/
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
/**
* 是否忽略某些表
* @param tableName
* @return
*/
@Override
public boolean ignoreTable(String tableName) {
return false;
}
}
上述代码中有一个类TenantContextHolder
,这个类是一个通过ThreadLocal变量来存储租户ID的类,具体实现如下:
/**
* 多租户上下文 Holder
*/
public class TenantContextHolder {
/**
* 当前租户编号
*/
private static final ThreadLocal<Long> TENANT_ID = new ThreadLocal<>();
/**
* 是否忽略租户
*/
private static final ThreadLocal<Boolean> IGNORE = new ThreadLocal<>();
/**
* 获得租户编号。
*
* @return 租户编号
*/
public static Long getTenantId() {
return TENANT_ID.get();
}
/**
* 获得租户编号。如果不存在,则抛出 NullPointerException 异常
*
* @return 租户编号
*/
public static Long getRequiredTenantId() {
Long tenantId = getTenantId();
if (tenantId == null) {
throw new NullPointerException("TenantContextHolder 不存在租户编号");
}
return tenantId;
}
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static void setIgnore(Boolean ignore) {
IGNORE.set(ignore);
}
/**
* 当前是否忽略租户
*
* @return 是否忽略
*/
public static boolean isIgnore() {
return Boolean.TRUE.equals(IGNORE.get());
}
public static void clear() {
TENANT_ID.remove();
IGNORE.remove();
}
}
其中通过setTenantId与getTenantId分别来设置与获取当下线程中的租户ID。
另外我们还需要通过一个filter来获取前端页面上传过来的租户ID值:
/**
* 多租户 Context Web 过滤器
*/
@Component("tenantContextWebFilter")
public class TenantContextWebFilter extends OncePerRequestFilter {
public static final String HEADER_TENANT_ID = "tanent-id";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 设置
Long tenantId = getTenantId(request);
if (tenantId != null) {
TenantContextHolder.setTenantId(tenantId);
}
try {
chain.doFilter(request, response);
} finally {
// 清理
TenantContextHolder.clear();
}
}
/**
* 获得租户编号,从 header 中
* 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
*
* @param request 请求
* @return 租户编号
*/
public static Long getTenantId(HttpServletRequest request) {
String tenantId = request.getHeader(HEADER_TENANT_ID);
return StrUtil.isNotEmpty(tenantId) ? Long.valueOf(tenantId) : null;
}
}
最后,我们需要给MybatisPlus添加拦截器,该拦截器使用我们实现的接口:
/**
* mybatis configuration
*/
@ConditionalOnClass({MybatisPlusAutoConfiguration.class, SqlSessionFactory.class, SqlSessionFactoryBean.class})
@Configuration
public class KevinQMybatisAutoConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(ObjectProvider<MybatisPlusInterceptorCustomizer> customizerObjectProvider) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加拦截器,注意多个拦截器的添加顺序
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new KevinQTenantLineHandler()));
return interceptor;
}
}
此时,若我们不对前端做修改,则会报错:租户编号不存在。
需要我们前端的小伙伴在请求的header部分添加tenant-id=xx
。
后端一方面是需要在数据库中需要分租户的表添加字段tenant_id,并且需要在代码中配置哪些表需要忽略租户ID:tenant-id。
遗留的一些问题
- 可能有一些接口也需要忽略租户ID,如基础的校验码获取等,需要配置与修改
- 如何快速配置忽略的数据库table表,硬改代码不是我们的风格。
- 如果涉及到定时任务,redis等,又该如何区分租户呢?
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)