赞
踩
做SaaS系统的小伙伴肯定对多租户不陌生,博主最近使用MybatisPlus的多租户插件时发现一些不方便的地方,因启用多租户时,租户之间是完全隔离的,现在需要一位管理员权限的用户在特定菜单功能下不能有租户隔离。常用的几种方法有:
@InterceptorIgnore(tenantLine = "true")
注解@InterceptorIgnore(tenantLine = "true")
public interface XXXMapper extends BaseMapper<XXX> {
List<XXX> selectList();
}
public interface XXXMapper extends BaseMapper<XXX> {
@InterceptorIgnore(tenantLine = "true")
List<XXX> selectList();
}
这种方式的缺点是如果在特定类上加注解就需要写两个mapper类,在方法上加的话需要创建两个Mapper接口实现BaseMapper,同时xml或者crud注解都需要写两份,这两种方式都比较繁琐。
但是这样的话多租户就失去了意义,直接行不通。
@InterceptorIgnore
注解更加灵活,以下是所有代码首先定义MybatisPlus的配置类创建一个拦截器MybatisPlusInterceptor
@Configuration @Slf4j public class MybatisPlusSaasConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { String tenantId = UserUtils.getLoginTenantId(); if(StringUtils.isAnyEmpty(tenantId)){ //默认租户id tenantId = "0"; } return new LongValue(tenantId); } @Override public String getTenantIdColumn(){ return "tenant_id"; } // 返回 true 表示不走租户逻辑 @Override public boolean ignoreTable(String tableName) { if (Objects.nonNull(MybatisTenantContext.get())){ log.info("是否做租户隔离:{}",MybatisTenantContext.get()); return MybatisTenantContext.get(); } //默认租户隔离 return false; } })); return interceptor; } }
定义一个ThreadLocal本地线程变量 MybatisTenantContext用于维护是否开启租户隔离变量
public class MybatisTenantContext {
private static final ThreadLocal<Boolean> TENANT_CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
public static Boolean get() {
return TENANT_CONTEXT_THREAD_LOCAL.get();
}
public static void set(boolean isIgnore){
TENANT_CONTEXT_THREAD_LOCAL.set(isIgnore);
}
public static void clear(){
TENANT_CONTEXT_THREAD_LOCAL.remove();
}
}
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface IgnoreTenant {
/**
* true为不做租户隔离 false为做租户隔离
* @return
*/
boolean isIgnore() default true;
}
注解切面类
ps:如果方法或者类上有其他注解用到租户隔离的,如:日志注解,字典翻译注解在point.proceed()后执行逻辑。需要注意切面类的执行顺序,一定要保证TenantIgnoreAspect 先执行,不然其它注解还是会有租户隔离的情况。可以在TenantIgnoreAspect 切面类加上@Order(Integer.MIN_VALUE)注解 保证执行顺序
@Aspect @Slf4j @Component public class TenantIgnoreAspect { /** * 切入点 */ @Pointcut("@within(com.xxx.IgnoreTenant) ||@annotation(com.xxx.IgnoreTenant)") public void pointcut() {} @Around("pointcut()") public Object around(ProceedingJoinPoint point) throws Throwable { try { Class<?> targetClass = point.getTarget().getClass(); IgnoreTenant classIgnoreTenant = targetClass.getAnnotation(IgnoreTenant.class); MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); IgnoreTenant methodIgnoreTenant = method.getAnnotation(IgnoreTenant.class); //判断类上是否有注解 boolean isClassAnnotated = AnnotationUtils.isAnnotationDeclaredLocally(IgnoreTenant.class, targetClass); //判断方法上是否有注解 boolean isMethodAnnotated = Objects.nonNull(methodIgnoreTenant); //如果类上有 if (isClassAnnotated) { MybatisTenantContext.set(classIgnoreTenant.isIgnore()); } //如果方法上有 以方法上的为主 if (isMethodAnnotated) { MybatisTenantContext.set(methodIgnoreTenant.isIgnore()); } Object result = point.proceed(); return result; }finally { MybatisTenantContext.clear(); } }
以上就是所有代码
使用示例
@Service
@IgnoreTenant
public class DemoService {
@IgnoreTenant
public List<String> demoList(){
return this.list();
};
}
ps:如果一个方法中有多个查询,但是只有特定查询需要忽略租户隔离,可以使用下面的方式
@Service
public class DemoService {
public List<String> demoList(String name){
try {
MybatisTenantContext.set(true);
this.listByName(name);
return this.list();
}finally {
MybatisTenantContext.clear();
}
}
}
以上代码是手动维护本地线程变量 MybatisTenantContext,不可以使用注解,使用完一定要记得clear。以上就是通过自定义注解忽略多租户隔离的实现方式,如果有小伙伴有更好的方式欢迎评论区提供建议。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。