MybatisPlus多租户插件的使用

举报
KevinQ 发表于 2023/04/19 11:24:34 2023/04/19
【摘要】 实现过程描述实现过程可以总结为:实现TenantLineHandler接口: KevinQTenantLineHandler定义一个类TenantContextHolder,通过TheadLocal存储与获取当前请求线程的租户编号定义Web过滤器TenantContextWebFilter,拦截请求,获取header中的租户编号,并通过步骤2的类来设置值通过MybatisPlus的接口,添...

实现过程描述

实现过程可以总结为:

  1. 实现TenantLineHandler接口: KevinQTenantLineHandler
  2. 定义一个类TenantContextHolder,通过TheadLocal存储与获取当前请求线程的租户编号
  3. 定义Web过滤器TenantContextWebFilter,拦截请求,获取header中的租户编号,并通过步骤2的类来设置值
  4. 通过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。

遗留的一些问题

  1. 可能有一些接口也需要忽略租户ID,如基础的校验码获取等,需要配置与修改
  2. 如何快速配置忽略的数据库table表,硬改代码不是我们的风格。
  3. 如果涉及到定时任务,redis等,又该如何区分租户呢?
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。