From 4dcb7c297bd73b3130cb4f18779ec4fb43b96eb4 Mon Sep 17 00:00:00 2001 From: ailanyin Date: Fri, 9 Jun 2023 16:02:04 +0800 Subject: [PATCH] add --- .../src/main/resources/application-dev.yml | 4 + .../qiaoba/common/base/DatasourceService.java | 19 --- .../common/base/constants/BaseConstant.java | 5 + .../common/base/context/BaseContext.java | 26 ++++- .../config/DynamicDataSourceConfig.java | 96 ++++++++++++--- .../config/DynamicDataSourceContext.java | 5 +- .../database/config/MybatisPlusConfig.java | 2 + .../constants/DynamicDatasourceConstant.java | 10 -- .../database/entity/DynamicDataSource.java | 20 ++++ .../factories/DynamicDataSourceFactory.java | 6 - .../filters/DynamicDataSourceFilter.java | 80 ------------- .../interceptors/SchemaInterceptor.java | 31 +++-- .../service/DynamicDatasourceService.java | 22 ++++ .../module/tenant/entity/SysTenant.java | 2 +- .../param/SysTenantDatasourceParam.java | 36 ++++++ .../filters/DynamicDataSourceFilter.java | 109 ++++++++++++++++++ .../tenant/handler/MysqlDataHandler.java | 4 +- .../service/SysTenantDatasourceService.java | 9 ++ .../impl/DynamicDatasourceServiceImpl.java | 48 ++++++++ .../impl/SysTenantDatasourceServiceImpl.java | 24 ++-- .../service/impl/SysTenantServiceImpl.java | 21 +--- 21 files changed, 412 insertions(+), 167 deletions(-) delete mode 100644 qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/DatasourceService.java delete mode 100644 qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/filters/DynamicDataSourceFilter.java create mode 100644 qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/service/DynamicDatasourceService.java create mode 100644 qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/param/SysTenantDatasourceParam.java create mode 100644 qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java create mode 100644 qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/DynamicDatasourceServiceImpl.java diff --git a/qiaoba-application/src/main/resources/application-dev.yml b/qiaoba-application/src/main/resources/application-dev.yml index dc9d19e..bb33f94 100644 --- a/qiaoba-application/src/main/resources/application-dev.yml +++ b/qiaoba-application/src/main/resources/application-dev.yml @@ -31,6 +31,10 @@ qiaoba: max: 20 is-use: false # 不使用该库 + # 连接测试query(配置检测连接是否有效) + connectionTestQuery: SELECT 1 + # 多久检查一次连接的活性 + keepaliveTime: 30000 mybatis-plus: configuration: diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/DatasourceService.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/DatasourceService.java deleted file mode 100644 index b47d965..0000000 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/DatasourceService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.qiaoba.common.base; - -/** - * 数据源接口 - * - * @author ailanyin - * @version 1.0 - * @since 2023/6/8 17:23 - */ -public interface DatasourceService { - - /** - * 检查租户是否允许访问 - * - * @param tenantId tenantId - * @return true = 允许 - */ - boolean checkTenantInfo(String tenantId); -} diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/BaseConstant.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/BaseConstant.java index 6c1f57e..050285c 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/BaseConstant.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/constants/BaseConstant.java @@ -29,6 +29,11 @@ public class BaseConstant { */ public static final String COLON_JOIN_STR = ":"; + /** + * 中划线拼接符号: '-' + */ + public static final String HYPHEN_JOIN_STR = "-"; + /** * 树的key的命名 */ diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java index 98af492..9abee7d 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/context/BaseContext.java @@ -14,6 +14,11 @@ public class BaseContext { */ private static final ThreadLocal DATABASE_TYPE_HOLDER = new ThreadLocal<>(); + /** + * SCHEMA + */ + private static final ThreadLocal SCHEMA_HOLDER = new ThreadLocal<>(); + /** * 租户ID */ @@ -66,6 +71,24 @@ public class BaseContext { DATASOURCE_HOLDER.set(dataSource); } + /** + * 是否是 Schema模式 + * + * @return Boolean + */ + public static Boolean getSchema() { + return SCHEMA_HOLDER.get(); + } + + /** + * 设置是否是 Schema模式 + * + * @param isSchema 是/否 + */ + public static void setSchema(boolean isSchema) { + SCHEMA_HOLDER.set(isSchema); + } + /** * 清除所有的ThreadLocal */ @@ -76,7 +99,8 @@ public class BaseContext { TENANT_ID_HOLDER.remove(); // 清除上下文中数据库类型 DATABASE_TYPE_HOLDER.remove(); - + // 清除上下文中 SCHEMA + SCHEMA_HOLDER.remove(); } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceConfig.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceConfig.java index a0ebdda..eada37d 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceConfig.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceConfig.java @@ -1,12 +1,18 @@ package com.qiaoba.common.database.config; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import com.alibaba.druid.pool.DruidDataSource; +import com.qiaoba.common.base.constants.TenantConstant; +import com.qiaoba.common.base.context.BaseContext; +import com.qiaoba.common.base.enums.BaseEnum; import com.qiaoba.common.database.constants.DynamicDatasourceConstant; +import com.qiaoba.common.database.entity.DynamicDataSource; import com.qiaoba.common.database.properties.DefaultDataSourceProperties; import com.qiaoba.common.database.properties.MasterInfo; import com.qiaoba.common.database.properties.PoolInfo; import com.qiaoba.common.database.properties.SlaveInfo; +import com.qiaoba.common.database.service.DynamicDatasourceService; import com.qiaoba.common.database.utils.JdbcUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; @@ -15,10 +21,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.sql.SQLException; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -32,40 +35,88 @@ import java.util.concurrent.ConcurrentHashMap; @Configuration public class DynamicDataSourceConfig { + /** + * Bean 必须叫 dataSource + */ @Resource private DynamicDataSourceContext dataSource; @Resource private DefaultDataSourceProperties defaultDataSourceProperties; + @Resource + private DynamicDatasourceService dynamicDatasourceService; - public static Map DATA_SOURCE_MAP = new ConcurrentHashMap<>(); + /** + * 使用中的数据源 + */ + public static Map MASTER_SOURCE_MAP = new ConcurrentHashMap<>(); + + /** + * 备用数据源 + */ + public static Map> SLAVE_SOURCE_MAP = new ConcurrentHashMap<>(); /** * 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法 */ @PostConstruct public void init() { + // 加载系统默认数据源 + initDefault(); + // 加载租户数据源 + initTenant(); + } + + private void initDefault() { MasterInfo master = defaultDataSourceProperties.getMaster(); List slaves = defaultDataSourceProperties.getSlaves(); - addDataSourceToMap(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY, buildDataSource(master.getDriver(), master.getUrl(), master.getUsername(), master.getPassword(), master.getPool())); + // 将主数据源使用 + addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID, buildDataSource(master.getDriver(), master.getUrl(), master.getUsername(), master.getPassword(), master.getPool())); + dataSource.freshDataSource(MASTER_SOURCE_MAP); + // 将从数据源备用 if (CollUtil.isNotEmpty(slaves)) { for (int i = 0; i < slaves.size(); i++) { - SlaveInfo slave = slaves.get(i); - if (slave.getIsUse()) { - addDataSourceToMap(DynamicDatasourceConstant.DEFAULT_SLAVE_DATASOURCE_PREFIX + i, buildDataSource(slave.getDriver(), slave.getUrl(), slave.getUsername(), slave.getPassword(), slave.getPool())); + if (!slaves.get(i).getIsUse()) { + slaves.remove(i); + } + } + if (CollUtil.isNotEmpty(slaves)) { + SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.SLAVE_PREFIX + TenantConstant.DEFAULT_TENANT_ID, slaves); + } + } + } + + private void initTenant() { + setDefaultSetting(); + List dynamicDataSourceList = dynamicDatasourceService.loadAllTenantDatasource(); + for (DynamicDataSource dynamicDataSource : dynamicDataSourceList) { + // 将主数据源加载 + if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsMaster())) { + addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), buildDataSource(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword(), new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount()))); + dataSource.freshDataSource(MASTER_SOURCE_MAP); + } + // 将从数据源备用 + else { + if (Objects.isNull(SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId()))) { + List slaves = new ArrayList<>(); + slaves.add(datasourceToSlave(dynamicDataSource)); + SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), slaves); + } else { + List slaves = SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId()); + slaves.add(datasourceToSlave(dynamicDataSource)); } } } - dataSource.freshDataSource(DATA_SOURCE_MAP); + BaseContext.clearAllHolder(); } public void addTenantDataSource(String tenantId, String driver, String url, String username, String password, PoolInfo poolInfo) { - addDataSourceToMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(driver, url, username, password, poolInfo)); - dataSource.freshDataSource(DATA_SOURCE_MAP); + addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(driver, url, username, password, poolInfo)); + dataSource.freshDataSource(MASTER_SOURCE_MAP); } public void deleteTenantDataSource(String tenantId) { - DATA_SOURCE_MAP.remove(DynamicDatasourceConstant.MASTER_PREFIX + tenantId); - dataSource.freshDataSource(DATA_SOURCE_MAP); + MASTER_SOURCE_MAP.remove(DynamicDatasourceConstant.MASTER_PREFIX + tenantId); + dataSource.freshDataSource(MASTER_SOURCE_MAP); } private static Object buildDataSource(String driver, String url, String username, String password, PoolInfo poolInfo) { @@ -96,16 +147,27 @@ public class DynamicDataSourceConfig { */ @PreDestroy public void close() { - Set> entries = DATA_SOURCE_MAP.entrySet(); + Set> entries = MASTER_SOURCE_MAP.entrySet(); for (Map.Entry entry : entries) { DruidDataSource dataSource = (DruidDataSource) entry.getValue(); dataSource.close(); } } - private void addDataSourceToMap(String name, Object dataSource) { + private void addMasterMap(String name, Object dataSource) { if (Objects.nonNull(dataSource)) { - DATA_SOURCE_MAP.put(name, dataSource); + MASTER_SOURCE_MAP.put(name, dataSource); } } + + private SlaveInfo datasourceToSlave(DynamicDataSource dynamicDataSource) { + SlaveInfo slaveInfo = BeanUtil.copyProperties(dynamicDataSource, SlaveInfo.class); + slaveInfo.setPool(new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount())); + return slaveInfo; + } + + public void setDefaultSetting() { + BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID); + BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID); + } } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceContext.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceContext.java index dafa427..55a39d2 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceContext.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/DynamicDataSourceContext.java @@ -1,5 +1,6 @@ package com.qiaoba.common.database.config; +import com.qiaoba.common.base.constants.TenantConstant; import com.qiaoba.common.base.context.BaseContext; import com.qiaoba.common.database.constants.DynamicDatasourceConstant; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @@ -20,8 +21,8 @@ public class DynamicDataSourceContext extends AbstractRoutingDataSource { * 设置默认数据源、全部数据源,及刷新 */ public void freshDataSource(Map targetDataSources) { - //默认数据源 - super.setDefaultTargetDataSource(targetDataSources.get(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY)); + //默认数据源 (determineCurrentLookupKey 如果找不到就使用默认数据源) + super.setDefaultTargetDataSource(targetDataSources.get(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID)); //设置全部数据源 super.setTargetDataSources(targetDataSources); //刷新(即把targetDataSources刷到resolvedDataSources中去,resolvedDataSources才是我们真正存放数据源的map) diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/MybatisPlusConfig.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/MybatisPlusConfig.java index c24f11b..3470280 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/MybatisPlusConfig.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/config/MybatisPlusConfig.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.qiaoba.common.base.context.BaseContext; +import com.qiaoba.common.database.interceptors.SchemaInterceptor; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; import org.springframework.context.annotation.Bean; @@ -31,6 +32,7 @@ public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new SchemaInterceptor()); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/constants/DynamicDatasourceConstant.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/constants/DynamicDatasourceConstant.java index 79d4132..98b9dee 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/constants/DynamicDatasourceConstant.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/constants/DynamicDatasourceConstant.java @@ -9,16 +9,6 @@ package com.qiaoba.common.database.constants; */ public class DynamicDatasourceConstant { - /** - * 系统默认的主数据源名称 - */ - public static final String DEFAULT_MASTER_DATASOURCE_KEY = "master_default"; - - /** - * 系统默认的从数据源名称 - */ - public static final String DEFAULT_SLAVE_DATASOURCE_PREFIX = "slave_default_"; - /** * 主数据源拼接前缀 master_tenantId */ diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/entity/DynamicDataSource.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/entity/DynamicDataSource.java index 3bc878c..5246443 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/entity/DynamicDataSource.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/entity/DynamicDataSource.java @@ -23,6 +23,11 @@ public class DynamicDataSource { */ private String tenantId; + /** + * 主库 + */ + private String isMaster; + /** * 数据库-url */ @@ -43,4 +48,19 @@ public class DynamicDataSource { */ private String driver; + /** + * 连接池-初始化大小 + */ + private Integer initCount; + + /** + * 连接池-最小空闲数 + */ + private Integer minCount; + + /** + * 连接池-最大连接数 + */ + private Integer maxCount; + } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/factories/DynamicDataSourceFactory.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/factories/DynamicDataSourceFactory.java index b2eb147..7afd0a8 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/factories/DynamicDataSourceFactory.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/factories/DynamicDataSourceFactory.java @@ -2,7 +2,6 @@ package com.qiaoba.common.database.factories; import com.qiaoba.common.database.config.DynamicDataSourceConfig; import com.qiaoba.common.database.config.DynamicDataSourceContext; -import com.qiaoba.common.database.filters.DynamicDataSourceFilter; import com.qiaoba.common.database.properties.DefaultDataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,11 +26,6 @@ public class DynamicDataSourceFactory { return new DynamicDataSourceConfig(); } - @Bean - public DynamicDataSourceFilter handleTenantFilter() { - return new DynamicDataSourceFilter(); - } - @Bean public DefaultDataSourceProperties defaultDataSourceProperties() { return new DefaultDataSourceProperties(); diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/filters/DynamicDataSourceFilter.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/filters/DynamicDataSourceFilter.java deleted file mode 100644 index 0f86831..0000000 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/filters/DynamicDataSourceFilter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.qiaoba.common.database.filters; - -import cn.hutool.core.util.StrUtil; -import com.qiaoba.common.base.DatasourceService; -import com.qiaoba.common.base.constants.TenantConstant; -import com.qiaoba.common.base.context.BaseContext; -import com.qiaoba.common.database.constants.DynamicDatasourceConstant; -import com.qiaoba.common.web.utils.ResponseUtil; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import javax.annotation.Resource; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * 动态切换不同租户的数据源 - * - * @author ailanyin - * @version 1.0 - * @since 2023-04-25 22:48:43 - */ -@Component -@Order(-10000) -public class DynamicDataSourceFilter extends OncePerRequestFilter { - - @Resource - private DatasourceService datasourceService; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String tenantId = request.getHeader(TenantConstant.HEADER_KEY_TENANT); - // 主库或没有tenantId - if (StrUtil.isBlank(tenantId) - || TenantConstant.DEFAULT_TENANT_ID.equals(tenantId) - || request.getRequestURI().startsWith("/tenant")) { - setDefaultSetting(); - filterChain.doFilter(request, response); - return; - } - - if (!checkTenantInfo(tenantId)) { - // 检查租户的信息,是否存在,是否过期,是否禁用 - ResponseUtil.response(response, "租户可能到期或被禁用"); - return; - } - before(tenantId); - filterChain.doFilter(request, response); - after(); - } - - private void before(String tenantId) { - //设置当前租户对应的数据源 - BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + tenantId); - //设置当前租户对应的租户ID - BaseContext.setTenantId(tenantId); - //设置当前租户对应的数据库类型 - BaseContext.setDatabaseType("MySQL"); - } - - private void after() { - BaseContext.clearAllHolder(); - } - - private boolean checkTenantInfo(String tenantId) { - return datasourceService.checkTenantInfo(tenantId); - } - - private void setDefaultSetting() { - BaseContext.setDataSource(DynamicDatasourceConstant.DEFAULT_MASTER_DATASOURCE_KEY); - //设置当前租户对应的租户ID - BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID); - //设置当前租户对应的数据库类型 - BaseContext.setDatabaseType("MySQL"); - } -} diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/interceptors/SchemaInterceptor.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/interceptors/SchemaInterceptor.java index 350acc1..c01be6d 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/interceptors/SchemaInterceptor.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/interceptors/SchemaInterceptor.java @@ -1,24 +1,39 @@ package com.qiaoba.common.database.interceptors; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; -import lombok.extern.slf4j.Slf4j; +import com.qiaoba.common.base.context.BaseContext; import org.apache.ibatis.executor.statement.StatementHandler; +import org.springframework.beans.factory.annotation.Value; import java.sql.Connection; import java.sql.SQLException; +import java.util.Objects; -@Slf4j +/** + * SchemaInterceptor + * + * @author ailanyin + * @version 1.0 + * @since 2023-04-25 22:48:43 + */ public class SchemaInterceptor implements InnerInterceptor { + @Value("${spring.application.name}") + private String baseDatabase; + @Override public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) { - String sql = "use `qiaoba-boot`;"; - try { - conn.createStatement().execute(sql); - } catch (SQLException e) { - throw new RuntimeException(e); - } + if (Objects.nonNull(BaseContext.getSchema()) && BaseContext.getSchema()) { + // use qiaoba-1; + String sql = StrUtil.format("use `{}-{}`;", baseDatabase, BaseContext.getTenantId()); + try { + conn.createStatement().execute(sql); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } InnerInterceptor.super.beforePrepare(sh, conn, transactionTimeout); } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/service/DynamicDatasourceService.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/service/DynamicDatasourceService.java new file mode 100644 index 0000000..59c669f --- /dev/null +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/service/DynamicDatasourceService.java @@ -0,0 +1,22 @@ +package com.qiaoba.common.database.service; + +import com.qiaoba.common.database.entity.DynamicDataSource; + +import java.util.List; + +/** + * 动态数据源接口 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/9 13:15 + */ +public interface DynamicDatasourceService { + + /** + * 加载所有租户的数据源 + * + * @return 数据源集合 + */ + List loadAllTenantDatasource(); +} diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/SysTenant.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/SysTenant.java index 4a47bb9..006b11f 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/SysTenant.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/SysTenant.java @@ -35,7 +35,7 @@ public class SysTenant extends BaseEntity { /** * 数据库模式 */ - public static final String DATABASE_MODE = "2"; + public static final String SCHEMA_MODE = "2"; /** * 数据源模式 diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/param/SysTenantDatasourceParam.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/param/SysTenantDatasourceParam.java new file mode 100644 index 0000000..62d94ec --- /dev/null +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/entity/param/SysTenantDatasourceParam.java @@ -0,0 +1,36 @@ +package com.qiaoba.module.tenant.entity.param; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +/** + * 租户数据源查询参数 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/9 13:18 + */ +@Getter +@Setter +@NoArgsConstructor +public class SysTenantDatasourceParam implements Serializable { + + private String tenantId; + + private String isMaster; + + private String isUse; + + + public SysTenantDatasourceParam(String tenantId) { + this.tenantId = tenantId; + } + + public SysTenantDatasourceParam(String tenantId, String isMaster) { + this.tenantId = tenantId; + this.isMaster = isMaster; + } +} diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java new file mode 100644 index 0000000..5782023 --- /dev/null +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/filters/DynamicDataSourceFilter.java @@ -0,0 +1,109 @@ +package com.qiaoba.module.tenant.filters; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.qiaoba.common.base.constants.TenantConstant; +import com.qiaoba.common.base.context.BaseContext; +import com.qiaoba.common.base.enums.BaseEnum; +import com.qiaoba.common.database.config.DynamicDataSourceConfig; +import com.qiaoba.common.database.constants.DynamicDatasourceConstant; +import com.qiaoba.common.web.utils.ResponseUtil; +import com.qiaoba.module.tenant.entity.SysTenant; +import com.qiaoba.module.tenant.service.SysTenantService; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Resource; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; +import java.util.Objects; + +/** + * 动态切换不同租户的数据源 + * + * @author ailanyin + * @version 1.0 + * @since 2023-04-25 22:48:43 + */ +@Component +@Order(-10000) +public class DynamicDataSourceFilter extends OncePerRequestFilter { + + @Resource + private SysTenantService sysTenantService; + @Resource + private DynamicDataSourceConfig dynamicDataSourceConfig; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String tenantId = request.getHeader(TenantConstant.HEADER_KEY_TENANT); + // 主库或没有tenantId + if (StrUtil.isBlank(tenantId) + || TenantConstant.DEFAULT_TENANT_ID.equals(tenantId) + || request.getRequestURI().startsWith("/tenant")) { + dynamicDataSourceConfig.setDefaultSetting(); + filterChain.doFilter(request, response); + return; + } + + SysTenant sysTenant = sysTenantService.selectById(tenantId); + if (checkTenantIsNotAllow(response, sysTenant)) { + return; + } + before(sysTenant); + filterChain.doFilter(request, response); + after(); + } + + private void before(SysTenant sysTenant) { + BaseContext.setTenantId(sysTenant.getTenantId()); + BaseContext.setSchema(sysTenant.getMode().equals(SysTenant.SCHEMA_MODE)); + // 数据源模式-设置第三方数据源 + if (sysTenant.getMode().equals(SysTenant.DATASOURCE_MODE)) { + //设置当前租户对应的数据源 + BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + sysTenant.getTenantId()); + } + // 字段模式/Schema模式-数据源选择默认数据源 + else { + BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID); + } + + + } + + private void after() { + BaseContext.clearAllHolder(); + } + + /** + * 检查租户是否不允许访问 + * + * @param sysTenant sysTenant + * @return 是->不允许 + */ + private boolean checkTenantIsNotAllow(HttpServletResponse response, SysTenant sysTenant) throws IOException { + if (Objects.isNull(sysTenant)) { + // 未找到租户信息 + ResponseUtil.errorAuth(response, 401, "未找到租户信息"); + return true; + } + + if (sysTenant.getStatus().equals(BaseEnum.ABNORMAL.getCode())) { + // 封禁状态 + ResponseUtil.errorAuth(response, 401, "租户已被封禁"); + return true; + } + + if (DateUtil.compare(sysTenant.getExpireTime(), new Date()) < 0) { + // 已过期 + ResponseUtil.errorAuth(response, 401, "租户已过期"); + return true; + } + return false; + } +} diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/handler/MysqlDataHandler.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/handler/MysqlDataHandler.java index 25cd7a2..2e73b43 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/handler/MysqlDataHandler.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/handler/MysqlDataHandler.java @@ -48,7 +48,9 @@ public class MysqlDataHandler implements DataHandler { // 手动提交 conn.setAutoCommit(false); // 创建表 - initTables(conn); + if (needCreateTables) { + initTables(conn); + } // 处理 sys_config handleSysConfig(conn, tenantId); // 处理 sys_post diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantDatasourceService.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantDatasourceService.java index de8189d..d65fc05 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantDatasourceService.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/SysTenantDatasourceService.java @@ -1,6 +1,7 @@ package com.qiaoba.module.tenant.service; import com.qiaoba.module.tenant.entity.SysTenantDatasource; +import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam; import java.util.List; @@ -52,4 +53,12 @@ public interface SysTenantDatasourceService { * @return list */ List selectList(String tenantId); + + /** + * 查询租户数据源 + * + * @param param 条件 + * @return list + */ + List selectList(SysTenantDatasourceParam param); } diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/DynamicDatasourceServiceImpl.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/DynamicDatasourceServiceImpl.java new file mode 100644 index 0000000..e54a53d --- /dev/null +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/DynamicDatasourceServiceImpl.java @@ -0,0 +1,48 @@ +package com.qiaoba.module.tenant.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.qiaoba.common.base.enums.BaseEnum; +import com.qiaoba.common.base.enums.DataBaseEnum; +import com.qiaoba.common.database.entity.DynamicDataSource; +import com.qiaoba.common.database.service.DynamicDatasourceService; +import com.qiaoba.module.tenant.entity.SysTenantDatasource; +import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam; +import com.qiaoba.module.tenant.service.SysTenantDatasourceService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * 动态数据源接口 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/9 13:16 + */ +@Service +@RequiredArgsConstructor +public class DynamicDatasourceServiceImpl implements DynamicDatasourceService { + + private final SysTenantDatasourceService sysTenantDatasourceService; + + @Override + public List loadAllTenantDatasource() { + SysTenantDatasourceParam param = new SysTenantDatasourceParam(); + param.setIsUse(BaseEnum.YES.getCode()); + List datasourceList = sysTenantDatasourceService.selectList(param); + return transform(datasourceList); + } + + private List transform(List datasourceList) { + List dynamicDataSourceList = new ArrayList<>(); + for (SysTenantDatasource datasource : datasourceList) { + DynamicDataSource dynamicDataSource = BeanUtil.copyProperties(datasource, DynamicDataSource.class); + dynamicDataSource.setDriver(DataBaseEnum.getDriver(datasource.getType())); + dynamicDataSource.setUrl(DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getName())); + dynamicDataSourceList.add(dynamicDataSource); + } + return dynamicDataSourceList; + } +} diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantDatasourceServiceImpl.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantDatasourceServiceImpl.java index 0fd3c82..74590f5 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantDatasourceServiceImpl.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantDatasourceServiceImpl.java @@ -1,8 +1,10 @@ package com.qiaoba.module.tenant.service.impl; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.qiaoba.common.base.enums.BaseEnum; import com.qiaoba.module.tenant.entity.SysTenantDatasource; +import com.qiaoba.module.tenant.entity.param.SysTenantDatasourceParam; import com.qiaoba.module.tenant.mapper.SysTenantDatasourceMapper; import com.qiaoba.module.tenant.service.SysTenantDatasourceService; import lombok.RequiredArgsConstructor; @@ -25,11 +27,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic @Override public SysTenantDatasource selectMaster(String tenantId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda() - .eq(SysTenantDatasource::getTenantId, tenantId) - .eq(SysTenantDatasource::getIsMaster, BaseEnum.YES.getCode()); - return sysTenantDatasourceMapper.selectOne(wrapper); + return sysTenantDatasourceMapper.selectOne(paramToWrapper(new SysTenantDatasourceParam(tenantId, BaseEnum.YES.getCode()))); } @Override @@ -49,9 +47,21 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic @Override public List selectList(String tenantId) { + return sysTenantDatasourceMapper.selectList(paramToWrapper(new SysTenantDatasourceParam(tenantId))); + } + + @Override + public List selectList(SysTenantDatasourceParam param) { + return sysTenantDatasourceMapper.selectList(paramToWrapper(param)); + } + + private QueryWrapper paramToWrapper(SysTenantDatasourceParam param) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.lambda() - .eq(SysTenantDatasource::getTenantId, tenantId); - return sysTenantDatasourceMapper.selectList(wrapper); + .eq(StrUtil.isNotBlank(param.getTenantId()), SysTenantDatasource::getTenantId, param.getTenantId()) + .eq(StrUtil.isNotBlank(param.getIsUse()), SysTenantDatasource::getIsUse, param.getIsUse()) + .eq(StrUtil.isNotBlank(param.getIsMaster()), SysTenantDatasource::getIsMaster, param.getIsMaster()); + + return wrapper; } } diff --git a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java index ffafa64..6ac41bf 100644 --- a/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java +++ b/qiaoba-modules/qiaoba-module-tenant/src/main/java/com/qiaoba/module/tenant/service/impl/SysTenantServiceImpl.java @@ -3,7 +3,7 @@ package com.qiaoba.module.tenant.service.impl; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.qiaoba.auth.utils.SecurityUtil; -import com.qiaoba.common.base.DatasourceService; +import com.qiaoba.common.base.constants.BaseConstant; import com.qiaoba.common.base.enums.BaseEnum; import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.base.exceptions.ServiceException; @@ -38,7 +38,7 @@ import java.util.Objects; */ @Service @RequiredArgsConstructor -public class SysTenantServiceImpl implements SysTenantService, DatasourceService { +public class SysTenantServiceImpl implements SysTenantService { @Value("${qiaoba.datasource.master.driver}") private String driver; @@ -143,8 +143,8 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService if (SysTenant.COLUMN_MODE.equals(sysTenant.getMode())) { return column(); } - if (SysTenant.DATABASE_MODE.equals(sysTenant.getMode())) { - return database(sysTenant.getTenantId()); + if (SysTenant.SCHEMA_MODE.equals(sysTenant.getMode())) { + return schema(sysTenant.getTenantId()); } if (SysTenant.DATASOURCE_MODE.equals(sysTenant.getMode())) { return datasource(sysTenant.getTenantId()); @@ -163,8 +163,8 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService /** * 处理数据库模式 */ - private Connection database(String tenantId) { - String realUrl = this.url.replaceFirst(baseDatabase, baseDatabase + "-" + tenantId); + private Connection schema(String tenantId) { + String realUrl = this.url.replaceFirst(baseDatabase, baseDatabase + BaseConstant.HYPHEN_JOIN_STR + tenantId); return JdbcUtil.getConnection(driver, realUrl, username, password); } @@ -179,13 +179,4 @@ public class SysTenantServiceImpl implements SysTenantService, DatasourceService return JdbcUtil.getConnection(DataBaseEnum.getDriver(master.getType()), DataBaseEnum.getUrl(master.getType(), master.getIp(), master.getPort(), master.getName()), master.getUsername(), master.getPassword()); } - @Override - public boolean checkTenantInfo(String tenantId) { - SysTenant sysTenant = selectById(tenantId); - if (Objects.isNull(sysTenant)) { - return false; - } - // todo 检查租户有没有过期/禁用 - return sysTenant.getStatus().equals(BaseEnum.NORMAL.getCode()); - } }