package cn.iocoder.yudao.framework.tenant.config;
|
|
import cn.iocoder.yudao.framework.common.biz.system.tenant.TenantCommonApi;
|
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
|
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
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.TenantJobAspect;
|
import cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;
|
import cn.iocoder.yudao.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;
|
import cn.iocoder.yudao.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;
|
import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
|
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.service.TenantFrameworkServiceImpl;
|
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
|
import cn.iocoder.yudao.framework.tenant.core.web.TenantVisitContextInterceptor;
|
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 jakarta.annotation.Resource;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
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.ApplicationContext;
|
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Primary;
|
import org.springframework.data.redis.cache.BatchStrategies;
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
import org.springframework.data.redis.cache.RedisCacheWriter;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
|
import java.util.*;
|
|
@AutoConfiguration
|
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
|
@EnableConfigurationProperties(TenantProperties.class)
|
public class YudaoTenantAutoConfiguration {
|
|
@Resource
|
private ApplicationContext applicationContext;
|
|
@Bean
|
public TenantFrameworkService tenantFrameworkService(TenantCommonApi tenantApi) {
|
return new TenantFrameworkServiceImpl(tenantApi);
|
}
|
|
// ========== 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;
|
}
|
|
@Bean
|
public TenantVisitContextInterceptor tenantVisitContextInterceptor(TenantProperties tenantProperties,
|
SecurityFrameworkService securityFrameworkService) {
|
return new TenantVisitContextInterceptor(tenantProperties, securityFrameworkService);
|
}
|
|
@Bean
|
public WebMvcConfigurer tenantWebMvcConfigurer(TenantProperties tenantProperties,
|
TenantVisitContextInterceptor tenantVisitContextInterceptor) {
|
return new WebMvcConfigurer() {
|
|
@Override
|
public void addInterceptors(InterceptorRegistry registry) {
|
registry.addInterceptor(tenantVisitContextInterceptor)
|
.excludePathPatterns(tenantProperties.getIgnoreVisitUrls().toArray(new String[0]));
|
}
|
};
|
}
|
|
// ========== Security ==========
|
|
@Bean
|
public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties,
|
WebProperties webProperties,
|
GlobalExceptionHandler globalExceptionHandler,
|
TenantFrameworkService tenantFrameworkService) {
|
FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
|
registrationBean.setFilter(new TenantSecurityWebFilter(webProperties, tenantProperties, getTenantIgnoreUrls(),
|
globalExceptionHandler, tenantFrameworkService));
|
registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
|
return registrationBean;
|
}
|
|
/**
|
* 如果 Controller 接口上,有 {@link TenantIgnore} 注解,则添加到忽略租户的 URL 集合中
|
*
|
* @return 忽略租户的 URL 集合
|
*/
|
private Set<String> getTenantIgnoreUrls() {
|
Set<String> ignoreUrls = new HashSet<>();
|
// 获得接口对应的 HandlerMethod 集合
|
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
|
applicationContext.getBean("requestMappingHandlerMapping");
|
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
// 获得有 @TenantIgnore 注解的接口
|
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
|
HandlerMethod handlerMethod = entry.getValue();
|
if (!handlerMethod.hasMethodAnnotation(TenantIgnore.class) // 方法级
|
&& !handlerMethod.getBeanType().isAnnotationPresent(TenantIgnore.class)) { // 接口级
|
continue;
|
}
|
// 添加到忽略的 URL 中
|
ignoreUrls.addAll(entry.getKey().getPatternValues());
|
}
|
return ignoreUrls;
|
}
|
|
// ========== MQ ==========
|
|
@Bean
|
public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
|
return new TenantRedisMessageInterceptor();
|
}
|
|
@Bean
|
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
public TenantRabbitMQInitializer tenantRabbitMQInitializer() {
|
return new TenantRabbitMQInitializer();
|
}
|
|
@Bean
|
@ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate")
|
public TenantRocketMQInitializer tenantRocketMQInitializer() {
|
return new TenantRocketMQInitializer();
|
}
|
|
// ========== Job ==========
|
|
@Bean
|
public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
|
return new TenantJobAspect(tenantFrameworkService);
|
}
|
|
// ========== Redis ==========
|
|
@Bean
|
@Primary // 引入租户时,tenantRedisCacheManager 为主 Bean
|
public RedisCacheManager tenantRedisCacheManager(RedisTemplate<String, Object> redisTemplate,
|
RedisCacheConfiguration redisCacheConfiguration,
|
YudaoCacheProperties yudaoCacheProperties,
|
TenantProperties tenantProperties) {
|
// 创建 RedisCacheWriter 对象
|
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
|
BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize()));
|
// 创建 TenantRedisCacheManager 对象
|
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches());
|
}
|
|
}
|