mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 10:57:15 +08:00 
			
		
		
		
	refactor(extension/tenant): 优化多租户模块代码及包结构
This commit is contained in:
		| @@ -18,6 +18,7 @@ package top.continew.starter.extension.tenant.autoconfigure; | ||||
|  | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import top.continew.starter.core.constant.PropertiesConstants; | ||||
| import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @@ -35,6 +36,11 @@ public class TenantProperties { | ||||
|      */ | ||||
|     private boolean enabled = true; | ||||
|  | ||||
|     /** | ||||
|      * 租户隔离级别 | ||||
|      */ | ||||
|     private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE; | ||||
|  | ||||
|     /** | ||||
|      * 租户 ID 列名 | ||||
|      */ | ||||
| @@ -63,6 +69,14 @@ public class TenantProperties { | ||||
|         this.enabled = enabled; | ||||
|     } | ||||
|  | ||||
|     public TenantIsolationLevel getIsolationLevel() { | ||||
|         return isolationLevel; | ||||
|     } | ||||
|  | ||||
|     public void setIsolationLevel(TenantIsolationLevel isolationLevel) { | ||||
|         this.isolationLevel = isolationLevel; | ||||
|     } | ||||
|  | ||||
|     public String getTenantIdColumn() { | ||||
|         return tenantIdColumn; | ||||
|     } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ package top.continew.starter.extension.tenant.config; | ||||
| import top.continew.starter.extension.tenant.context.TenantContext; | ||||
|  | ||||
| /** | ||||
|  * 租户数据提供者 | ||||
|  * 租户提供者 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2.7.0 | ||||
| @@ -27,11 +27,11 @@ import top.continew.starter.extension.tenant.context.TenantContext; | ||||
| public interface TenantProvider { | ||||
|  | ||||
|     /** | ||||
|      * 根据租户ID获取租户数据 | ||||
|      * 根据租户 ID 获取租户上下文 | ||||
|      * | ||||
|      * @param tenantId 租户 ID | ||||
|      * @param verify   是否验证租户有效性 | ||||
|      * @return 数据源配置 | ||||
|      * @param isVerify 是否验证有效性 | ||||
|      * @return 租户上下文 | ||||
|      */ | ||||
|     TenantContext getByTenantId(String tenantId, boolean verify); | ||||
|     TenantContext getByTenantId(String tenantId, boolean isVerify); | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,10 @@ | ||||
|  | ||||
| package top.continew.starter.extension.tenant.context; | ||||
|  | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.alibaba.ttl.TransmittableThreadLocal; | ||||
| import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; | ||||
| import top.continew.starter.extension.tenant.config.TenantDataSource; | ||||
| import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; | ||||
|  | ||||
| import java.util.Optional; | ||||
| @@ -69,11 +72,22 @@ public class TenantContextHolder { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取隔离级别 | ||||
|      * 获取租户隔离级别 | ||||
|      * | ||||
|      * @return 租户隔离级别 | ||||
|      */ | ||||
|     public static TenantIsolationLevel getIsolationLevel() { | ||||
|         return Optional.ofNullable(getContext()) | ||||
|             .map(TenantContext::getIsolationLevel) | ||||
|             .orElse(TenantIsolationLevel.LINE); | ||||
|             .orElse(SpringUtil.getBean(TenantProperties.class).getIsolationLevel()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取租户数据源 | ||||
|      * | ||||
|      * @return 租户数据源 | ||||
|      */ | ||||
|     public static TenantDataSource getDataSource() { | ||||
|         return Optional.ofNullable(getContext()).map(TenantContext::getDataSource).orElse(null); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,6 +31,7 @@ public interface TenantDataSourceHandler { | ||||
|     /** | ||||
|      * 切换数据源 | ||||
|      * | ||||
|      * @param tenantDataSource 数据源配置 | ||||
|      */ | ||||
|     void changeDataSource(TenantDataSource tenantDataSource); | ||||
|  | ||||
|   | ||||
| @@ -17,15 +17,18 @@ | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
|  | ||||
| /** | ||||
|  * @description: 租户处理器 | ||||
|  * @author: 小熊 | ||||
|  * @create: 2024-12-18 19:37 | ||||
|  * 租户处理器 | ||||
|  * | ||||
|  * @author 小熊 | ||||
|  * @since 2.8.0 | ||||
|  */ | ||||
| public interface TenantHandler { | ||||
|  | ||||
|     /** | ||||
|      * 在指定租户中执行方法 | ||||
|      * 在指定租户中执行 | ||||
|      * | ||||
|      * @param tenantId 租户 ID | ||||
|      * @param runnable 方法 | ||||
|      */ | ||||
|     void executeInTenant(Long tenantId, Runnable runnable); | ||||
|  | ||||
|     void execute(Long tenantId, Runnable runnable); | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; | ||||
| import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import jakarta.annotation.PostConstruct; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.NoSuchBeanDefinitionException; | ||||
| @@ -31,10 +32,16 @@ import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.core.ResolvableType; | ||||
| import top.continew.starter.core.constant.PropertiesConstants; | ||||
| import top.continew.starter.extension.tenant.config.TenantProvider; | ||||
| import top.continew.starter.extension.tenant.handler.*; | ||||
| import top.continew.starter.extension.tenant.handler.DefaultTenantHandler; | ||||
| import top.continew.starter.extension.tenant.handler.TenantDataSourceHandler; | ||||
| import top.continew.starter.extension.tenant.handler.TenantHandler; | ||||
| import top.continew.starter.extension.tenant.handler.datasource.DefaultTenantDataSourceHandler; | ||||
| import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceAdvisor; | ||||
| import top.continew.starter.extension.tenant.handler.datasource.TenantDataSourceInterceptor; | ||||
| import top.continew.starter.extension.tenant.handler.line.DefaultTenantLineHandler; | ||||
|  | ||||
| /** | ||||
|  * 多租户自动配置 | ||||
|  * 租户自动配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2.7.0 | ||||
| @@ -45,12 +52,10 @@ import top.continew.starter.extension.tenant.handler.*; | ||||
| public class TenantAutoConfiguration { | ||||
|  | ||||
|     private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class); | ||||
|     private final TenantProperties tenantProperties; | ||||
|  | ||||
|     private TenantAutoConfiguration() { | ||||
|     } | ||||
|  | ||||
|     static { | ||||
|         log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization."); | ||||
|     private TenantAutoConfiguration(TenantProperties tenantProperties) { | ||||
|         this.tenantProperties = tenantProperties; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -100,11 +105,11 @@ public class TenantAutoConfiguration { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 多租户数据源提供者 | ||||
|      * 租户提供者 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public TenantProvider tenantDataSourceProvider() { | ||||
|     public TenantProvider tenantProvider() { | ||||
|         if (log.isErrorEnabled()) { | ||||
|             log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType | ||||
|                 .forClass(TenantProvider.class)); | ||||
| @@ -118,7 +123,11 @@ public class TenantAutoConfiguration { | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public TenantHandler tenantHandler(TenantDataSourceHandler tenantDataSourceHandler, TenantProvider tenantProvider) { | ||||
|         return new DefaultTenantHandler(tenantDataSourceHandler, tenantProvider); | ||||
|         return new DefaultTenantHandler(tenantProperties, tenantDataSourceHandler, tenantProvider); | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public void postConstruct() { | ||||
|         log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization."); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,58 +16,62 @@ | ||||
|  | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
|  | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; | ||||
| import top.continew.starter.extension.tenant.autoconfigure.TenantProperties; | ||||
| import top.continew.starter.extension.tenant.config.TenantProvider; | ||||
| import top.continew.starter.extension.tenant.context.TenantContext; | ||||
| import top.continew.starter.extension.tenant.context.TenantContextHolder; | ||||
| import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; | ||||
|  | ||||
| /** | ||||
|  * @description: 租户处理器 | ||||
|  * @author: 小熊 | ||||
|  * @create: 2024-12-18 19:43 | ||||
|  * 租户处理器 | ||||
|  * | ||||
|  * @author 小熊 | ||||
|  * @since 2.8.0 | ||||
|  */ | ||||
| public class DefaultTenantHandler implements TenantHandler { | ||||
|  | ||||
|     private final TenantProperties tenantProperties; | ||||
|     private final TenantDataSourceHandler dataSourceHandler; | ||||
|     private final TenantProvider tenantProvider; | ||||
|  | ||||
|     public DefaultTenantHandler(TenantDataSourceHandler dataSourceHandler, TenantProvider tenantProvider) { | ||||
|     public DefaultTenantHandler(TenantProperties tenantProperties, | ||||
|                                 TenantDataSourceHandler dataSourceHandler, | ||||
|                                 TenantProvider tenantProvider) { | ||||
|         this.tenantProperties = tenantProperties; | ||||
|         this.dataSourceHandler = dataSourceHandler; | ||||
|         this.tenantProvider = tenantProvider; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void executeInTenant(Long tenantId, Runnable runnable) { | ||||
|         boolean enabled = SpringUtil.getProperty("continew-starter.tenant.enabled", Boolean.class, false); | ||||
|         if (enabled) { | ||||
|             TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false); | ||||
|             // 保存当前的租户上下文 | ||||
|             TenantContext originalContext = TenantContextHolder.getContext(); | ||||
|     public void execute(Long tenantId, Runnable runnable) { | ||||
|         if (!tenantProperties.isEnabled()) { | ||||
|             return; | ||||
|         } | ||||
|         TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false); | ||||
|         // 保存当前的租户上下文 | ||||
|         TenantContext originalContext = TenantContextHolder.getContext(); | ||||
|         boolean isPush = false; | ||||
|         try { | ||||
|             // 设置新的租户上下文 | ||||
|             TenantContextHolder.setContext(tenantHandler); | ||||
|             // 切换数据源 | ||||
|             boolean isPush = false; | ||||
|             try { | ||||
|                 // 设置新的租户上下文 | ||||
|                 TenantContextHolder.setContext(tenantHandler); | ||||
|                 if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) { | ||||
|                     //切换数据源 | ||||
|                     dataSourceHandler.changeDataSource(tenantHandler.getDataSource()); | ||||
|                     isPush = true; | ||||
|                 } | ||||
|                 runnable.run(); | ||||
|             } finally { | ||||
|                 // 恢复原始的租户上下文 | ||||
|                 if (originalContext != null) { | ||||
|                     TenantContextHolder.setContext(originalContext); | ||||
|                 } else { | ||||
|                     TenantContextHolder.clearContext(); | ||||
|                 } | ||||
|                 if (isPush) { | ||||
|                     DynamicDataSourceContextHolder.poll(); | ||||
|                 } | ||||
|             if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) { | ||||
|                 dataSourceHandler.changeDataSource(tenantHandler.getDataSource()); | ||||
|                 isPush = true; | ||||
|             } | ||||
|             // 执行业务逻辑 | ||||
|             runnable.run(); | ||||
|         } finally { | ||||
|             // 恢复原始的租户上下文 | ||||
|             if (originalContext != null) { | ||||
|                 TenantContextHolder.setContext(originalContext); | ||||
|             } else { | ||||
|                 TenantContextHolder.clearContext(); | ||||
|             } | ||||
|             if (isPush) { | ||||
|                 DynamicDataSourceContextHolder.poll(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
| package top.continew.starter.extension.tenant.handler.datasource; | ||||
| 
 | ||||
| import cn.hutool.core.text.CharSequenceUtil; | ||||
| import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; | ||||
| @@ -24,6 +24,7 @@ import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import top.continew.starter.extension.tenant.config.TenantDataSource; | ||||
| import top.continew.starter.extension.tenant.handler.TenantDataSourceHandler; | ||||
| 
 | ||||
| import javax.sql.DataSource; | ||||
| 
 | ||||
| @@ -47,16 +48,17 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler { | ||||
| 
 | ||||
|     @Override | ||||
|     public void changeDataSource(TenantDataSource tenantDataSource) { | ||||
|         if (tenantDataSource != null) { | ||||
|             String dataSourceName = tenantDataSource.getPoolName(); | ||||
|             if (!this.containsDataSource(dataSourceName)) { | ||||
|                 DataSource datasource = this.createDataSource(tenantDataSource); | ||||
|                 dynamicRoutingDataSource.addDataSource(dataSourceName, datasource); | ||||
|                 log.info("Load data source: {}", dataSourceName); | ||||
|             } | ||||
|             DynamicDataSourceContextHolder.push(dataSourceName); | ||||
|             log.info("Change data source: {}", dataSourceName); | ||||
|         if (tenantDataSource == null) { | ||||
|             return; | ||||
|         } | ||||
|         String dataSourceName = tenantDataSource.getPoolName(); | ||||
|         if (!this.containsDataSource(dataSourceName)) { | ||||
|             DataSource datasource = this.createDataSource(tenantDataSource); | ||||
|             dynamicRoutingDataSource.addDataSource(dataSourceName, datasource); | ||||
|             log.info("Load data source: {}", dataSourceName); | ||||
|         } | ||||
|         DynamicDataSourceContextHolder.push(dataSourceName); | ||||
|         log.info("Change data source: {}", dataSourceName); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
| package top.continew.starter.extension.tenant.handler.datasource; | ||||
| 
 | ||||
| import org.aopalliance.aop.Advice; | ||||
| import org.springframework.aop.Pointcut; | ||||
| @@ -14,13 +14,14 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
| package top.continew.starter.extension.tenant.handler.datasource; | ||||
| 
 | ||||
| import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; | ||||
| import org.aopalliance.intercept.MethodInterceptor; | ||||
| import org.aopalliance.intercept.MethodInvocation; | ||||
| import top.continew.starter.extension.tenant.context.TenantContextHolder; | ||||
| import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; | ||||
| import top.continew.starter.extension.tenant.handler.TenantDataSourceHandler; | ||||
| 
 | ||||
| /** | ||||
|  * 租户数据源级隔离拦截器 | ||||
| @@ -38,20 +39,19 @@ public class TenantDataSourceInterceptor implements MethodInterceptor { | ||||
| 
 | ||||
|     @Override | ||||
|     public Object invoke(MethodInvocation invocation) throws Throwable { | ||||
|         if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) { | ||||
|             // 切换数据源 | ||||
|             boolean isPush = false; | ||||
|             try { | ||||
|                 tenantDataSourceHandler.changeDataSource(TenantContextHolder.getContext().getDataSource()); | ||||
|                 isPush = true; | ||||
|                 return invocation.proceed(); | ||||
|             } finally { | ||||
|                 if (isPush) { | ||||
|                     DynamicDataSourceContextHolder.poll(); | ||||
|                 } | ||||
|         if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) { | ||||
|             return invocation.proceed(); | ||||
|         } | ||||
|         // 切换数据源 | ||||
|         boolean isPush = false; | ||||
|         try { | ||||
|             tenantDataSourceHandler.changeDataSource(TenantContextHolder.getDataSource()); | ||||
|             isPush = true; | ||||
|             return invocation.proceed(); | ||||
|         } finally { | ||||
|             if (isPush) { | ||||
|                 DynamicDataSourceContextHolder.poll(); | ||||
|             } | ||||
|         } | ||||
|         return invocation.proceed(); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.continew.starter.extension.tenant.handler; | ||||
| package top.continew.starter.extension.tenant.handler.line; | ||||
| 
 | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
		Reference in New Issue
	
	Block a user