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 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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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.");
}
}

View File

@@ -16,45 +16,51 @@
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) {
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);
// 切换数据源
if (TenantIsolationLevel.DATASOURCE.equals(tenantHandler.getIsolationLevel())) {
//切换数据源
dataSourceHandler.changeDataSource(tenantHandler.getDataSource());
isPush = true;
}
// 执行业务逻辑
runnable.run();
} finally {
// 恢复原始的租户上下文
@@ -68,6 +74,4 @@ public class DefaultTenantHandler implements TenantHandler {
}
}
}
}
}

View File

@@ -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,7 +48,9 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
@Override
public void changeDataSource(TenantDataSource tenantDataSource) {
if (tenantDataSource != null) {
if (tenantDataSource == null) {
return;
}
String dataSourceName = tenantDataSource.getPoolName();
if (!this.containsDataSource(dataSourceName)) {
DataSource datasource = this.createDataSource(tenantDataSource);
@@ -57,7 +60,6 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
DynamicDataSourceContextHolder.push(dataSourceName);
log.info("Change data source: {}", dataSourceName);
}
}
@Override
public boolean containsDataSource(String dataSourceName) {

View File

@@ -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;

View File

@@ -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,11 +39,13 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) {
if (TenantIsolationLevel.LINE.equals(TenantContextHolder.getIsolationLevel())) {
return invocation.proceed();
}
// 切换数据源
boolean isPush = false;
try {
tenantDataSourceHandler.changeDataSource(TenantContextHolder.getContext().getDataSource());
tenantDataSourceHandler.changeDataSource(TenantContextHolder.getDataSource());
isPush = true;
return invocation.proceed();
} finally {
@@ -51,7 +54,4 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
}
}
}
return invocation.proceed();
}
}

View File

@@ -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;