mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-10-25 18:57:17 +08:00
refactor(extension/tenant): 多租户组件适配动态隔离级别 (#8)
This commit is contained in:
@@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.tenant.autoconfigure;
|
package top.continew.starter.extension.tenant.autoconfigure;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import top.continew.starter.extension.tenant.context.TenantContext;
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,20 +29,25 @@ import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
|||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.7.0
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public class TenantInterceptor implements HandlerInterceptor {
|
public class TenantInterceptor implements HandlerInterceptor, Ordered {
|
||||||
|
|
||||||
private final TenantProperties tenantProperties;
|
private final TenantProperties tenantProperties;
|
||||||
|
private final TenantProvider tenantProvider;
|
||||||
|
|
||||||
public TenantInterceptor(TenantProperties tenantProperties) {
|
public TenantInterceptor(TenantProperties tenantProperties, TenantProvider tenantProvider) {
|
||||||
this.tenantProperties = tenantProperties;
|
this.tenantProperties = tenantProperties;
|
||||||
|
this.tenantProvider = tenantProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
String tenantId = request.getHeader(tenantProperties.getTenantIdHeader());
|
||||||
TenantContext tenantContext = new TenantContext();
|
TenantContextHolder.setContext(tenantProvider.getByTenantId(tenantId, true));
|
||||||
tenantContext.setTenantId(Convert.toLong(tenantId));
|
|
||||||
TenantContextHolder.setContext(tenantContext);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ 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;
|
||||||
|
|
||||||
@@ -36,11 +35,6 @@ public class TenantProperties {
|
|||||||
*/
|
*/
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户隔离级别
|
|
||||||
*/
|
|
||||||
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户 ID 列名
|
* 租户 ID 列名
|
||||||
*/
|
*/
|
||||||
@@ -69,14 +63,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import top.continew.starter.core.constant.PropertiesConstants;
|
import top.continew.starter.core.constant.PropertiesConstants;
|
||||||
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多租户 Web MVC 自动配置
|
* 多租户 Web MVC 自动配置
|
||||||
@@ -35,13 +36,15 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
|||||||
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
public class TenantWebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final TenantProperties tenantProperties;
|
private final TenantProperties tenantProperties;
|
||||||
|
private final TenantProvider tenantProvider;
|
||||||
|
|
||||||
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties) {
|
public TenantWebMvcAutoConfiguration(TenantProperties tenantProperties, TenantProvider tenantProvider) {
|
||||||
this.tenantProperties = tenantProperties;
|
this.tenantProperties = tenantProperties;
|
||||||
|
this.tenantProvider = tenantProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(new TenantInterceptor(tenantProperties));
|
registry.addInterceptor(new TenantInterceptor(tenantProperties, tenantProvider));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,19 +16,22 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.tenant.config;
|
package top.continew.starter.extension.tenant.config;
|
||||||
|
|
||||||
|
import top.continew.starter.extension.tenant.context.TenantContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户数据源提供者
|
* 租户数据提供者
|
||||||
*
|
*
|
||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 2.7.0
|
* @since 2.7.0
|
||||||
*/
|
*/
|
||||||
public interface TenantDataSourceProvider {
|
public interface TenantProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据租户 ID 获取数据源配置
|
* 根据租户ID获取租户数据
|
||||||
*
|
*
|
||||||
* @param tenantId 租户 ID
|
* @param tenantId 租户 ID
|
||||||
|
* @param verify 是否验证租户有效性
|
||||||
* @return 数据源配置
|
* @return 数据源配置
|
||||||
*/
|
*/
|
||||||
TenantDataSource getByTenantId(String tenantId);
|
TenantContext getByTenantId(String tenantId, boolean verify);
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package top.continew.starter.extension.tenant.context;
|
package top.continew.starter.extension.tenant.context;
|
||||||
|
|
||||||
|
import top.continew.starter.extension.tenant.config.TenantDataSource;
|
||||||
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户上下文
|
* 租户上下文
|
||||||
*
|
*
|
||||||
@@ -29,6 +32,16 @@ public class TenantContext {
|
|||||||
*/
|
*/
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隔离级别
|
||||||
|
*/
|
||||||
|
private TenantIsolationLevel isolationLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据源信息
|
||||||
|
*/
|
||||||
|
private TenantDataSource dataSource;
|
||||||
|
|
||||||
public Long getTenantId() {
|
public Long getTenantId() {
|
||||||
return tenantId;
|
return tenantId;
|
||||||
}
|
}
|
||||||
@@ -36,4 +49,20 @@ public class TenantContext {
|
|||||||
public void setTenantId(Long tenantId) {
|
public void setTenantId(Long tenantId) {
|
||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TenantIsolationLevel getIsolationLevel() {
|
||||||
|
return isolationLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsolationLevel(TenantIsolationLevel isolationLevel) {
|
||||||
|
this.isolationLevel = isolationLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TenantDataSource getDataSource() {
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSource(TenantDataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package top.continew.starter.extension.tenant.context;
|
package top.continew.starter.extension.tenant.context;
|
||||||
|
|
||||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||||
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -66,4 +67,13 @@ public class TenantContextHolder {
|
|||||||
public static Long getTenantId() {
|
public static Long getTenantId() {
|
||||||
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
|
return Optional.ofNullable(getContext()).map(TenantContext::getTenantId).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取隔离级别
|
||||||
|
*/
|
||||||
|
public static TenantIsolationLevel getIsolationLevel() {
|
||||||
|
return Optional.ofNullable(getContext())
|
||||||
|
.map(TenantContext::getIsolationLevel)
|
||||||
|
.orElse(TenantIsolationLevel.LINE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ public interface TenantDataSourceHandler {
|
|||||||
/**
|
/**
|
||||||
* 切换数据源
|
* 切换数据源
|
||||||
*
|
*
|
||||||
* @param dataSourceName 数据源名称
|
|
||||||
*/
|
*/
|
||||||
void changeDataSource(String dataSourceName);
|
void changeDataSource(TenantDataSource tenantDataSource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否存在指定数据源
|
* 是否存在指定数据源
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 租户处理器
|
||||||
|
* @author: 小熊
|
||||||
|
* @create: 2024-12-18 19:37
|
||||||
|
*/
|
||||||
|
public interface TenantHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定租户中执行方法
|
||||||
|
*/
|
||||||
|
void executeInTenant(Long tenantId, Runnable runnable);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 核心模块 -->
|
<!-- 核心模块 -->
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ 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 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.TenantDataSourceProvider;
|
import top.continew.starter.extension.tenant.config.TenantProvider;
|
||||||
import top.continew.starter.extension.tenant.handler.*;
|
import top.continew.starter.extension.tenant.handler.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,85 +49,76 @@ public class TenantAutoConfiguration {
|
|||||||
private TenantAutoConfiguration() {
|
private TenantAutoConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static {
|
||||||
* 租户隔离级别:行级
|
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant' completed initialization.");
|
||||||
*/
|
|
||||||
@AutoConfiguration
|
|
||||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "line", matchIfMissing = true)
|
|
||||||
public static class Line {
|
|
||||||
static {
|
|
||||||
log.debug("[ContiNew Starter] - Auto Configuration 'Tenant-Line' completed initialization.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户行级隔离拦截器
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
|
|
||||||
return new TenantLineInnerInterceptor(tenantLineHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户行级隔离处理器(默认)
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
|
||||||
return new DefaultTenantLineHandler(properties);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户隔离级别:数据源级
|
* 租户行级隔离拦截器
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@Bean
|
||||||
@ConditionalOnProperty(name = PropertiesConstants.TENANT + ".isolation-level", havingValue = "datasource")
|
@ConditionalOnMissingBean
|
||||||
public static class DataSource {
|
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantLineHandler tenantLineHandler) {
|
||||||
static {
|
return new TenantLineInnerInterceptor(tenantLineHandler);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户行级隔离处理器(默认)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantLineHandler tenantLineHandler(TenantProperties properties) {
|
||||||
|
return new DefaultTenantLineHandler(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户数据源级隔离通知
|
||||||
|
*/
|
||||||
|
@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(javax.sql.DataSource dataSource,
|
||||||
|
DefaultDataSourceCreator dataSourceCreator) {
|
||||||
|
return new DefaultTenantDataSourceHandler((DynamicRoutingDataSource)dataSource, dataSourceCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多租户数据源提供者
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantProvider tenantDataSourceProvider() {
|
||||||
|
if (log.isErrorEnabled()) {
|
||||||
|
log.error("Consider defining a bean of type '{}' in your configuration.", ResolvableType
|
||||||
|
.forClass(TenantProvider.class));
|
||||||
|
}
|
||||||
|
throw new NoSuchBeanDefinitionException(TenantProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户处理器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public TenantHandler tenantHandler(TenantDataSourceHandler tenantDataSourceHandler, TenantProvider tenantProvider) {
|
||||||
|
return new DefaultTenantHandler(tenantDataSourceHandler, tenantProvider);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ 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.config.TenantDataSourceProvider;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
@@ -39,30 +38,25 @@ public class DefaultTenantDataSourceHandler implements TenantDataSourceHandler {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(DefaultTenantDataSourceHandler.class);
|
||||||
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||||
private final DefaultDataSourceCreator dataSourceCreator;
|
private final DefaultDataSourceCreator dataSourceCreator;
|
||||||
private final TenantDataSourceProvider tenantDataSourceProvider;
|
|
||||||
|
|
||||||
public DefaultTenantDataSourceHandler(TenantDataSourceProvider tenantDataSourceProvider,
|
public DefaultTenantDataSourceHandler(DynamicRoutingDataSource dynamicRoutingDataSource,
|
||||||
DynamicRoutingDataSource dynamicRoutingDataSource,
|
|
||||||
DefaultDataSourceCreator dataSourceCreator) {
|
DefaultDataSourceCreator dataSourceCreator) {
|
||||||
this.tenantDataSourceProvider = tenantDataSourceProvider;
|
|
||||||
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
this.dynamicRoutingDataSource = dynamicRoutingDataSource;
|
||||||
this.dataSourceCreator = dataSourceCreator;
|
this.dataSourceCreator = dataSourceCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeDataSource(String dataSourceName) {
|
public void changeDataSource(TenantDataSource tenantDataSource) {
|
||||||
if (!this.containsDataSource(dataSourceName)) {
|
if (tenantDataSource != null) {
|
||||||
TenantDataSource tenantDataSource = tenantDataSourceProvider.getByTenantId(dataSourceName);
|
String dataSourceName = tenantDataSource.getPoolName();
|
||||||
if (null == tenantDataSource) {
|
if (!this.containsDataSource(dataSourceName)) {
|
||||||
throw new IllegalArgumentException("Data source [%s] configuration not found"
|
DataSource datasource = this.createDataSource(tenantDataSource);
|
||||||
.formatted(dataSourceName));
|
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);
|
||||||
|
log.info("Load data source: {}", dataSourceName);
|
||||||
}
|
}
|
||||||
DataSource datasource = this.createDataSource(tenantDataSource);
|
DynamicDataSourceContextHolder.push(dataSourceName);
|
||||||
dynamicRoutingDataSource.addDataSource(dataSourceName, datasource);
|
log.info("Change data source: {}", dataSourceName);
|
||||||
log.info("Load data source: {}", dataSourceName);
|
|
||||||
}
|
}
|
||||||
DynamicDataSourceContextHolder.push(dataSourceName);
|
|
||||||
log.info("Change data source: {}", dataSourceName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.handler;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public class DefaultTenantHandler implements TenantHandler {
|
||||||
|
|
||||||
|
private final TenantDataSourceHandler dataSourceHandler;
|
||||||
|
private final TenantProvider tenantProvider;
|
||||||
|
|
||||||
|
public DefaultTenantHandler(TenantDataSourceHandler dataSourceHandler, TenantProvider tenantProvider) {
|
||||||
|
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();
|
||||||
|
// 切换数据源
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import net.sf.jsqlparser.expression.Expression;
|
|||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
import top.continew.starter.extension.tenant.autoconfigure.TenantProperties;
|
||||||
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
import top.continew.starter.extension.tenant.context.TenantContextHolder;
|
||||||
|
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认租户行级隔离处理器
|
* 默认租户行级隔离处理器
|
||||||
@@ -54,7 +55,8 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean ignoreTable(String tableName) {
|
public boolean ignoreTable(String tableName) {
|
||||||
Long tenantId = TenantContextHolder.getTenantId();
|
Long tenantId = TenantContextHolder.getTenantId();
|
||||||
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
if ((null != tenantId && tenantId.equals(tenantProperties
|
||||||
|
.getSuperTenantId())) || TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class TenantDataSourceAdvisor extends AbstractPointcutAdvisor implements
|
|||||||
*/
|
*/
|
||||||
private Pointcut buildPointcut() {
|
private Pointcut buildPointcut() {
|
||||||
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||||
cut.setExpression("!@annotation(top.continew.starter.extension.tenant.annotation.TenantDataSourceIgnore)");
|
cut.setExpression("execution(* *..controller..*(..))");
|
||||||
return new ComposablePointcut((Pointcut)cut);
|
return new ComposablePointcut((Pointcut)cut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户数据源级隔离拦截器
|
* 租户数据源级隔离拦截器
|
||||||
@@ -37,21 +38,20 @@ public class TenantDataSourceInterceptor implements MethodInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||||
Long tenantId = TenantContextHolder.getTenantId();
|
if (TenantContextHolder.getIsolationLevel() == TenantIsolationLevel.DATASOURCE) {
|
||||||
if (null == tenantId) {
|
// 切换数据源
|
||||||
return invocation.proceed();
|
boolean isPush = false;
|
||||||
}
|
try {
|
||||||
// 切换数据源
|
tenantDataSourceHandler.changeDataSource(TenantContextHolder.getContext().getDataSource());
|
||||||
boolean isPush = false;
|
isPush = true;
|
||||||
try {
|
return invocation.proceed();
|
||||||
String dataSourceName = tenantId.toString();
|
} finally {
|
||||||
tenantDataSourceHandler.changeDataSource(dataSourceName);
|
if (isPush) {
|
||||||
isPush = true;
|
DynamicDataSourceContextHolder.poll();
|
||||||
return invocation.proceed();
|
}
|
||||||
} finally {
|
|
||||||
if (isPush) {
|
|
||||||
DynamicDataSourceContextHolder.poll();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return invocation.proceed();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.log.aop.aspect;
|
package top.continew.starter.log.aop.aspect;
|
||||||
|
|
||||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||||
@@ -48,7 +64,7 @@ public class ConsoleLogAspect {
|
|||||||
// 打印请求日志
|
// 打印请求日志
|
||||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||||
Instant startTime = Instant.now();
|
Instant startTime = Instant.now();
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
HttpServletRequest request = attributes.getRequest();
|
HttpServletRequest request = attributes.getRequest();
|
||||||
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
log.info("[{}] {}", request.getMethod(), request.getRequestURI());
|
||||||
@@ -65,16 +81,16 @@ public class ConsoleLogAspect {
|
|||||||
// 打印请求耗时
|
// 打印请求耗时
|
||||||
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
|
||||||
Instant endTime = Instant.now();
|
Instant endTime = Instant.now();
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||||
if (attributes == null) {
|
if (attributes == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HttpServletRequest request = attributes.getRequest();
|
HttpServletRequest request = attributes.getRequest();
|
||||||
HttpServletResponse response = attributes.getResponse();
|
HttpServletResponse response = attributes.getResponse();
|
||||||
Duration timeTaken = Duration.between(timeTtl.get(), endTime);
|
Duration timeTaken = Duration.between(timeTtl.get(), endTime);
|
||||||
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(),
|
log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response != null
|
||||||
response != null ? response.getStatus() : "N/A",
|
? response.getStatus()
|
||||||
timeTaken.toMillis());
|
: "N/A", timeTaken.toMillis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.log.aop.aspect;
|
package top.continew.starter.log.aop.aspect;
|
||||||
|
|
||||||
import cn.hutool.core.text.CharSequenceUtil;
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
@@ -58,7 +74,7 @@ public class LogAspect {
|
|||||||
@Before(value = "pointcutService()")
|
@Before(value = "pointcutService()")
|
||||||
public void doBefore() {
|
public void doBefore() {
|
||||||
Instant startTime = Instant.now();
|
Instant startTime = Instant.now();
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
HttpServletRequest request = attributes.getRequest();
|
HttpServletRequest request = attributes.getRequest();
|
||||||
LogRecord.Started startedLogRecord = LogRecord.start(startTime, new RecordableServletHttpRequest(request));
|
LogRecord.Started startedLogRecord = LogRecord.start(startTime, new RecordableServletHttpRequest(request));
|
||||||
@@ -66,7 +82,6 @@ public class LogAspect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理请求后执行 - 正常返回
|
* 处理请求后执行 - 正常返回
|
||||||
*
|
*
|
||||||
@@ -98,7 +113,7 @@ public class LogAspect {
|
|||||||
private void handleAfterCompletion(JoinPoint joinPoint, Exception ex) {
|
private void handleAfterCompletion(JoinPoint joinPoint, Exception ex) {
|
||||||
try {
|
try {
|
||||||
Instant endTime = Instant.now();
|
Instant endTime = Instant.now();
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||||
if (attributes == null) {
|
if (attributes == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,7 +125,7 @@ public class LogAspect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取方法和类注解信息
|
// 获取方法和类注解信息
|
||||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method method = signature.getMethod();
|
||||||
Class<?> targetClass = joinPoint.getTarget().getClass();
|
Class<?> targetClass = joinPoint.getTarget().getClass();
|
||||||
|
|
||||||
@@ -122,9 +137,7 @@ public class LogAspect {
|
|||||||
|
|
||||||
// 完成日志记录
|
// 完成日志记录
|
||||||
LogRecord finishedLogRecord = startedLogRecord
|
LogRecord finishedLogRecord = startedLogRecord
|
||||||
.finish(endTime,
|
.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includeSet);
|
||||||
new RecordableServletHttpResponse(response, response.getStatus()),
|
|
||||||
includeSet);
|
|
||||||
// 记录异常
|
// 记录异常
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
finishedLogRecord.getResponse().setStatus(1);
|
finishedLogRecord.getResponse().setStatus(1);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class LogAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public LogAspect logAspect() {
|
public LogAspect logAspect() {
|
||||||
return new LogAspect(logDao(),logProperties);
|
return new LogAspect(logDao(), logProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ public class LogProperties {
|
|||||||
*/
|
*/
|
||||||
private Set<Include> includes = Include.defaultIncludes();
|
private Set<Include> includes = Include.defaultIncludes();
|
||||||
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ public class LogRecord {
|
|||||||
public String getErrorMsg() {
|
public String getErrorMsg() {
|
||||||
return errorMsg;
|
return errorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setErrorMsg(String errorMsg) {
|
public void setErrorMsg(String errorMsg) {
|
||||||
this.errorMsg = errorMsg;
|
this.errorMsg = errorMsg;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user