init
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>BladeX-Tool</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-starter-tenant-dynamic</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<module.name>org.springblade.blade.starter.tenant.dynamic</module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--Blade-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-starter-db-dynamic</artifactId>
|
||||
</dependency>
|
||||
<!-- sharding-jdbc -->
|
||||
<dependency>
|
||||
<groupId>org.apache.shardingsphere</groupId>
|
||||
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Auto -->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-auto</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.config;
|
||||
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
|
||||
import com.baomidou.dynamic.datasource.creator.druid.DruidDataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.processor.DsJakartaHeaderProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsJakartaSessionProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
||||
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceCreatorAutoConfiguration;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.db.dynamic.config.DynamicDataSourceConfiguration;
|
||||
import org.springblade.core.db.dynamic.processor.DynamicDataSourceProcessor;
|
||||
import org.springblade.core.tenant.annotation.TenantDS;
|
||||
import org.springblade.core.tenant.dynamic.*;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import static org.springblade.core.tenant.constant.TenantDynamicConstant.TENANT_DYNAMIC_DATASOURCE_PROP;
|
||||
import static org.springblade.core.tenant.constant.TenantDynamicConstant.TENANT_DYNAMIC_GLOBAL_PROP;
|
||||
|
||||
/**
|
||||
* 多租户数据源配置类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties({DataSourceProperties.class, DynamicDataSourceProperties.class})
|
||||
@AutoConfiguration(before = {DruidDataSourceAutoConfigure.class, DynamicDataSourceAutoConfiguration.class, DynamicDataSourceConfiguration.class})
|
||||
@Import(value = {DynamicDataSourceCreatorAutoConfiguration.class})
|
||||
@ConditionalOnProperty(value = TENANT_DYNAMIC_DATASOURCE_PROP, havingValue = "true")
|
||||
public class TenantDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DsProcessor dsProcessor() {
|
||||
DsProcessor headerProcessor = new DsJakartaHeaderProcessor();
|
||||
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
|
||||
DsTenantIdProcessor tenantIdProcessor = new DsTenantIdProcessor();
|
||||
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
|
||||
headerProcessor.setNextProcessor(sessionProcessor);
|
||||
sessionProcessor.setNextProcessor(tenantIdProcessor);
|
||||
tenantIdProcessor.setNextProcessor(spelExpressionProcessor);
|
||||
return headerProcessor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public DynamicDataSourceProcessor dynamicTenantDataSourceProcessor() {
|
||||
return new TenantDataSourceProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor() {
|
||||
return new TenantDataSourceAnnotationInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public DynamicDataSourceAnnotationAdvisor dynamicTenantDatasourceAnnotationAdvisor(TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor, DynamicDataSourceProperties dynamicDataSourceProperties) {
|
||||
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(tenantDataSourceAnnotationInterceptor, TenantDS.class);
|
||||
advisor.setOrder(dynamicDataSourceProperties.getAop().getOrder());
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
|
||||
public TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor() {
|
||||
return new TenantDataSourceGlobalInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
|
||||
public TenantDataSourceGlobalAdvisor tenantDataSourceGlobalAdvisor(TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor, DynamicDataSourceProperties dynamicDataSourceProperties) {
|
||||
TenantDataSourceGlobalAdvisor advisor = new TenantDataSourceGlobalAdvisor(tenantDataSourceGlobalInterceptor);
|
||||
advisor.setOrder(dynamicDataSourceProperties.getAop().getOrder() + 1);
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Order
|
||||
@AutoConfiguration
|
||||
@AllArgsConstructor
|
||||
@ConditionalOnProperty(value = TENANT_DYNAMIC_DATASOURCE_PROP, havingValue = "true")
|
||||
public static class TenantDataSourceAnnotationConfiguration implements SmartInitializingSingleton {
|
||||
|
||||
private final TenantDataSourceAnnotationInterceptor tenantDataSourceAnnotationInterceptor;
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final DruidDataSourceCreator dataSourceCreator;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
TenantDataSourceHolder tenantDataSourceHolder = new TenantDataSourceHolder(dataSource, dataSourceCreator, jdbcTemplate);
|
||||
tenantDataSourceAnnotationInterceptor.setHolder(tenantDataSourceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
@Order
|
||||
@AutoConfiguration
|
||||
@AllArgsConstructor
|
||||
@ConditionalOnProperty(value = TENANT_DYNAMIC_GLOBAL_PROP, havingValue = "true")
|
||||
public static class TenantDataSourceGlobalConfiguration implements SmartInitializingSingleton {
|
||||
|
||||
private final TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor;
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final DruidDataSourceCreator dataSourceCreator;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
TenantDataSourceHolder tenantDataSourceHolder = new TenantDataSourceHolder(dataSource, dataSourceCreator, jdbcTemplate);
|
||||
tenantDataSourceGlobalInterceptor.setHolder(tenantDataSourceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.constant;
|
||||
|
||||
/**
|
||||
* 租户动态数据源常量.
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface TenantDynamicConstant extends TenantBaseConstant{
|
||||
|
||||
/**
|
||||
* 租户动态数据源键
|
||||
*/
|
||||
String TENANT_DYNAMIC_DATASOURCE_PROP = "blade.tenant.dynamic-datasource";
|
||||
|
||||
/**
|
||||
* 租户全局动态数据源切面键
|
||||
*/
|
||||
String TENANT_DYNAMIC_GLOBAL_PROP = "blade.tenant.dynamic-global";
|
||||
|
||||
/**
|
||||
* 租户是否存在数据源
|
||||
*/
|
||||
String TENANT_DATASOURCE_EXIST_STATEMENT = "select datasource_id from blade_tenant WHERE is_deleted = 0 AND tenant_id = ?";
|
||||
|
||||
/**
|
||||
* 租户数据源基础SQL
|
||||
*/
|
||||
String TENANT_DATASOURCE_BASE_STATEMENT = "SELECT category, tenant_id as tenantId, driver_class as driverClass, url, username, password, sharding_config as shardingConfig from blade_tenant tenant LEFT JOIN blade_tenant_datasource datasource ON tenant.datasource_id = datasource.id ";
|
||||
|
||||
/**
|
||||
* 租户单数据源SQL
|
||||
*/
|
||||
String TENANT_DATASOURCE_SINGLE_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0 AND tenant.tenant_id = ?";
|
||||
|
||||
/**
|
||||
* 租户集动态数据源SQL
|
||||
*/
|
||||
String TENANT_DATASOURCE_GROUP_STATEMENT = TENANT_DATASOURCE_BASE_STATEMENT + "WHERE tenant.is_deleted = 0";
|
||||
|
||||
/**
|
||||
* 租户未找到返回信息
|
||||
*/
|
||||
String TENANT_DATASOURCE_NOT_FOUND = "未找到租户信息,数据源加载失败!";
|
||||
|
||||
/**
|
||||
* oracle驱动类
|
||||
*/
|
||||
String ORACLE_DRIVER_CLASS = "oracle.jdbc.OracleDriver";
|
||||
|
||||
/**
|
||||
* oracle校验
|
||||
*/
|
||||
String ORACLE_VALIDATE_STATEMENT = "select 1 from dual";
|
||||
|
||||
/**
|
||||
* 通用校验
|
||||
*/
|
||||
String COMMON_VALIDATE_STATEMENT = "select 1";
|
||||
|
||||
/**
|
||||
* jdbc数据源分类
|
||||
*/
|
||||
int JDBC_CATEGORY = 1;
|
||||
|
||||
/**
|
||||
* sharding数据源分类
|
||||
*/
|
||||
int SHARDING_CATEGORY = 2;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import com.baomidou.dynamic.datasource.processor.DsProcessor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springblade.core.tenant.TenantUtil;
|
||||
|
||||
/**
|
||||
* 租户动态数据源解析器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class DsTenantIdProcessor extends DsProcessor {
|
||||
|
||||
public static final String TENANT_ID_KEY = "#token.tenantId";
|
||||
|
||||
@Override
|
||||
public boolean matches(String key) {
|
||||
return key.equals(TENANT_ID_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doDetermineDatasource(MethodInvocation invocation, String key) {
|
||||
return TenantUtil.getTenantId();
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 租户数据源
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class TenantDataSource {
|
||||
|
||||
/**
|
||||
* 数据源类型
|
||||
*/
|
||||
private int category;
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
/**
|
||||
* 数据源ID
|
||||
*/
|
||||
private String datasourceId;
|
||||
/**
|
||||
* 驱动类
|
||||
*/
|
||||
private String driverClass;
|
||||
/**
|
||||
* 数据库链接
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 数据库账号名
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 数据库密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 分库分表配置
|
||||
*/
|
||||
private String shardingConfig;
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springblade.core.tenant.TenantUtil;
|
||||
import org.springblade.core.tenant.exception.TenantDataSourceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* 租户数据源切换拦截器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Setter
|
||||
public class TenantDataSourceAnnotationInterceptor implements MethodInterceptor {
|
||||
|
||||
private TenantDataSourceHolder holder;
|
||||
|
||||
@Override
|
||||
public Object invoke(@NonNull MethodInvocation invocation) throws Throwable {
|
||||
String tenantId = TenantUtil.getTenantId();
|
||||
if (StringUtil.isBlank(tenantId)) {
|
||||
throw new TenantDataSourceException("租户数据源切换失败,租户ID不能为空");
|
||||
}
|
||||
try {
|
||||
holder.handleDataSource(tenantId);
|
||||
DynamicDataSourceContextHolder.push(tenantId);
|
||||
return invocation.proceed();
|
||||
} catch (Exception exception) {
|
||||
log.error("租户数据源切换异常", exception);
|
||||
throw new TenantDataSourceException(exception.getMessage(), exception);
|
||||
} finally {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
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;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import static org.springblade.core.launch.constant.AppConstant.BASE_PACKAGES;
|
||||
|
||||
/**
|
||||
* 租户数据源全局处理器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class TenantDataSourceGlobalAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
|
||||
|
||||
private final Advice advice;
|
||||
|
||||
private final Pointcut pointcut;
|
||||
|
||||
public TenantDataSourceGlobalAdvisor(@NonNull TenantDataSourceGlobalInterceptor tenantDataSourceGlobalInterceptor) {
|
||||
this.advice = tenantDataSourceGlobalInterceptor;
|
||||
this.pointcut = buildPointcut();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
|
||||
if (this.advice instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private Pointcut buildPointcut() {
|
||||
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
|
||||
cut.setExpression(
|
||||
"(@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)) && " +
|
||||
"(!@annotation(" + BASE_PACKAGES + ".core.tenant.annotation.NonDS) && !@within(" + BASE_PACKAGES + ".core.tenant.annotation.NonDS))"
|
||||
);
|
||||
return new ComposablePointcut((Pointcut) cut);
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springblade.core.tenant.TenantUtil;
|
||||
import org.springblade.core.tenant.exception.TenantDataSourceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* 租户数据源全局拦截器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Setter
|
||||
public class TenantDataSourceGlobalInterceptor implements MethodInterceptor {
|
||||
|
||||
private TenantDataSourceHolder holder;
|
||||
|
||||
@Override
|
||||
public Object invoke(@NonNull MethodInvocation invocation) throws Throwable {
|
||||
String tenantId = TenantUtil.getTenantId();
|
||||
try {
|
||||
if (StringUtil.isNotBlank(tenantId)) {
|
||||
holder.handleDataSource(tenantId);
|
||||
DynamicDataSourceContextHolder.push(tenantId);
|
||||
}
|
||||
return invocation.proceed();
|
||||
} catch (Exception exception) {
|
||||
log.error("租户全局数据源切换异常", exception);
|
||||
throw new TenantDataSourceException(exception.getMessage(), exception);
|
||||
} finally {
|
||||
if (StringUtil.isNotBlank(tenantId)) {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.creator.druid.DruidConfig;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.db.dynamic.holder.DynamicDataSourceHolder;
|
||||
import org.springblade.core.tenant.utils.ShardingUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springblade.core.tenant.constant.TenantDynamicConstant.*;
|
||||
|
||||
/**
|
||||
* 租户数据源核心处理类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class TenantDataSourceHolder implements DynamicDataSourceHolder {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final DataSourceCreator dataSourceCreator;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 数据源缓存处理
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
@Override
|
||||
public void handleDataSource(String tenantId) {
|
||||
// 获取储存的数据源集合
|
||||
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
|
||||
Set<String> keys = ds.getDataSources().keySet();
|
||||
// 配置不存在则动态添加数据源,以懒加载的模式解决分布式场景的配置同步
|
||||
// 为了保证数据完整性,配置后生成数据源缓存,后台便无法修改更换数据源,若一定要修改请迁移数据后重启服务或自行修改底层逻辑
|
||||
if (!keys.contains(tenantId)) {
|
||||
TenantDataSource tenantDataSource = getDataSource(tenantId);
|
||||
if (tenantDataSource != null) {
|
||||
int category = tenantDataSource.getCategory();
|
||||
if (category == JDBC_CATEGORY) {
|
||||
// 创建数据源配置
|
||||
DataSourceProperty dataSourceProperty = new DataSourceProperty();
|
||||
// 拷贝数据源配置
|
||||
BeanUtils.copyProperties(tenantDataSource, dataSourceProperty);
|
||||
// 设置驱动类
|
||||
dataSourceProperty.setDriverClassName(tenantDataSource.getDriverClass());
|
||||
// 关闭懒加载
|
||||
dataSourceProperty.setLazy(Boolean.FALSE);
|
||||
// 设置SQL校验
|
||||
DruidConfig druid = dataSourceProperty.getDruid();
|
||||
if (StringUtil.equals(dataSourceProperty.getDriverClassName(), ORACLE_DRIVER_CLASS)) {
|
||||
druid.setValidationQuery(ORACLE_VALIDATE_STATEMENT);
|
||||
} else {
|
||||
druid.setValidationQuery(COMMON_VALIDATE_STATEMENT);
|
||||
}
|
||||
// 创建Jdbc数据源
|
||||
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
|
||||
// 添加最新数据源
|
||||
ds.addDataSource(tenantId, dataSource);
|
||||
} else if (category == SHARDING_CATEGORY) {
|
||||
// 创建Sharding数据源
|
||||
DataSource dataSource = ShardingUtil.createDataSource(tenantDataSource.getShardingConfig());
|
||||
// 添加最新数据源
|
||||
ds.addDataSource(tenantId, dataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断租户是否有数据源配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
private Boolean existDataSource(String tenantId) {
|
||||
// 将租户是否配置数据源进行缓存,若重新配置会将此缓存清空并在下次请求的时候懒加载
|
||||
// 若租户没有配置数据源则会自动使用master数据源,此举是为了避免在没有数据库的时候频繁查询导致缓存击穿
|
||||
Boolean exist = CacheUtil.get(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_EXIST_KEY, tenantId, Boolean.class, Boolean.FALSE);
|
||||
if (exist == null) {
|
||||
TenantDataSource tenantDataSource = jdbcTemplate.queryForObject(TENANT_DATASOURCE_EXIST_STATEMENT, new BeanPropertyRowMapper<>(TenantDataSource.class), tenantId);
|
||||
if (tenantDataSource != null && StringUtil.isNotBlank(tenantDataSource.getDatasourceId())) {
|
||||
exist = Boolean.TRUE;
|
||||
} else {
|
||||
exist = Boolean.FALSE;
|
||||
}
|
||||
CacheUtil.put(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_EXIST_KEY, tenantId, exist, Boolean.FALSE);
|
||||
}
|
||||
return exist;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应的数据源配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
private TenantDataSource getDataSource(String tenantId) {
|
||||
// 不存在租户数据源则返回空,防止缓存击穿
|
||||
if (!existDataSource(tenantId)) {
|
||||
return null;
|
||||
}
|
||||
// 获取租户数据源信息
|
||||
TenantDataSource tenantDataSource = CacheUtil.get(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_KEY, tenantId, TenantDataSource.class, Boolean.FALSE);
|
||||
if (tenantDataSource == null) {
|
||||
tenantDataSource = jdbcTemplate.queryForObject(TENANT_DATASOURCE_SINGLE_STATEMENT, new BeanPropertyRowMapper<>(TenantDataSource.class), tenantId);
|
||||
if (tenantDataSource != null && StringUtil.isNoneBlank(tenantDataSource.getTenantId(), tenantDataSource.getDriverClass(), tenantDataSource.getUrl(), tenantDataSource.getUsername(), tenantDataSource.getPassword())) {
|
||||
CacheUtil.put(TENANT_DATASOURCE_CACHE, TENANT_DATASOURCE_KEY, tenantId, tenantDataSource, Boolean.FALSE);
|
||||
} else {
|
||||
tenantDataSource = null;
|
||||
}
|
||||
}
|
||||
return tenantDataSource;
|
||||
}
|
||||
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.dynamic;
|
||||
|
||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.db.dynamic.creator.DynamicDataSourceProperty;
|
||||
import org.springblade.core.db.dynamic.processor.DynamicDataSourceProcessor;
|
||||
import org.springblade.core.tenant.utils.ShardingUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springblade.core.tenant.constant.TenantDynamicConstant.*;
|
||||
|
||||
/**
|
||||
* 租户动态数据源扩展
|
||||
* <p>
|
||||
* 实现数据源扩展来支持租户级别的动态数据源
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
public class TenantDataSourceProcessor implements DynamicDataSourceProcessor {
|
||||
|
||||
@Override
|
||||
public Map<String, DataSourceProperty> loadDataSourceProperties(Statement statement, DynamicDataSourceProperties dynamicDataSourceProperties) throws SQLException {
|
||||
Map<String, DataSourceProperty> map = new HashMap<>(16);
|
||||
|
||||
// 构建动态数据源
|
||||
ResultSet rs = statement.executeQuery(TENANT_DATASOURCE_GROUP_STATEMENT);
|
||||
while (rs.next()) {
|
||||
int category = rs.getInt("category");
|
||||
String tenantId = rs.getString("tenantId");
|
||||
String driver = rs.getString("driverClass");
|
||||
String url = rs.getString("url");
|
||||
String username = rs.getString("username");
|
||||
String password = rs.getString("password");
|
||||
String shardingConfig = rs.getString("shardingConfig");
|
||||
// JDBC直连配置
|
||||
if (category == JDBC_CATEGORY && StringUtil.isNoneBlank(tenantId, driver, url, username, password)) {
|
||||
DataSourceProperty jdbcProperty = new DataSourceProperty();
|
||||
jdbcProperty.setDriverClassName(driver);
|
||||
jdbcProperty.setUrl(url);
|
||||
jdbcProperty.setUsername(username);
|
||||
jdbcProperty.setPassword(password);
|
||||
jdbcProperty.setDruid(dynamicDataSourceProperties.getDruid());
|
||||
map.put(tenantId, jdbcProperty);
|
||||
log.info("Loaded JDBC datasource config for tenant {}", tenantId);
|
||||
}
|
||||
// Sharding分库分表配置
|
||||
else if (category == SHARDING_CATEGORY && StringUtil.isNotBlank(shardingConfig)) {
|
||||
DataSource dataSource = ShardingUtil.createDataSource(shardingConfig);
|
||||
DynamicDataSourceProperty shardingProperty = new DynamicDataSourceProperty();
|
||||
shardingProperty.setTenantId(tenantId);
|
||||
shardingProperty.setDataSource(dataSource);
|
||||
map.put(tenantId, shardingProperty);
|
||||
log.info("Loaded Sharding datasource config for tenant {}", tenantId);
|
||||
}
|
||||
}
|
||||
log.info("Tenant datasource processor loaded {} datasources", map.size());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.env;
|
||||
|
||||
import org.springblade.core.auto.annotation.AutoEnvPostProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* 初始化分库分表配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AutoEnvPostProcessor
|
||||
public class TenantEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
private static final String DYNAMIC_DATASOURCE_KEY = "spring.datasource.dynamic.enabled";
|
||||
|
||||
private static final String DYNAMIC_FILTER_KEY = "spring.datasource.dynamic.druid.proxy-filters[0]";
|
||||
|
||||
private static final String AUTOCONFIGURE_EXCLUDE_KEY = "spring.autoconfigure.exclude";
|
||||
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
environment.getSystemProperties().put(DYNAMIC_DATASOURCE_KEY, "false");
|
||||
environment.getSystemProperties().put(DYNAMIC_FILTER_KEY, "sqlLogInterceptor");
|
||||
environment.getSystemProperties().put(AUTOCONFIGURE_EXCLUDE_KEY, "com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.tenant.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* ShardingUtil
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ShardingUtil {
|
||||
|
||||
@SneakyThrows
|
||||
public static DataSource createDataSource(String yamlConfig) {
|
||||
return YamlShardingSphereDataSourceFactory.createDataSource(yamlConfig.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user