mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-11-04 18:59:22 +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