微服务项目中由于默认开启了租户隔离,但是有些情况下需要个别方法不启用。
为了实现这个目标自定义了一个忽略租户隔离的注解:
@IgnoreTenant
将他加在方法上即可,例如:
@IgnoreTenantpublic CrmSmsTemplate getTemplateByCodeAndTenandtId(String code, Integer tenantId) {return crmSmsTemplateMapper.selectOne(new QueryWrapper<CrmSmsTemplate>().eq("code", code).eq("tenant_id", tenantId));}
实现步骤:
1.首先定义MybatisPlus的配置类创建一个拦截器MybatisPlusInterceptor
将MybatisPlusSaasConfig中的mybatisPlusInterceptor方法修改
package org.jeecg.config.mybatis;import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** 单数据源配置(jeecg.datasource.open = false时生效)* @Author zhoujf**/
@Configuration
@MapperScan(value={"org.jeecg.modules.**.mapper*"})
public class MybatisPlusSaasConfig {/*** 是否开启系统模块的租户隔离* 控制范围:用户、角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告** 实现功能* 1.用户表通过硬编码实现租户ID隔离* 2.角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告除了硬编码还加入的 TENANT_TABLE 配置中,实现租户隔离更安全* 3.菜单表、租户表不做租户隔离* 4.通过拦截器MybatisInterceptor实现,增删改查数据 自动注入租户ID*/public static final Boolean OPEN_SYSTEM_TENANT_CONTROL = true;/*** 哪些表需要做多租户 表需要添加一个字段 tenant_id*/public static final List<String> TENANT_TABLE = new ArrayList<String>();static {//1.需要租户隔离的表请在此配置if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {//a.系统管理表// TENANT_TABLE.add("sys_user");TENANT_TABLE.add("sys_role");TENANT_TABLE.add("crm_sys_config");TENANT_TABLE.add("sys_queue");//TENANT_TABLE.add("sys_announcement");}//2.示例测试//TENANT_TABLE.add("demo");//3.online租户隔离测试//TENANT_TABLE.add("ceapp_issue");}@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptorinterceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {String tenantId = TenantContext.getTenant();//如果通过线程获取租户ID为空,则通过当前请求的request获取租户(shiro排除拦截器的请求会获取不到租户ID)if(oConvertUtils.isEmpty(tenantId)){try {tenantId = TokenUtils.getTenantIdByRequest(SpringContextUtils.getHttpServletRequest());} catch (Exception e) {//e.printStackTrace();}}if(oConvertUtils.isEmpty(tenantId)){tenantId = "0";}return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn(){return TenantConstant.TENANT_ID_TABLE;}// 返回 true 表示不走租户逻辑@Overridepublic boolean ignoreTable(String tableName) {tableName=tableName.replace("`","");for(String temp: TENANT_TABLE){if(temp.equalsIgnoreCase(tableName)){if (Objects.nonNull(MybatisTenantContext.get())){return MybatisTenantContext.get();}return false; // 其他表进行租户过滤}}return true; // 返回 true,表示不对这些表进行租户过滤}}));//update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());//update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题interceptor.addInnerInterceptor(new PaginationInnerInterceptor());//【jeecg-boot/issues/3847】增加@Version乐观锁支持 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}/*** 动态表名切换拦截器,用于适配vue2和vue3同一个表有多个的情况,如sys_role_index在vue3情况下表名为sys_role_index_v3* @return*/private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {//获取需要动态解析的表名String dynamicTableName = ThreadLocalDataHelper.get(CommonConstant.DYNAMIC_TABLE_NAME);//当dynamicTableName不为空时才走动态表名处理逻辑,否则返回原始表名if (ObjectUtil.isNotEmpty(dynamicTableName) && dynamicTableName.equals(tableName)) {// 获取前端传递的版本号标识Object version = ThreadLocalDataHelper.get(CommonConstant.VERSION);if (ObjectUtil.isNotEmpty(version)) {//拼接表名规则(原始表名+下划线+前端传递的版本号)return tableName + "_" + version;}}return tableName;});return dynamicTableNameInnerInterceptor;}}
2,定义一个ThreadLocal本地线程变量 MybatisTenantContext用于维护是否开启租户隔离变量
package org.jeecg.config.mybatis;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();}
}
3.
自定义注解IgnoreTenant
package org.jeecg.config.filter;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface IgnoreTenant {/*** true为不做租户隔离 false为做租户隔离* @return*/boolean isIgnore() default true;
}
4.注解实现类
ps:如果方法或者类上有其他注解用到租户隔离的,如:日志注解,字典翻译注解在point.proceed()后执行逻辑。需要注意切面类的执行顺序,一定要保证TenantIgnoreAspect 先执行,不然其它注解还是会有租户隔离的情况。可以在TenantIgnoreAspect 切面类加上@Order(Integer.MIN_VALUE)注解 保证执行顺序
package org.jeecg.config.filter;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.config.mybatis.MybatisTenantContext;
import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Objects;@Aspect
@Slf4j
@Component
@Order(Integer.MIN_VALUE)
public class TenantIgnoreAspect {/*** 切入点*/@Pointcut("@within(org.jeecg.config.filter.IgnoreTenant) ||@annotation(org.jeecg.config.filter.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();}}
}
到此为止就可以使用注解:@IgnoreTenant
参考:MyBatis-Plus忽略多租户隔离自定义注解_mybaits plus 忽略租户隔离的注解-CSDN博客