mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	【新增】新增 @TenantIgnore 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑
				
					
				
			This commit is contained in:
		| @@ -0,0 +1,106 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.hutool.core.annotation.AnnotationUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | ||||
| import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; | ||||
| import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; | ||||
| import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| @Configuration | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantAutoConfiguration { | ||||
|  | ||||
|     // ========== AOP ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantIgnoreAspect tenantIgnoreAspect() { | ||||
|         return new TenantIgnoreAspect(); | ||||
|     } | ||||
|  | ||||
|     // ========== DB ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties, | ||||
|                                                                  MybatisPlusInterceptor interceptor) { | ||||
|         TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); | ||||
|         // 添加到 interceptor 中 | ||||
|         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 | ||||
|         MyBatisUtils.addInterceptor(interceptor, inner, 0); | ||||
|         return inner; | ||||
|     } | ||||
|  | ||||
|     // ========== WEB ========== | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() { | ||||
|         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantContextWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
|     // ========== Security ========== | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                                                                    WebProperties webProperties, | ||||
|                                                                                    GlobalExceptionHandler globalExceptionHandler, | ||||
|                                                                                    TenantFrameworkService tenantFrameworkService) { | ||||
|         FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties, | ||||
|                 globalExceptionHandler, tenantFrameworkService)); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
|     // ========== MQ ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { | ||||
|         return new TenantRedisMessageInterceptor(); | ||||
|     } | ||||
|  | ||||
|     // ========== Job ========== | ||||
|  | ||||
|     @Bean | ||||
|     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||
|     public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) { | ||||
|         return new BeanPostProcessor() { | ||||
|  | ||||
|             @Override | ||||
|             public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|                 if (!(bean instanceof JobHandler)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|                 // 有 TenantJob 注解的情况下,才会进行处理 | ||||
|                 if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|  | ||||
|                 // 使用 TenantJobHandlerDecorator 装饰 | ||||
|                 return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 DB 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantDatabaseAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties, | ||||
|                                                                  MybatisPlusInterceptor interceptor) { | ||||
|         TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); | ||||
|         // 添加到 interceptor 中 | ||||
|         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 | ||||
|         MyBatisUtils.addInterceptor(interceptor, inner, 0); | ||||
|         return inner; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,46 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.hutool.core.annotation.AnnotationUtil; | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Job 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantJobAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||
|     public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) { | ||||
|         return new BeanPostProcessor() { | ||||
|  | ||||
|             @Override | ||||
|             public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|                 if (!(bean instanceof JobHandler)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|                 // 有 TenantJob 注解的情况下,才会进行处理 | ||||
|                 if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|  | ||||
|                 // 使用 TenantJobHandlerDecorator 装饰 | ||||
|                 return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 MQ 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantMQAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { | ||||
|         return new TenantRedisMessageInterceptor(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Web 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantSecurityAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                                                                    WebProperties webProperties, | ||||
|                                                                                    GlobalExceptionHandler globalExceptionHandler, | ||||
|                                                                                    TenantFrameworkService tenantFrameworkService) { | ||||
|         FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties, | ||||
|                 globalExceptionHandler, tenantFrameworkService)); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Web 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) | ||||
| public class YudaoTenantWebAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() { | ||||
|         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantContextWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.aop; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 忽略租户,标记指定方法不进行租户的自动过滤 | ||||
|  * | ||||
|  * 注意,只有 DB 的场景会过滤,其它场景暂时不过滤: | ||||
|  * 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的 | ||||
|  * 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Inherited | ||||
| public @interface TenantIgnore { | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.aop; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
|  | ||||
| /** | ||||
|  * 忽略多租户的 Aspect,基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。 | ||||
|  * 例如说,一个定时任务,读取所有数据,进行处理。 | ||||
|  * 又例如说,读取所有数据,进行缓存。 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Aspect | ||||
| @Slf4j | ||||
| public class TenantIgnoreAspect { | ||||
|  | ||||
|     @Around("@annotation(tenantIgnore)") | ||||
|     public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable { | ||||
|         Boolean oldIgnore = TenantContextHolder.isIgnore(); | ||||
|         try { | ||||
|             TenantContextHolder.setIgnore(true); | ||||
|             // 执行逻辑 | ||||
|             return joinPoint.proceed(); | ||||
|         } finally { | ||||
|             TenantContextHolder.setIgnore(oldIgnore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -9,8 +9,16 @@ import com.alibaba.ttl.TransmittableThreadLocal; | ||||
|  */ | ||||
| public class TenantContextHolder { | ||||
|  | ||||
|     /** | ||||
|      * 当前租户编号 | ||||
|      */ | ||||
|     private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 是否忽略租户 | ||||
|      */ | ||||
|     private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 获得租户编号。 | ||||
|      * | ||||
| @@ -37,8 +45,22 @@ public class TenantContextHolder { | ||||
|         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(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
| import lombok.AllArgsConstructor; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| import net.sf.jsqlparser.expression.StringValue; | ||||
| import net.sf.jsqlparser.expression.LongValue; | ||||
|  | ||||
| /** | ||||
|  * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能 | ||||
| @@ -20,12 +20,13 @@ public class TenantDatabaseInterceptor implements TenantLineHandler { | ||||
|  | ||||
|     @Override | ||||
|     public Expression getTenantId() { | ||||
|         return new StringValue(TenantContextHolder.getRequiredTenantId().toString()); | ||||
|         return new LongValue( TenantContextHolder.getRequiredTenantId()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean ignoreTable(String tableName) { | ||||
|         return CollUtil.contains(properties.getIgnoreTables(), tableName); | ||||
|         return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户 | ||||
|             || CollUtil.contains(properties.getIgnoreTables(), tableName); // 情况二,忽略多租户的表 | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,14 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.job; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker; | ||||
|  | ||||
| /** | ||||
|  * 多租户 JobHandlerInvoker 拓展实现类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class TenantJobHandlerInvoker extends JobHandlerInvoker { | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -12,17 +12,23 @@ public class TenantUtils { | ||||
|     /** | ||||
|      * 使用指定租户,执行对应的逻辑 | ||||
|      * | ||||
|      * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户 | ||||
|      * 当然,执行完成后,还是会恢复回去 | ||||
|      * | ||||
|      * @param tenantId 租户编号 | ||||
|      * @param runnable 逻辑 | ||||
|      */ | ||||
|     public static void execute(Long tenantId, Runnable runnable) { | ||||
|         Long oldTenantId = TenantContextHolder.getTenantId(); | ||||
|         Boolean oldIgnore = TenantContextHolder.isIgnore(); | ||||
|         try { | ||||
|             TenantContextHolder.setTenantId(tenantId); | ||||
|             TenantContextHolder.setIgnore(false); | ||||
|             // 执行逻辑 | ||||
|             runnable.run(); | ||||
|         } finally { | ||||
|             TenantContextHolder.setTenantId(oldTenantId); | ||||
|             TenantContextHolder.setIgnore(oldIgnore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV