refactor(extension/tenant): 优化多租户模块代码及包结构

This commit is contained in:
2024-12-23 20:56:32 +08:00
parent 0d334523e9
commit 613599f921
11 changed files with 128 additions and 81 deletions

View File

@@ -18,6 +18,7 @@ package top.continew.starter.extension.tenant.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import java.util.List; import java.util.List;
@@ -35,6 +36,11 @@ public class TenantProperties {
*/ */
private boolean enabled = true; private boolean enabled = true;
/**
* 租户隔离级别
*/
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
/** /**
* 租户 ID 列名 * 租户 ID 列名
*/ */
@@ -63,6 +69,14 @@ public class TenantProperties {
this.enabled = enabled; this.enabled = enabled;
} }
public TenantIsolationLevel getIsolationLevel() {
return isolationLevel;
}
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
this.isolationLevel = isolationLevel;
}
public String getTenantIdColumn() { public String getTenantIdColumn() {
return tenantIdColumn; return tenantIdColumn;
} }

View File

@@ -19,7 +19,7 @@ package top.continew.starter.extension.tenant.config;
import top.continew.starter.extension.tenant.context.TenantContext; import top.continew.starter.extension.tenant.context.TenantContext;
/** /**
* 租户数据提供者 * 租户提供者
* *
* @author Charles7c * @author Charles7c
* @since 2.7.0 * @since 2.7.0
@@ -27,11 +27,11 @@ import top.continew.starter.extension.tenant.context.TenantContext;
public interface TenantProvider { public interface TenantProvider {
/** /**
* 根据租户ID获取租户数据 * 根据租户 ID 获取租户上下文
* *
* @param tenantId 租户 ID * @param tenantId 租户 ID
* @param verify 是否验证租户有效性 * @param isVerify 是否验证有效性
* @return 数据源配置 * @return 租户上下文
*/ */
TenantContext getByTenantId(String tenantId, boolean verify); TenantContext getByTenantId(String tenantId, boolean isVerify);
} }

View File

@@ -16,7 +16,10 @@
package top.continew.starter.extension.tenant.context; package top.continew.starter.extension.tenant.context;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.ttl.TransmittableThreadLocal; 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 top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import java.util.Optional; import java.util.Optional;
@@ -69,11 +72,22 @@ public class TenantContextHolder {
} }
/** /**
* 获取隔离级别 * 获取租户隔离级别
*
* @return 租户隔离级别
*/ */
public static TenantIsolationLevel getIsolationLevel() { public static TenantIsolationLevel getIsolationLevel() {
return Optional.ofNullable(getContext()) return Optional.ofNullable(getContext())
.map(TenantContext::getIsolationLevel) .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);
} }
} }

View File

@@ -31,6 +31,7 @@ public interface TenantDataSourceHandler {
/** /**
* 切换数据源 * 切换数据源
* *
* @param tenantDataSource 数据源配置
*/ */
void changeDataSource(TenantDataSource tenantDataSource); void changeDataSource(TenantDataSource tenantDataSource);

View File

@@ -17,15 +17,18 @@
package top.continew.starter.extension.tenant.handler; package top.continew.starter.extension.tenant.handler;
/** /**
* @description: 租户处理器 * 租户处理器
* @author: 小熊 *
* @create: 2024-12-18 19:37 * @author 小熊
* @since 2.8.0
*/ */
public interface TenantHandler { public interface TenantHandler {
/** /**
* 在指定租户中执行方法 * 在指定租户中执行
*
* @param tenantId 租户 ID
* @param runnable 方法
*/ */
void executeInTenant(Long tenantId, Runnable runnable); void execute(Long tenantId, Runnable runnable);
} }

View File

@@ -20,6 +20,7 @@ import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -31,10 +32,16 @@ import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.extension.tenant.config.TenantProvider; 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 * @author Charles7c
* @since 2.7.0 * @since 2.7.0
@@ -45,12 +52,10 @@ import top.continew.starter.extension.tenant.handler.*;
public class TenantAutoConfiguration { public class TenantAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(TenantAutoConfiguration.class);
private final TenantProperties tenantProperties;
private TenantAutoConfiguration() { private TenantAutoConfiguration(TenantProperties tenantProperties) {
} this.tenantProperties = tenantProperties;
static {
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization.");
} }
/** /**
@@ -100,11 +105,11 @@ public class TenantAutoConfiguration {
} }
/** /**
* 租户数据源提供者 * 租户提供者
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TenantProvider tenantDataSourceProvider() { public TenantProvider tenantProvider() {
if (log.isErrorEnabled()) { if (log.isErrorEnabled()) {
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
.forClass(TenantProvider.class)); .forClass(TenantProvider.class));
@@ -118,7 +123,11 @@ public class TenantAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TenantHandler tenantHandler(TenantDataSourceHandler tenantDataSourceHandler, TenantProvider tenantProvider) { 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.");
}
} }

View File

@@ -16,45 +16,51 @@
package top.continew.starter.extension.tenant.handler; package top.continew.starter.extension.tenant.handler;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; 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.config.TenantProvider;
import top.continew.starter.extension.tenant.context.TenantContext; import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.context.TenantContextHolder; import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; 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 { public class DefaultTenantHandler implements TenantHandler {
private final TenantProperties tenantProperties;
private final TenantDataSourceHandler dataSourceHandler; private final TenantDataSourceHandler dataSourceHandler;
private final TenantProvider tenantProvider; 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.dataSourceHandler = dataSourceHandler;
this.tenantProvider = tenantProvider; this.tenantProvider = tenantProvider;
} }
@Override @Override
public void executeInTenant(Long tenantId, Runnable runnable) { public void execute(Long tenantId, Runnable runnable) {
boolean enabled = SpringUtil.getProperty("continew-starter.tenant.enabled", Boolean.class, false); if (!tenantProperties.isEnabled()) {
if (enabled) { return;
}
TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false); TenantContext tenantHandler = tenantProvider.getByTenantId(tenantId.toString(), false);
// 保存当前的租户上下文 // 保存当前的租户上下文
TenantContext originalContext = TenantContextHolder.getContext(); TenantContext originalContext = TenantContextHolder.getContext();
// 切换数据源
boolean isPush = false; boolean isPush = false;
try { try {
// 设置新的租户上下文 // 设置新的租户上下文
TenantContextHolder.setContext(tenantHandler); TenantContextHolder.setContext(tenantHandler);
if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) {
// 切换数据源 // 切换数据源
if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) {
dataSourceHandler.changeDataSource(tenantHandler.getDataSource()); dataSourceHandler.changeDataSource(tenantHandler.getDataSource());
isPush = true; isPush = true;
} }
// 执行业务逻辑
runnable.run(); runnable.run();
} finally { } finally {
// 恢复原始的租户上下文 // 恢复原始的租户上下文
@@ -69,5 +75,3 @@ public class DefaultTenantHandler implements TenantHandler {
} }
} }
} }
}

View File

@@ -14,7 +14,7 @@
* limitations under the License. * 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 cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
@@ -24,6 +24,7 @@ import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import top.continew.starter.extension.tenant.config.TenantDataSource; import top.continew.starter.extension.tenant.config.TenantDataSource;
import top.continew.starter.extension.tenant.handler.TenantDataSourceHandler;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -47,7 +48,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
@Override @Override
public void changeDataSource(TenantDataSource tenantDataSource) { public void changeDataSource(TenantDataSource tenantDataSource) {
if (tenantDataSource != null) { if (tenantDataSource == null) {
return;
}
String dataSourceName = tenantDataSource.getPoolName(); String dataSourceName = tenantDataSource.getPoolName();
if (!this.containsDataSource(dataSourceName)) { if (!this.containsDataSource(dataSourceName)) {
DataSource datasource = this.createDataSource(tenantDataSource); DataSource datasource = this.createDataSource(tenantDataSource);
@@ -57,7 +60,6 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
DynamicDataSourceContextHolder.push(dataSourceName); DynamicDataSourceContextHolder.push(dataSourceName);
log.info("Change data source: {}", dataSourceName); log.info("Change data source: {}", dataSourceName);
} }
}
@Override @Override
public boolean containsDataSource(String dataSourceName) { public boolean containsDataSource(String dataSourceName) {

View File

@@ -14,7 +14,7 @@
* limitations under the License. * 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.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut; import org.springframework.aop.Pointcut;

View File

@@ -14,13 +14,14 @@
* limitations under the License. * 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 com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import top.continew.starter.extension.tenant.context.TenantContextHolder; import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel; import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
import top.continew.starter.extension.tenant.handler.TenantDataSourceHandler;
/** /**
* 租户数据源级隔离拦截器 * 租户数据源级隔离拦截器
@@ -38,11 +39,13 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
@Override @Override
public Object invoke(MethodInvocation invocation) throws Throwable { public Object invoke(MethodInvocation invocation) throws Throwable {
if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) { if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) {
return invocation.proceed();
}
// 切换数据源 // 切换数据源
boolean isPush = false; boolean isPush = false;
try { try {
tenantDataSourceHandler.changeDataSource(TenantContextHolder.getContext().getDataSource()); tenantDataSourceHandler.changeDataSource(TenantContextHolder.getDataSource());
isPush = true; isPush = true;
return invocation.proceed(); return invocation.proceed();
} finally { } finally {
@@ -51,7 +54,4 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
} }
} }
} }
return invocation.proceed();
}
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * 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 cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;