mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-25 22:57:08 +08:00
feat(extension/tenant): 新增多租户数据源级隔离支持
This commit is contained in:
@@ -18,5 +18,17 @@
|
|||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>transmittable-thread-local</artifactId>
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
@@ -14,22 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package top.continew.starter.data.mp.autoconfigure;
|
package top.continew.starter.extension.tenant.annotation;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否启用数据权限注解
|
* 多租户数据源级隔离忽略注解
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.1.0
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@ConditionalOnProperty(prefix = "mybatis-plus.extension.data-permission", name = PropertiesConstants.ENABLED, havingValue = "true")
|
public @interface TenantDataSourceIgnore {
|
||||||
public @interface ConditionalOnEnabledDataPermission {
|
|
||||||
}
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
package top.continew.starter.extension.tenant.autoconfigure;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户拦截器
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class TenantInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final TenantProperties tenantProperties;
|
||||||
|
|
||||||
|
public TenantInterceptor(TenantProperties tenantProperties) {
|
||||||
|
this.tenantProperties = tenantProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
||||||
|
TenantContext tenantContext = new TenantContext();
|
||||||
|
tenantContext.setTenantId(Convert.toLong(tenantId));
|
||||||
|
TenantContextHolder.setContext(tenantContext);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -46,6 +46,11 @@ public class TenantProperties {
|
|||||||
*/
|
*/
|
||||||
private String tenantIdColumn = "tenant_id";
|
private String tenantIdColumn = "tenant_id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头中租户 ID 键名
|
||||||
|
*/
|
||||||
|
private String tenantIdHeader = "X-Tenant-Id";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 超级租户 ID
|
* 超级租户 ID
|
||||||
*/
|
*/
|
||||||
@@ -80,6 +85,14 @@ public class TenantProperties {
|
|||||||
this.tenantIdColumn = tenantIdColumn;
|
this.tenantIdColumn = tenantIdColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTenantIdHeader() {
|
||||||
|
return tenantIdHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantIdHeader(String tenantIdHeader) {
|
||||||
|
this.tenantIdHeader = tenantIdHeader;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getSuperTenantId() {
|
public Long getSuperTenantId() {
|
||||||
return superTenantId;
|
return superTenantId;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package top.continew.starter.extension.tenant.autoconfigure;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多租户 Web MVC 自动配置
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnWebApplication
|
||||||
|
@ConditionalOnProperty(prefix = PropertiesConstants.TENANT, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||||
|
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final TenantProperties tenantProperties;
|
||||||
|
|
||||||
|
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties) {
|
||||||
|
this.tenantProperties = tenantProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(new TenantInterceptor(tenantProperties));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,75 @@
|
|||||||
|
package top.continew.starter.extension.tenant.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源配置
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class TenantDataSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接池名称
|
||||||
|
*/
|
||||||
|
private String poolName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驱动类名
|
||||||
|
*/
|
||||||
|
private String driverClassName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库连接地址
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getPoolName() {
|
||||||
|
return poolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoolName(String poolName) {
|
||||||
|
this.poolName = poolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDriverClassName() {
|
||||||
|
return driverClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDriverClassName(String driverClassName) {
|
||||||
|
this.driverClassName = driverClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package top.continew.starter.extension.tenant.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源提供者
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public interface TenantDataSourceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据租户 ID 获取数据源配置
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 ID
|
||||||
|
* @return 数据源配置
|
||||||
|
*/
|
||||||
|
TenantDataSource getByTenantId(String tenantId);
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package top.continew.starter.extension.tenant.handler;
|
||||||
|
|
||||||
|
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离处理器
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public interface TenantDataSourceHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换数据源
|
||||||
|
*
|
||||||
|
* @param dataSourceName 数据源名称
|
||||||
|
*/
|
||||||
|
void changeDataSource(String dataSourceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否存在指定数据源
|
||||||
|
*
|
||||||
|
* @param dataSourceName 数据源名称
|
||||||
|
* @return 是否存在指定数据源
|
||||||
|
*/
|
||||||
|
boolean containsDataSource(String dataSourceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建数据源
|
||||||
|
*
|
||||||
|
* @param tenantDataSource 数据源配置
|
||||||
|
* @return 数据源
|
||||||
|
*/
|
||||||
|
DataSource createDataSource(TenantDataSource tenantDataSource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除数据源
|
||||||
|
*
|
||||||
|
* @param dataSourceName 数据源名称
|
||||||
|
*/
|
||||||
|
void removeDataSource(String dataSourceName);
|
||||||
|
}
|
@@ -16,17 +16,22 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.tenant.autoconfigure;
|
package top.continew.starter.extension.tenant.autoconfigure;
|
||||||
|
|
||||||
|
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.handler.TenantLineHandler;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||||
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.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
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.handler.DefaultTenantLineHandler;
|
import top.continew.starter.extension.tenant.config.TenantDataSourceProvider;
|
||||||
|
import top.continew.starter.extension.tenant.handler.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户自动配置
|
* 多租户自动配置
|
||||||
@@ -72,4 +77,55 @@ public class TenantAutoConfiguration {
|
|||||||
return new DefaultTenantLineHandler(properties);
|
return new DefaultTenantLineHandler(properties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户隔离级别:数据源级
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "datasource")
|
||||||
|
public static class DataSource {
|
||||||
|
static {
|
||||||
|
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-DataSource' completed initialization.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离通知
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantDataSourceAdvisor tenantDataSourceAdvisor(TenantDataSourceInterceptor tenantDataSourceInterceptor) {
|
||||||
|
return new TenantDataSourceAdvisor(tenantDataSourceInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离拦截器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantDataSourceInterceptor tenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||||
|
return new TenantDataSourceInterceptor(tenantDataSourceHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离处理器(默认)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantDataSourceHandler tenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider, DynamicRoutingDataSource dynamicRoutingDataSource, DefaultDataSourceCreator dataSourceCreator) {
|
||||||
|
return new DefaultTenantDataSourceHandler(tenantDataSourceProvider, dynamicRoutingDataSource, dataSourceCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多租户数据源提供者
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantDataSourceProvider tenantDataSourceProvider() {
|
||||||
|
if (log.isErrorEnabled()) {
|
||||||
|
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||||
|
.forClass(TenantDataSourceProvider.class));
|
||||||
|
}
|
||||||
|
throw new NoSuchBeanDefinitionException(TenantDataSourceProvider.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,70 @@
|
|||||||
|
package top.continew.starter.extension.tenant.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||||
|
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||||
|
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
|
||||||
|
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.config.TenantDataSourceProvider;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认租户数据源级隔离处理器
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
|
||||||
|
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||||
|
private final DefaultDataSourceCreator dataSourceCreator;
|
||||||
|
private final TenantDataSourceProvider tenantDataSourceProvider;
|
||||||
|
|
||||||
|
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider, DynamicRoutingDataSource dynamicRoutingDataSource, DefaultDataSourceCreator dataSourceCreator) {
|
||||||
|
this.tenantDataSourceProvider = tenantDataSourceProvider;
|
||||||
|
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
||||||
|
this.dataSourceCreator = dataSourceCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeDataSource(String dataSourceName) {
|
||||||
|
if (!this.containsDataSource(dataSourceName)) {
|
||||||
|
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
|
||||||
|
if (null == tenantDataSource) {
|
||||||
|
throw new IllegalArgumentException("Data source [%s] configuration not found".formatted(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
|
||||||
|
public boolean containsDataSource(String dataSourceName) {
|
||||||
|
return CharSequenceUtil.isNotBlank(dataSourceName) && dynamicRoutingDataSource.getDataSources()
|
||||||
|
.containsKey(dataSourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource createDataSource(TenantDataSource tenantDataSource) {
|
||||||
|
DataSourceProperty dataSourceProperty = new DataSourceProperty();
|
||||||
|
dataSourceProperty.setPoolName(tenantDataSource.getPoolName());
|
||||||
|
dataSourceProperty.setDriverClassName(tenantDataSource.getDriverClassName());
|
||||||
|
dataSourceProperty.setUrl(tenantDataSource.getUrl());
|
||||||
|
dataSourceProperty.setUsername(tenantDataSource.getUsername());
|
||||||
|
dataSourceProperty.setPassword(tenantDataSource.getPassword());
|
||||||
|
return dataSourceCreator.createDataSource(dataSourceProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeDataSource(String dataSourceName) {
|
||||||
|
dynamicRoutingDataSource.removeDataSource(dataSourceName);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
package top.continew.starter.extension.tenant.handler;
|
||||||
|
|
||||||
|
import org.aopalliance.aop.Advice;
|
||||||
|
import org.springframework.aop.Pointcut;
|
||||||
|
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||||
|
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||||
|
import org.springframework.aop.support.ComposablePointcut;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离通知
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
|
||||||
|
|
||||||
|
private final Advice advice;
|
||||||
|
private final Pointcut pointcut;
|
||||||
|
|
||||||
|
public TenantDataSourceAdvisor(TenantDataSourceInterceptor interceptor) {
|
||||||
|
this.advice = interceptor;
|
||||||
|
this.pointcut = buildPointcut();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pointcut getPointcut() {
|
||||||
|
return this.pointcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Advice getAdvice() {
|
||||||
|
return this.advice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||||
|
if (this.advice instanceof BeanFactoryAware beanFactoryAware) {
|
||||||
|
beanFactoryAware.setBeanFactory(beanFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建切点
|
||||||
|
*
|
||||||
|
* @return 切点
|
||||||
|
*/
|
||||||
|
private Pointcut buildPointcut() {
|
||||||
|
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||||
|
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
|
||||||
|
return new ComposablePointcut((Pointcut) cut);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
package top.continew.starter.extension.tenant.handler;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离拦截器
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2.7.0
|
||||||
|
*/
|
||||||
|
public class TenantDataSourceInterceptor implements MethodInterceptor {
|
||||||
|
|
||||||
|
private final TenantDataSourceHandler tenantDataSourceHandler;
|
||||||
|
|
||||||
|
public TenantDataSourceInterceptor(TenantDataSourceHandler tenantDataSourceHandler) {
|
||||||
|
this.tenantDataSourceHandler = tenantDataSourceHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||||
|
Long tenantId = TenantContextHolder.getTenantId();
|
||||||
|
if (null == tenantId) {
|
||||||
|
return invocation.proceed();
|
||||||
|
}
|
||||||
|
// 切换数据源
|
||||||
|
boolean isPush = false;
|
||||||
|
try {
|
||||||
|
String dataSourceName = tenantId.toString();
|
||||||
|
tenantDataSourceHandler.changeDataSource(dataSourceName);
|
||||||
|
isPush = true;
|
||||||
|
return invocation.proceed();
|
||||||
|
} finally {
|
||||||
|
if (isPush) {
|
||||||
|
DynamicDataSourceContextHolder.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user