diff --git a/pom.xml b/pom.xml index ac8e629..6d7e5ad 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,8 @@ 3.0.2 2.7.0 + + 2.3 @@ -137,6 +139,12 @@ ip2region ${ip2region.version} + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + com.qiaoba diff --git a/qiaoba-application/src/main/resources/application-dev.yml b/qiaoba-application/src/main/resources/application-dev.yml index a87b902..2976fcb 100644 --- a/qiaoba-application/src/main/resources/application-dev.yml +++ b/qiaoba-application/src/main/resources/application-dev.yml @@ -37,4 +37,4 @@ mybatis-plus: logging: level: - com.qiaoba: trace #开发环境输出sql日志 + com.qiaoba: info #开发环境输出sql日志 diff --git a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/enums/DataBaseEnum.java b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/enums/DataBaseEnum.java index f56ac12..a431d06 100644 --- a/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/enums/DataBaseEnum.java +++ b/qiaoba-commons/qiaoba-common-base/src/main/java/com/qiaoba/common/base/enums/DataBaseEnum.java @@ -82,4 +82,15 @@ public enum DataBaseEnum { } throw new ServiceException(StrUtil.format("未找到数据库checkSql, Type: {}", type)); } + + public static String getIp(String url, String driver) { + for (DataBaseEnum dataBaseEnum : values()) { + if (dataBaseEnum.getDriver().equals(driver)) { + url = url.replaceFirst(url.substring(0, dataBaseEnum.url.indexOf(StrUtil.EMPTY_JSON)), ""); + return url.split(StrUtil.COLON)[0]; + } + } + throw new ServiceException(StrUtil.format("未找到数据库IP, Driver: {}", driver)); + } + } 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 b6206ee..b30e37d 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 @@ -9,6 +9,7 @@ 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.entity.DynamicDataSource; +import com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor; import com.qiaoba.common.database.properties.DataSourceProperties; import com.qiaoba.common.database.service.DynamicDatasourceService; import com.qiaoba.common.database.utils.JdbcUtil; @@ -89,13 +90,28 @@ public class DynamicDataSourceConfig { private void initDefault() { List dataSources = dataSourceProperties.getDataSources(); - // 0索引作为主数据源 - addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(0))); - dataSources.remove(0); - // 非0索引的备用 + for (int i = 0; i < dataSources.size(); i++) { + // 0索引作为主数据源 + Object dataSource = buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i)); + if (Objects.isNull(dataSource)) { + // 默认的主数据源挂了 + // 加入到错误数据源Map 等待重试 + NotOnlineDatasourceMonitor.addErrorDatasource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i)); + dataSources.remove(i); + } else { + addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, dataSource); + dataSources.remove(i); + break; + } + } + + if (CollUtil.isEmpty(PRIMARY_DATASOURCE_MAP)) { + log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置"); + } + // 其他数据源备用 addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources); // 刷新数据源 - dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP); + this.dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP); } private void initTenant() { @@ -106,10 +122,19 @@ public class DynamicDataSourceConfig { for (int i = 0; i < dataSources.size(); i++) { DynamicDataSource dynamicDataSource = dataSources.get(i); if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsPrimary())) { - addPrimaryMap(tenantId, buildDataSource(dataSources.get(i).getTenantId(), dataSources.get(i))); - // 去除主要数据源,剩下皆为备用数据源 - dataSources.remove(i); - break; + Object dataSource = buildDataSource(dataSources.get(i).getTenantId(), dataSources.get(i)); + if (Objects.isNull(dataSource)) { + // 默认的主数据源挂了 + // 加入到错误数据源Map 等待重试 + NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dataSources.get(i)); + // 在数据源集合中删除, 防止将错误的数据源加载到备用数据源中 + dataSources.remove(i); + } else { + addPrimaryMap(tenantId, dataSource); + // 去除主要数据源,剩下皆为备用数据源 + dataSources.remove(i); + break; + } } } // 备用数据源 @@ -129,6 +154,17 @@ public class DynamicDataSourceConfig { public void changePrimaryDatasource(String tenantId, Object datasource) { PRIMARY_DATASOURCE_MAP.put(tenantId, datasource); + // 将数据源的类型保存 + DruidPooledConnection connection = null; + try { + connection = ((DruidDataSource) datasource).getConnection(); + TENANT_DATASOURCE_TYPE_MAP.put(tenantId, connection.getMetaData().getDatabaseProductName()); + } catch (SQLException e) { + e.printStackTrace(); + } finally { + // 归还 connection + IoUtil.close(connection); + } dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP); } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/NotOnlineDatasourceMonitor.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/NotOnlineDatasourceMonitor.java new file mode 100644 index 0000000..bcabc3e --- /dev/null +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/NotOnlineDatasourceMonitor.java @@ -0,0 +1,117 @@ +package com.qiaoba.common.database.monitor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import com.baomidou.lock.LockInfo; +import com.baomidou.lock.LockTemplate; +import com.qiaoba.common.database.config.DynamicDataSourceConfig; +import com.qiaoba.common.database.entity.DynamicDataSource; +import com.qiaoba.common.database.utils.JdbcUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 不在线的数据源监控 + * 尝试连接,如果连接成功,加入到备用数据源 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 10:46 + */ +@Component +@Slf4j +public class NotOnlineDatasourceMonitor { + + /** + * 错误数据源(连接中断) + *

+ * key: 租户ID + * value: 错误数据源 list + */ + public static Map> ERROR_DATASOURCE_MAP = new ConcurrentHashMap<>(); + + private static final String LOCK_KEY = "lock4j:notOnlineDatasourceMonitor"; + + @Resource + private LockTemplate lockTemplate; + + + @PostConstruct + public void init() { + // 10s 运行一次 + new Timer().schedule(new TimerTask() { + @Override + public void run() { + // 项目启动时加载数据源还未完成 + if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) { + return; + } + // 没有暂时失联的数据源, 直接结束 + if (CollUtil.isEmpty(ERROR_DATASOURCE_MAP)) { + return; + } + // expire = -1 锁自动续期, 防止数据源过多或异常等待, 超过默认锁 30s + final LockInfo lockInfo = lockTemplate.lock(LOCK_KEY, -1, 1000); + //申请锁失败 说明集群中其他设备正在执行监控 + if (null == lockInfo) { + return; + } + //申请锁成功 + try { + log.trace("开始-[错误数据源重试]-线程, 时间: {}", new Date()); + tryConnect(); + log.trace("结束-[错误数据源重试]-线程, 时间: {}", new Date()); + } finally { + // 释放锁 + lockTemplate.releaseLock(lockInfo); + } + + } + }, 0, 10 * 1000); + } + + private void tryConnect() { + Set tenantIds = ERROR_DATASOURCE_MAP.keySet(); + for (String tenantId : tenantIds) { + List errorDatasourceList = ERROR_DATASOURCE_MAP.get(tenantId); + for (int i = 0; i < errorDatasourceList.size(); i++) { + DynamicDataSource errorDatasource = errorDatasourceList.get(i); + // 说明连接成功 + boolean check = JdbcUtil.checkConnect(errorDatasource.getDriver(), errorDatasource.getUrl(), errorDatasource.getUsername(), errorDatasource.getPassword()); + if (check) { + log.info("数据源重连成功, Url: {}", errorDatasource.getUrl()); + // 从errorMap中删除 + errorDatasourceList.remove(errorDatasource); + if (CollUtil.isEmpty(errorDatasourceList)) { + ERROR_DATASOURCE_MAP.remove(tenantId); + } + // 加入到备用Map中 + addBackupMap(tenantId, errorDatasource); + } + } + } + } + + private void addBackupMap(String tenantId, DynamicDataSource dataSource) { + List dataSourceList = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId); + if (CollUtil.isEmpty(dataSourceList)) { + DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.put(tenantId, ListUtil.toList(dataSource)); + } else { + dataSourceList.add(dataSource); + } + } + + public static void addErrorDatasource(String tenantId, DynamicDataSource dataSource) { + List errorDataSourceList = NotOnlineDatasourceMonitor.ERROR_DATASOURCE_MAP.get(tenantId); + if (CollUtil.isEmpty(errorDataSourceList)) { + NotOnlineDatasourceMonitor.ERROR_DATASOURCE_MAP.put(tenantId, ListUtil.toList(dataSource)); + } else { + errorDataSourceList.add(dataSource); + } + } +} diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/DatasourceConnectionMonitor.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/OnlineDatasourceMonitor.java similarity index 53% rename from qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/DatasourceConnectionMonitor.java rename to qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/OnlineDatasourceMonitor.java index 945cc11..e0b600c 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/DatasourceConnectionMonitor.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/monitor/OnlineDatasourceMonitor.java @@ -1,5 +1,6 @@ package com.qiaoba.common.database.monitor; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import com.alibaba.druid.pool.DruidDataSource; import com.baomidou.lock.LockInfo; @@ -9,39 +10,40 @@ import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.database.config.DynamicDataSourceConfig; import com.qiaoba.common.database.entity.DynamicDataSource; import com.qiaoba.common.database.service.DynamicDatasourceService; +import com.qiaoba.common.database.utils.JdbcUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** - * 监控-数据源连接监控 + * 在线的数据源监控 + * 尝试连接, 如果连接不成功, 替换可用数据源, 将失败数据源加入到错误数据源, 等待重试 * * @author ailanyin * @version 1.0 - * @since 2023/6/14 14:48 + * @since 2023/6/26 10:46 */ @Component @Slf4j -public class DatasourceConnectionMonitor { +public class OnlineDatasourceMonitor { + + private static final String LOCK_KEY = "lock4j:onlineDatasourceMonitor"; + private static Map WAIT_UPDATE_DATASOURCE_STATUS = new ConcurrentHashMap<>(); + private static Map> WAIT_ADD_ERROR_MAP = new ConcurrentHashMap<>(); + + @Resource + private LockTemplate lockTemplate; @Resource private DynamicDataSourceConfig dynamicDataSourceConfig; @Resource private DynamicDatasourceService dynamicDatasourceService; - @Resource - private LockTemplate lockTemplate; - - private static Map WAIT_UPDATE_DATASOURCE_STATUS = new ConcurrentHashMap<>(); - - private static final String LOCK_KEY = "lock4j:datasourceConnectionMonitor"; @PostConstruct public void init() { @@ -49,7 +51,7 @@ public class DatasourceConnectionMonitor { new Timer().schedule(new TimerTask() { @Override public void run() { - // 项目加载数据源还未完成 + // 项目启动时加载数据源还未完成 if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) { return; } @@ -62,8 +64,9 @@ public class DatasourceConnectionMonitor { } //申请锁成功 try { - // 执行监控 - datasourceConnectionMonitor(); + log.trace("开始运行数据源监控线程, 时间: {}", new Date()); + tryConnect(); + log.trace("结束运行数据源监控线程, 时间: {}", new Date()); } finally { // 释放锁 lockTemplate.releaseLock(lockInfo); @@ -73,83 +76,62 @@ public class DatasourceConnectionMonitor { }, 0, 1000); } - /** - * 核心监控内容 - */ - private void datasourceConnectionMonitor() { - log.trace("开始运行数据源监控线程, 时间: {}", new Date()); + private void tryConnect() { for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) { Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(tenantId); + DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId); if (Objects.isNull(primary)) { // 说明初始化主要数据源的时候出错 log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId); - // 切换备用数据源 - changePrimary(tenantId); - } else { - DruidDataSource dataSource = (DruidDataSource) primary; - Connection connection = null; - try { - connection = dataSource.getConnection(); - if (check(connection, tenantId)) { - // 说明数据源正常 - log.trace("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId); - // 主数据 处理任务 - if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) { - handleJob(); - } - continue; - } + // 切换备用数据源为主数据源 + backToPrimary(tenantId); + continue; + } - log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId); - // 主数据源异常 切换备用数据源 - if (changePrimary(tenantId)) { - // 备用切换成功, 关闭原有异常数据源 - IoUtil.close(dataSource); + DruidDataSource dataSource = (DruidDataSource) primary; + Connection connection = null; + try { + connection = dataSource.getConnection(); + if (JdbcUtil.checkConnect(connection)) { + // 说明数据源正常 + log.trace("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId); + // 系统默认主数据源 处理任务 + if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) { + handleUpdateDatasourceStatus(); + handleErrorDatasource(); } - } catch (SQLException e) { - - } finally { - IoUtil.close(connection); + continue; } + + log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId); + // 主数据源异常 切换备用数据源 + if (!backToPrimary(tenantId)) { + // 备用切换失败 + // 关闭原有异常数据源 + IoUtil.close(dataSource); + // 在数据源Map中删除 + DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.remove(tenantId); + } + // 将原有异常数据源加入到错误数据源Map, 等待重试 + addErrorDatasource(tenantId, dataSource); + + } catch (SQLException e) { + + } finally { + IoUtil.close(connection); } } - - log.trace("结束运行数据源监控线程, 时间: {}", new Date()); } /** - * 检查数据源连接可用性 - * - * @param conn 数据源 - * @param tenantId 租户ID - * @return 结果 - */ - private Boolean check(Connection conn, String tenantId) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = conn.createStatement(); - // 允许 2s 延时 - stmt.setQueryTimeout(2); - rs = stmt.executeQuery(DataBaseEnum.getCheckSql(DynamicDataSourceConfig.TENANT_DATASOURCE_TYPE_MAP.get(tenantId))); - return true; - } catch (Exception e) { - return false; - } finally { - IoUtil.close(rs); - IoUtil.close(stmt); - } - } - - /** - * 切换主数据源 + * 切换备用数据源为主数据源 * * @param tenantId 租户ID */ - private Boolean changePrimary(String tenantId) { + private Boolean backToPrimary(String tenantId) { // 备用数据源 List dataSources = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId); - if (Objects.isNull(dataSources)) { + if (CollUtil.isEmpty(dataSources)) { log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId); return false; } @@ -184,7 +166,7 @@ public class DatasourceConnectionMonitor { } } - private void handleJob() { + private void handleUpdateDatasourceStatus() { Set keys = WAIT_UPDATE_DATASOURCE_STATUS.keySet(); for (String key : keys) { try { @@ -195,8 +177,46 @@ public class DatasourceConnectionMonitor { WAIT_UPDATE_DATASOURCE_STATUS.remove(key); log.info("更新数据库中租户数据源状态完成, 租户ID: {}", key); } catch (Exception e) { - log.error("更新数据库中租户数据源状态完成, 租户ID: {}, 失败原因: {}", key, e.getMessage()); + log.error("更新数据库中租户数据源状态未完成, 租户ID: {}, 失败原因: {}", key, e.getMessage()); } } } + + private void handleErrorDatasource() { + Set tenantIds = WAIT_ADD_ERROR_MAP.keySet(); + for (String tenantId : tenantIds) { + List ipList = WAIT_ADD_ERROR_MAP.get(tenantId); + for (String ip : ipList) { + NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dynamicDatasourceService.selectByIp(tenantId, ip)); + } + WAIT_ADD_ERROR_MAP.remove(tenantId); + } + } + + private void addErrorDatasource(String tenantId, DruidDataSource dataSource) { + // 主系统 + if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) { + DynamicDataSource dynamicDataSource = new DynamicDataSource(); + dynamicDataSource.setPassword(dataSource.getPassword()); + dynamicDataSource.setUsername(dataSource.getUsername()); + dynamicDataSource.setUrl(dataSource.getUrl()); + dynamicDataSource.setDriver(dataSource.getDriverClassName()); + dynamicDataSource.setTenantId(tenantId); + dynamicDataSource.setInitialSize(dataSource.getInitialSize()); + dynamicDataSource.setMaxActive(dataSource.getMaxActive()); + dynamicDataSource.setMinIdle(dataSource.getMinIdle()); + NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dynamicDataSource); + return; + } + + + // 普通租户 + String ip = DataBaseEnum.getIp(dataSource.getUrl(), dataSource.getDriverClassName()); + List errorIpList = WAIT_ADD_ERROR_MAP.get(tenantId); + if (CollUtil.isEmpty(errorIpList)) { + WAIT_ADD_ERROR_MAP.put(tenantId, CollUtil.toList(ip)); + } else { + errorIpList.add(ip); + } + } } 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 index 6886db8..9729ea2 100644 --- 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 @@ -28,4 +28,13 @@ public interface DynamicDatasourceService { * @param datasourceId 数据源ID */ void changePrimaryDatasource(String tenantId, String datasourceId); + + /** + * 通过IP查询 + * + * @param tenantId tenantId + * @param ip ip + * @return obj + */ + DynamicDataSource selectByIp(String tenantId, String ip); } diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/utils/JdbcUtil.java b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/utils/JdbcUtil.java index b9d5ad4..3c5e1b1 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/utils/JdbcUtil.java +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/java/com/qiaoba/common/database/utils/JdbcUtil.java @@ -2,11 +2,15 @@ package com.qiaoba.common.database.utils; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; +import com.qiaoba.common.base.enums.DataBaseEnum; import com.qiaoba.common.base.exceptions.ServiceException; +import com.qiaoba.common.database.config.DynamicDataSourceConfig; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; /** * JdbcUtil @@ -42,6 +46,29 @@ public class JdbcUtil { } } + /** + * 检查数据源连接可用性 + * + * @param conn 数据源 + * @return 结果 + */ + public static boolean checkConnect(Connection conn) { + Statement stmt = null; + ResultSet rs = null; + try { + stmt = conn.createStatement(); + // 允许 2s 延时 + stmt.setQueryTimeout(2); + rs = stmt.executeQuery(DataBaseEnum.getCheckSql(conn.getMetaData().getDatabaseProductName())); + return true; + } catch (Exception e) { + return false; + } finally { + IoUtil.close(rs); + IoUtil.close(stmt); + } + } + public static Connection getConnection(String driver, String url, String username, String password) { Connection conn = null; try { diff --git a/qiaoba-commons/qiaoba-common-datasource/src/main/resources/META-INF/spring.factories b/qiaoba-commons/qiaoba-common-datasource/src/main/resources/META-INF/spring.factories index d0230dc..ee3a243 100644 --- a/qiaoba-commons/qiaoba-common-datasource/src/main/resources/META-INF/spring.factories +++ b/qiaoba-commons/qiaoba-common-datasource/src/main/resources/META-INF/spring.factories @@ -1,6 +1,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.qiaoba.common.database.factories.DynamicDataSourceFactory,\ - com.qiaoba.common.database.monitor.DatasourceConnectionMonitor,\ + com.qiaoba.common.database.monitor.OnlineDatasourceMonitor,\ + com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor,\ com.qiaoba.common.database.handlers.schema.SchemaHandlerFactory,\ com.qiaoba.common.database.handlers.schema.MysqlSchemaHandler,\ com.qiaoba.common.database.config.MybatisPlusConfig diff --git a/qiaoba-modules/pom.xml b/qiaoba-modules/pom.xml index ff7861b..e16b796 100644 --- a/qiaoba-modules/pom.xml +++ b/qiaoba-modules/pom.xml @@ -18,6 +18,7 @@ qiaoba-module-monitor qiaoba-module-tenant qiaoba-module-demo + qiaoba-module-generator qiaoba-modules: 新建的模块, 统一放在这个模块下面 diff --git a/qiaoba-modules/qiaoba-module-generator/pom.xml b/qiaoba-modules/qiaoba-module-generator/pom.xml new file mode 100644 index 0000000..9780049 --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/pom.xml @@ -0,0 +1,34 @@ + + + + qiaoba-modules + com.qiaoba + 1.0 + + 4.0.0 + + qiaoba-module-generator + + 代码生成 + + + + com.qiaoba + qiaoba-common-base + + + com.qiaoba + qiaoba-common-datasource + + + com.qiaoba + qiaoba-common-web + + + org.apache.velocity + velocity-engine-core + + + diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/controller/GenController.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/controller/GenController.java new file mode 100644 index 0000000..928c111 --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/controller/GenController.java @@ -0,0 +1,20 @@ +package com.qiaoba.module.generator.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 代码生成 Web层 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 9:09 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/tool/gen") +public class GenController { +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/dto/TableDto.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/dto/TableDto.java new file mode 100644 index 0000000..0e7074a --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/dto/TableDto.java @@ -0,0 +1,36 @@ +package com.qiaoba.module.generator.entity.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 数据库表信息 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 9:21 + */ +@Data +public class TableDto implements Serializable { + + /** + * 表名称 + */ + private String tableName; + + /** + * 表描述 + */ + private String tableComment; + + /** + * 数据库类型 Mysql/Oracle/PgSql/Sql Server + */ + private String dbType; + + /** + * 模式 + */ + private String schema; +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/vo/DbTableVo.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/vo/DbTableVo.java new file mode 100644 index 0000000..48d8e3e --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/entity/vo/DbTableVo.java @@ -0,0 +1,38 @@ +package com.qiaoba.module.generator.entity.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 数据库表信息 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 9:21 + */ +@Data +public class DbTableVo implements Serializable { + + /** + * 表名称 + */ + private String tableName; + + /** + * 表描述 + */ + private String tableComment; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/mapper/TableMapper.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/mapper/TableMapper.java new file mode 100644 index 0000000..176e737 --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/mapper/TableMapper.java @@ -0,0 +1,25 @@ +package com.qiaoba.module.generator.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.qiaoba.module.generator.entity.dto.TableDto; +import com.qiaoba.module.generator.entity.vo.DbTableVo; +import org.apache.ibatis.annotations.Param; + +/** + * 数据库表 数据层 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 9:24 + */ +public interface TableMapper { + + /** + * 查询数据库表 + * + * @param page 分页 + * @param dto 查询参数 + * @return list + */ + Page selectPageDbTableList(Page page, @Param("dto") TableDto dto); +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/TableService.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/TableService.java new file mode 100644 index 0000000..63517ba --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/TableService.java @@ -0,0 +1,25 @@ +package com.qiaoba.module.generator.service; + +import com.qiaoba.common.database.entity.PageQuery; +import com.qiaoba.common.database.entity.TableDataInfo; +import com.qiaoba.module.generator.entity.dto.TableDto; +import com.qiaoba.module.generator.entity.vo.DbTableVo; + +/** + * 数据库表 服务层 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 9:24 + */ +public interface TableService { + + /** + * 分页查询数据库表 + * + * @param dto 查询条件 + * @param pageQuery 分页信息 + * @return list + */ + TableDataInfo selectPageDbTableList(TableDto dto, PageQuery pageQuery); +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/impl/TableServiceImpl.java b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/impl/TableServiceImpl.java new file mode 100644 index 0000000..e6a3a8e --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/java/com/qiaoba/module/generator/service/impl/TableServiceImpl.java @@ -0,0 +1,41 @@ +package com.qiaoba.module.generator.service.impl; + +import com.qiaoba.common.base.context.BaseContext; +import com.qiaoba.common.base.enums.DataBaseEnum; +import com.qiaoba.common.database.config.DynamicDataSourceConfig; +import com.qiaoba.common.database.entity.PageQuery; +import com.qiaoba.common.database.entity.TableDataInfo; +import com.qiaoba.module.generator.entity.dto.TableDto; +import com.qiaoba.module.generator.entity.vo.DbTableVo; +import com.qiaoba.module.generator.mapper.TableMapper; +import com.qiaoba.module.generator.service.TableService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 数据库表 服务层实现 + * + * @author ailanyin + * @version 1.0 + * @since 2023/6/26 10:12 + */ +@Service +@RequiredArgsConstructor +public class TableServiceImpl implements TableService { + + private final TableMapper tableMapper; + + @Override + public TableDataInfo selectPageDbTableList(TableDto dto, PageQuery pageQuery) { + String dbType = DynamicDataSourceConfig.TENANT_DATASOURCE_TYPE_MAP.get(BaseContext.getTenantId()); + dto.setDbType(dbType); + dto.setSchema(selectSchema()); + return TableDataInfo.build(tableMapper.selectPageDbTableList(pageQuery.build(), dto)); + } + + private String selectSchema() { + // PgSQL 需要设置 schema + //DataBaseEnum.POSTGRE_SQL.getType().equals(dbType) + return null; + } +} diff --git a/qiaoba-modules/qiaoba-module-generator/src/main/resources/mapper/TableMapper.xml b/qiaoba-modules/qiaoba-module-generator/src/main/resources/mapper/TableMapper.xml new file mode 100644 index 0000000..8c36ab9 --- /dev/null +++ b/qiaoba-modules/qiaoba-module-generator/src/main/resources/mapper/TableMapper.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + 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 index a58e834..a429d0c 100644 --- 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 @@ -24,8 +24,16 @@ public class SysTenantDatasourceParam implements Serializable { private String isPrimary; + private String ip; + public SysTenantDatasourceParam(String tenantId) { this.tenantId = tenantId; } + public SysTenantDatasourceParam(String tenantId, String isPrimary) { + this.tenantId = tenantId; + this.isPrimary = isPrimary; + } + + } 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 327821a..6a20f70 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 @@ -22,6 +22,15 @@ public interface SysTenantDatasourceService { */ SysTenantDatasource selectPrimary(String tenantId); + /** + * 通过IP查询 + * + * @param tenantId tenantId + * @param ip ip + * @return obj + */ + SysTenantDatasource selectByIp(String tenantId, String ip); + /** * 新增 * 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 index 1ec2f25..ed1bbbb 100644 --- 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 @@ -1,8 +1,10 @@ package com.qiaoba.module.tenant.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; import com.qiaoba.common.base.enums.BaseEnum; import com.qiaoba.common.base.enums.DataBaseEnum; +import com.qiaoba.common.base.exceptions.ServiceException; import com.qiaoba.common.database.entity.DynamicDataSource; import com.qiaoba.common.database.service.DynamicDatasourceService; import com.qiaoba.module.tenant.entity.SysTenantDatasource; @@ -11,10 +13,7 @@ import com.qiaoba.module.tenant.service.SysTenantDatasourceService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * 动态数据源接口 @@ -35,7 +34,7 @@ public class DynamicDatasourceServiceImpl implements DynamicDatasourceService { List tenantIds = sysTenantDatasourceService.selectTenantIds(); for (String tenantId : tenantIds) { List datasourceList = sysTenantDatasourceService.selectList(new SysTenantDatasourceParam(tenantId)); - datasourceMap.put(tenantId, transform(datasourceList)); + datasourceMap.put(tenantId, transformList(datasourceList)); } return datasourceMap; } @@ -51,17 +50,30 @@ public class DynamicDatasourceServiceImpl implements DynamicDatasourceService { sysTenantDatasourceService.setBackupDatasourceExcludeId(tenantId, datasourceId); } - private List transform(List datasourceList) { + @Override + public DynamicDataSource selectByIp(String tenantId, String ip) { + SysTenantDatasource sysTenantDatasource = sysTenantDatasourceService.selectByIp(tenantId, ip); + if (Objects.isNull(sysTenantDatasource)) { + throw new ServiceException(StrUtil.format("未找到数据源,查询方式: {}", ip)); + } + return transform(sysTenantDatasource); + } + + private List transformList(List datasourceList) { List dynamicDataSourceList = new ArrayList<>(); for (SysTenantDatasource datasource : datasourceList) { - DynamicDataSource dynamicDataSource = BeanUtil.copyProperties(datasource, DynamicDataSource.class); - dynamicDataSource.setInitialSize(datasource.getInitCount()); - dynamicDataSource.setMinIdle(datasource.getMinCount()); - dynamicDataSource.setMaxActive(datasource.getMaxCount()); - dynamicDataSource.setDriver(DataBaseEnum.getDriver(datasource.getType())); - dynamicDataSource.setUrl(DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getName())); - dynamicDataSourceList.add(dynamicDataSource); + dynamicDataSourceList.add(transform(datasource)); } return dynamicDataSourceList; } + + private DynamicDataSource transform(SysTenantDatasource datasource) { + DynamicDataSource dynamicDataSource = BeanUtil.copyProperties(datasource, DynamicDataSource.class); + dynamicDataSource.setInitialSize(datasource.getInitCount()); + dynamicDataSource.setMinIdle(datasource.getMinCount()); + dynamicDataSource.setMaxActive(datasource.getMaxCount()); + dynamicDataSource.setDriver(DataBaseEnum.getDriver(datasource.getType())); + dynamicDataSource.setUrl(DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getName())); + return dynamicDataSource; + } } 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 e3ab75f..f3eb95f 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 @@ -30,6 +30,13 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic return sysTenantDatasourceMapper.selectOne(paramToWrapper(new SysTenantDatasourceParam(tenantId, BaseEnum.YES.getCode()))); } + @Override + public SysTenantDatasource selectByIp(String tenantId, String ip) { + SysTenantDatasourceParam param = new SysTenantDatasourceParam(tenantId); + param.setIp(ip); + return sysTenantDatasourceMapper.selectOne(paramToWrapper(param)); + } + @Override public int insert(SysTenantDatasource sysTenantDatasource) { return sysTenantDatasourceMapper.insert(sysTenantDatasource); @@ -69,6 +76,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic QueryWrapper wrapper = new QueryWrapper<>(); wrapper.lambda() .eq(StrUtil.isNotBlank(param.getTenantId()), SysTenantDatasource::getTenantId, param.getTenantId()) + .eq(StrUtil.isNotBlank(param.getIp()), SysTenantDatasource::getIp, param.getIp()) .eq(StrUtil.isNotBlank(param.getIsPrimary()), SysTenantDatasource::getIsPrimary, param.getIsPrimary()); return wrapper; }