From f0844de9eccf0aab980c39cc17b00ae61f7f1947 Mon Sep 17 00:00:00 2001 From: tangwei Date: Tue, 8 Sep 2026 11:28:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=BA=86=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=95=B0=E6=8D=AE=E6=BA=90=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/datasource/DataSourceKeys.java | 18 +- .../datasource/DynamicDataSource.java | 124 +++++++- .../datasource/DynamicDataSourceConfig.java | 34 +- .../DynamicDataSourceController.java | 106 +++++++ .../datasource/DynamicDataSourceRegistry.java | 300 ++++++++++++++++++ .../datasource/GenDatasourceConf.java | 125 ++++++++ .../src/main/resources/application-dev.yml | 64 ---- .../src/main/resources/application-prod.yml | 64 ---- 8 files changed, 665 insertions(+), 170 deletions(-) create mode 100644 backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceController.java create mode 100644 backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceRegistry.java create mode 100644 backend/src/main/java/com/yfd/platform/datasource/GenDatasourceConf.java diff --git a/backend/src/main/java/com/yfd/platform/datasource/DataSourceKeys.java b/backend/src/main/java/com/yfd/platform/datasource/DataSourceKeys.java index 19a8459a..1b6960ee 100644 --- a/backend/src/main/java/com/yfd/platform/datasource/DataSourceKeys.java +++ b/backend/src/main/java/com/yfd/platform/datasource/DataSourceKeys.java @@ -2,21 +2,31 @@ package com.yfd.platform.datasource; /** * 数据源 key 常量 + *

+ * 系统内置 key(yml 固定配置): + *

+ * 扩展数据源 key 格式:{@code ext-{type}-{序号}},如 {@code ext-oracle-01}、{@code ext-mysql-01}, + * 由数据库表 {@code GEN_DATASOURCE_CONF.DS_KEY} 配置,运行时动态注册。 */ public interface DataSourceKeys { - /** 达梦主库 */ + /** 达梦主库(业务主库,yml 固定配置) */ String DM_MASTER = "dm-master"; /** 达梦从库 */ String DM_SLAVE = "dm-slave"; - /** Oracle 主库 */ + /** Oracle 主库(由数据库表 GEN_DATASOURCE_CONF 动态注册,不再从 yml 配置) */ String ORACLE_MASTER = "oracle-master"; - /** Oracle 从库 */ + /** Oracle 从库(由数据库表 GEN_DATASOURCE_CONF 动态注册,不再从 yml 配置) */ String ORACLE_SLAVE = "oracle-slave"; /** 默认数据源 */ String DEFAULT = DM_MASTER; -} + + /** 扩展数据源 key 前缀 */ + String EXT_PREFIX = "ext-"; +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java index 7517cb93..2d5f62b6 100644 --- a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSource.java @@ -1,23 +1,39 @@ package com.yfd.platform.datasource; +import com.alibaba.druid.pool.DruidDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; +import java.util.Set; + +/** + * 动态数据源路由,支持运行时注册/注销/热刷新 + * + * @author wxy + */ +public class DynamicDataSource extends AbstractRoutingDataSource { + + private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class); -/****************************** - * 用途说明: - * 作者姓名: wxy - * 创建时间: 2022/9/23 17:47 - ******************************/ -public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal contextHolder = new ThreadLocal<>(); - public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { + /** + * 用户手动注册的目标数据源(含 yml 配置 + 数据库表注册) + */ + private final Map targetDataSources = new HashMap<>(); + + private final Object lock = new Object(); + + public DynamicDataSource(DataSource defaultTargetDataSource, Map initialDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); - super.setTargetDataSources(new HashMap(targetDataSources)); - super.afterPropertiesSet(); + if (initialDataSources != null) { + this.targetDataSources.putAll(initialDataSources); + } + rebuildResolvedDataSources(); } @Override @@ -38,10 +54,96 @@ public class DynamicDataSource extends AbstractRoutingDataSource { } /** - * 根据数据源 key 获取目标数据源实例(如 dm-master / oracle-master) + * 根据数据源 key 获取目标数据源实例 */ public DataSource getDataSourceByKey(String key) { Map resolved = getResolvedDataSources(); return resolved == null ? null : resolved.get(key); } -} + + /** + * 运行时注册一个数据源,线程安全,立即生效 + * + * @param key 路由 key,如 "ext-oracle-01" + * @param ds 数据源实例 + */ + public void register(String key, DataSource ds) { + synchronized (lock) { + // 如果已有旧数据源且不同实例,关闭旧连接池 + Object old = targetDataSources.get(key); + if (old instanceof DruidDataSource && old != ds) { + closeQuietly((DruidDataSource) old); + } + targetDataSources.put(key, ds); + rebuildResolvedDataSources(); + log.info("数据源已注册: key={}, type={}", key, ds.getClass().getSimpleName()); + } + } + + /** + * 运行时注销一个数据源,线程安全,立即生效 + * + * @param key 路由 key + * @return true-已注销并关闭连接池;false-未找到该 key + */ + public boolean unregister(String key) { + synchronized (lock) { + Object removed = targetDataSources.remove(key); + if (removed == null) { + return false; + } + rebuildResolvedDataSources(); + closeQuietly(removed); + log.info("数据源已注销: key={}", key); + return true; + } + } + + /** + * 批量替换同名数据源(先保留旧连接池,注册新实例,再关闭旧的) + * 用于热刷新时零中断更新 + */ + public void registerReplace(String key, DataSource newDs) { + synchronized (lock) { + Object old = targetDataSources.get(key); + targetDataSources.put(key, newDs); + rebuildResolvedDataSources(); + closeQuietly(old); + log.info("数据源已热替换: key={}", key); + } + } + + /** + * 获取所有已注册的数据源 key + */ + @SuppressWarnings("unchecked") + public Set getRegisteredKeys() { + Map resolved = getResolvedDataSources(); + return resolved == null ? Set.of() : resolved.keySet(); + } + + /** + * 判断 key 是否已注册 + */ + public boolean containsKey(String key) { + return getDataSourceByKey(key) != null; + } + + /** + * 重建路由表(内部同步需由调用方保证线程安全) + */ + private void rebuildResolvedDataSources() { + super.setTargetDataSources(new HashMap<>(targetDataSources)); + super.afterPropertiesSet(); + } + + private void closeQuietly(Object ds) { + if (ds instanceof DruidDataSource) { + try { + ((DruidDataSource) ds).close(); + } catch (Exception e) { + log.warn("关闭旧数据源连接池异常: {}", e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java index e0d4b0a0..1632f711 100644 --- a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java @@ -14,17 +14,17 @@ import java.util.HashMap; import java.util.Map; /** - * 动态数据源配置,支持 达梦(DM) 和 Oracle 两种数据库,每种各有主从(master/slave)。 + * 动态数据源配置,仅保留业务主库(达梦 DM)的 yml 配置。 *

- * 每个数据源可通过配置项 {@code spring.datasource.druid..enabled} 按需开启/关闭, - * 关闭后不会创建对应的连接池,也不会注册到路由数据源中。 + * 其他数据源(如 Oracle、MySQL、PostgreSQL 等)通过数据库表 + * {@code GEN_DATASOURCE_CONF} 动态注册,见 {@link DynamicDataSourceRegistry}。 * * @author yfd */ @Configuration public class DynamicDataSourceConfig { - // ==================== 达梦(DM) 数据源 ==================== + // ==================== 达梦(DM) 数据源(业务主库,yml 固定配置) ==================== @Bean @ConfigurationProperties("spring.datasource.druid.dm-master") @@ -40,37 +40,17 @@ public class DynamicDataSourceConfig { return newDruidDataSource(); } - // ==================== Oracle 数据源 ==================== - - @Bean - @ConfigurationProperties("spring.datasource.druid.oracle-master") - @ConditionalOnProperty(prefix = "spring.datasource.druid.oracle-master", name = "enabled", havingValue = "true", matchIfMissing = true) - public DataSource oracleMasterDataSource() { - return newDruidDataSource(); - } - - @Bean - @ConfigurationProperties("spring.datasource.druid.oracle-slave") - @ConditionalOnProperty(prefix = "spring.datasource.druid.oracle-slave", name = "enabled", havingValue = "true", matchIfMissing = true) - public DataSource oracleSlaveDataSource() { - return newDruidDataSource(); - } - // ==================== 路由数据源 ==================== @Bean @Primary public DynamicDataSource dataSource(@Qualifier("dmMasterDataSource") ObjectProvider dmMaster, - @Qualifier("dmSlaveDataSource") ObjectProvider dmSlave, - @Qualifier("oracleMasterDataSource") ObjectProvider oracleMaster, - @Qualifier("oracleSlaveDataSource") ObjectProvider oracleSlave) { + @Qualifier("dmSlaveDataSource") ObjectProvider dmSlave) { Map targetDataSources = new HashMap<>(); putIfPresent(targetDataSources, DataSourceKeys.DM_MASTER, dmMaster); putIfPresent(targetDataSources, DataSourceKeys.DM_SLAVE, dmSlave); - putIfPresent(targetDataSources, DataSourceKeys.ORACLE_MASTER, oracleMaster); - putIfPresent(targetDataSources, DataSourceKeys.ORACLE_SLAVE, oracleSlave); - // 默认优先使用达梦主库;若未开启则回退到任意一个已开启的数据源 + // 默认使用达梦主库 DataSource defaultDataSource = dmMaster.getIfAvailable(); if (defaultDataSource == null) { defaultDataSource = targetDataSources.values().stream().findFirst() @@ -92,4 +72,4 @@ public class DynamicDataSourceConfig { targetDataSources.put(key, dataSource); } } -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceController.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceController.java new file mode 100644 index 00000000..43ae5b35 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceController.java @@ -0,0 +1,106 @@ +package com.yfd.platform.datasource; + +import com.yfd.platform.config.ResponseResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.sql.DataSource; +import java.util.*; + +/** + * 动态数据源管理控制器,提供运行时数据源注册/注销/热刷新/列表查询等操作。 + *

+ * 数据源配置存储在数据库表 {@code GEN_DATASOURCE_CONF} 中,通过此接口可: + *

    + *
  • 查看当前已注册的所有数据源
  • + *
  • 手动注册/注销单个数据源
  • + *
  • 从数据库表热刷新全部数据源
  • + *
+ */ +@RestController +@RequestMapping("/datasource") +@Tag(name = "动态数据源管理") +@Validated +public class DynamicDataSourceController { + + @Resource + private DynamicDataSourceRegistry dynamicDataSourceRegistry; + + @Resource + private DynamicDataSource dynamicDataSource; + + /** + * 列出所有已注册的数据源(含系统内置 dm-master/dm-slave) + */ + @GetMapping("/list") + @Operation(summary = "列出所有已注册的数据源") + public ResponseResult listDataSources() { + Set keys = dynamicDataSource.getRegisteredKeys(); + List> list = new ArrayList<>(keys.size()); + for (Object key : keys) { + Map item = new LinkedHashMap<>(); + item.put("key", key); + DataSource ds = dynamicDataSource.getDataSourceByKey(key.toString()); + item.put("type", ds == null ? "unknown" : ds.getClass().getSimpleName()); + item.put("alive", ds != null); + list.add(item); + } + // 内置 key 排前,扩展 key 排后 + list.sort(Comparator.comparing(m -> { + String k = (String) m.get("key"); + if (DataSourceKeys.DM_MASTER.equals(k)) return 0; + if (DataSourceKeys.DM_SLAVE.equals(k)) return 1; + return 2; + })); + return ResponseResult.successData(list); + } + + /** + * 热刷新所有数据源:从数据库表重新读取配置并注册 + */ + @PostMapping("/refresh") + @Operation(summary = "热刷新数据源(从数据库表重新读取并注册)") + public ResponseResult refreshDataSources() { + dynamicDataSourceRegistry.refresh(); + return ResponseResult.success("动态数据源热刷新完成"); + } + + /** + * 手动注册一个数据源(仅注册到路由,不持久化到数据库表) + */ + @PostMapping("/register") + @Operation(summary = "手动注册一个数据源(仅运行时,不持久化到表)") + public ResponseResult registerDataSource(@RequestBody GenDatasourceConf conf) { + if (conf.getDsKey() == null || conf.getDsKey().isBlank()) { + return ResponseResult.error("dsKey 不能为空"); + } + if (conf.getUrl() == null || conf.getUrl().isBlank()) { + return ResponseResult.error("url 不能为空"); + } + if (dynamicDataSourceRegistry.registerRuntime(conf)) { + return ResponseResult.success("数据源手动注册成功: " + conf.getDsKey()); + } else { + return ResponseResult.error("数据源注册失败: " + conf.getDsKey()); + } + } + + /** + * 注销一个数据源 + */ + @DeleteMapping("/unregister/{key}") + @Operation(summary = "注销一个数据源") + public ResponseResult unregisterDataSource(@PathVariable("key") String key) { + if (DataSourceKeys.DM_MASTER.equals(key) || DataSourceKeys.DM_SLAVE.equals(key)) { + return ResponseResult.error("系统内置数据源不允许注销"); + } + boolean removed = dynamicDataSource.unregister(key); + if (removed) { + return ResponseResult.success("数据源已注销: " + key); + } else { + return ResponseResult.error("未找到数据源: " + key); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceRegistry.java b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceRegistry.java new file mode 100644 index 00000000..50559e7c --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/DynamicDataSourceRegistry.java @@ -0,0 +1,300 @@ +package com.yfd.platform.datasource; + +import com.alibaba.druid.pool.DruidDataSource; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 动态数据源注册器,从数据库表 {@code GEN_DATASOURCE_CONF} 读取配置, + * 构建 Druid 连接池并注册到 {@link DynamicDataSource},支持运行时热刷新。 + *

+ * 使用方式: + *

    + *
  • 启动时 {@link #init()} 自动注册所有已启用的数据源
  • + *
  • 运行时调用 {@link #refresh()} 重新读取表并热刷新(零中断)
  • + *
+ */ +@Component +public class DynamicDataSourceRegistry { + + private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceRegistry.class); + + /** 业务主库(dm-master)的 DataSource,用于查询 GEN_DATASOURCE_CONF 表 */ + @Resource + @Qualifier("dmMasterDataSource") + private DataSource dmMasterDataSource; + + @Resource + private DynamicDataSource dynamicDataSource; + + private final Object refreshLock = new Object(); + + /** + * 启动时初始化:从数据库表加载所有已启用的数据源并注册 + */ + @PostConstruct + public void init() { + try { + List configs = loadConfigs(); + if (configs.isEmpty()) { + log.info("GEN_DATASOURCE_CONF 表中无已启用的动态数据源配置,跳过注册"); + return; + } + int success = 0; + int fail = 0; + for (GenDatasourceConf conf : configs) { + if (tryRegister(conf)) { + success++; + } else { + fail++; + } + } + log.info("动态数据源初始化完成: 成功={}, 失败={}", success, fail); + } catch (Exception e) { + log.error("动态数据源初始化失败(业务主库未就绪,稍后手动刷新即可): {}", e.getMessage()); + } + } + + /** + * 运行时热刷新:重新读取数据库表,自动注册新增/变更的数据源,注销已删除/禁用的数据源 + */ + public void refresh() { + synchronized (refreshLock) { + log.info("开始热刷新动态数据源..."); + List configs = loadConfigs(); + + // 1. 收集表中期望的 key + java.util.Set expectedKeys = new java.util.HashSet<>(); + for (GenDatasourceConf conf : configs) { + if (conf.getDsKey() != null) { + expectedKeys.add(conf.getDsKey()); + } + } + + // 2. 注册或替换表中数据源 + int success = 0; + int fail = 0; + for (GenDatasourceConf conf : configs) { + if (tryRegister(conf)) { + success++; + } else { + fail++; + } + } + + // 3. 注销表中已不存在或已禁用的数据源(排除内置的 dm-master/dm-slave) + java.util.Set registeredKeys = dynamicDataSource.getRegisteredKeys(); + for (Object keyObj : registeredKeys) { + String key = keyObj.toString(); + // 跳过系统内置数据源 + if (DataSourceKeys.DM_MASTER.equals(key) || DataSourceKeys.DM_SLAVE.equals(key)) { + continue; + } + if (!expectedKeys.contains(key)) { + dynamicDataSource.unregister(key); + log.info("热刷新: 已注销数据源 key={}", key); + } + } + + log.info("动态数据源热刷新完成: 成功={}, 失败={}, 注销={}", success, fail, + registeredKeys.size() - expectedKeys.size()); + } + } + + /** + * 从数据库表查询所有已启用的数据源配置 + */ + private List loadConfigs() { + JdbcTemplate jt = new JdbcTemplate(dmMasterDataSource); + String sql = "SELECT * FROM GEN_DATASOURCE_CONF WHERE NVL(IS_DELETED, 0) = 0 AND NVL(ENABLED, 1) = 1 ORDER BY ORDER_INDEX"; + List> rows = jt.queryForList(sql); + List list = new ArrayList<>(rows.size()); + for (Map row : rows) { + GenDatasourceConf conf = mapRow(row); + if (conf.getDsKey() == null || conf.getDsKey().isBlank()) { + log.warn("GEN_DATASOURCE_CONF 记录 ID={} 缺少 DS_KEY,跳过", row.get("ID")); + continue; + } + list.add(conf); + } + return list; + } + + /** + * 尝试注册单个数据源 + * + * @return true-注册成功;false-注册失败 + */ + private boolean tryRegister(GenDatasourceConf conf) { + String key = conf.getDsKey().trim(); + try { + DruidDataSource ds = buildDataSource(conf); + // 使用 registerReplace 热替换,运行时零中断 + dynamicDataSource.registerReplace(key, ds); + log.info("数据源注册成功: key={}, url={}", key, conf.getUrl()); + return true; + } catch (Exception e) { + log.error("数据源注册失败: key={}, url={}, 原因={}", key, conf.getUrl(), e.getMessage(), e); + return false; + } + } + + /** + * 运行时手动注册(API 调用),不持久化到数据库表 + * + * @return true-注册成功 + */ + public boolean registerRuntime(GenDatasourceConf conf) { + return tryRegister(conf); + } + + /** + * 根据配置构建 Druid 数据源 + */ + private DruidDataSource buildDataSource(GenDatasourceConf conf) { + DruidDataSource ds = new DruidDataSource(); + + // 驱动:先按 URL 自动识别,再按配置 + String driver = conf.getDriver(); + if (driver == null || driver.isBlank()) { + driver = resolveDriverFromUrl(conf.getUrl()); + } + ds.setDriverClassName(driver); + ds.setUrl(conf.getUrl()); + ds.setUsername(conf.getUsername()); + ds.setPassword(conf.getPassword()); + + // 连接池大小 + int maxActive = (conf.getMaxActive() != null && conf.getMaxActive() > 0) + ? conf.getMaxActive() : 20; + ds.setInitialSize(Math.min(5, maxActive)); + ds.setMinIdle(Math.min(5, maxActive)); + ds.setMaxActive(maxActive); + + // 超时 + long connTimeout = (conf.getConnTimeout() != null && conf.getConnTimeout() > 0) + ? conf.getConnTimeout() : 30000L; + ds.setMaxWait(connTimeout); + + // 通用连接池配置 + ds.setValidationQuery("SELECT 1"); + ds.setValidationQueryTimeout(3); + ds.setTestWhileIdle(true); + ds.setTestOnBorrow(false); + ds.setTestOnReturn(false); + ds.setKeepAlive(true); + ds.setBreakAfterAcquireFailure(true); + ds.setConnectionErrorRetryAttempts(0); + ds.setTimeBetweenConnectErrorMillis(30000); + ds.setTimeBetweenEvictionRunsMillis(60000); + ds.setMinEvictableIdleTimeMillis(180000); + ds.setMaxEvictableIdleTimeMillis(300000); + ds.setRemoveAbandoned(true); + ds.setRemoveAbandonedTimeout(1800); + ds.setLogAbandoned(true); + + try { + ds.init(); + } catch (SQLException e) { + // 关闭半初始化的连接池再抛出 + ds.close(); + throw new IllegalStateException("初始化数据源失败: " + conf.getUrl(), e); + } + + return ds; + } + + /** + * 根据 URL 自动识别 JDBC 驱动类名 + */ + private String resolveDriverFromUrl(String url) { + if (url == null) { + throw new IllegalArgumentException("URL 为空,无法自动识别驱动类型"); + } + String lower = url.toLowerCase(); + if (lower.startsWith("jdbc:dm")) { + return "dm.jdbc.driver.DmDriver"; + } + if (lower.startsWith("jdbc:oracle")) { + return "oracle.jdbc.OracleDriver"; + } + if (lower.startsWith("jdbc:mysql")) { + return "com.mysql.cj.jdbc.Driver"; + } + if (lower.startsWith("jdbc:postgresql")) { + return "org.postgresql.Driver"; + } + if (lower.startsWith("jdbc:sqlserver") || lower.startsWith("jdbc:microsoft")) { + return "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + } + throw new IllegalArgumentException("无法根据 URL 自动识别驱动类型: " + url); + } + + /** + * 将数据库行映射为实体 + */ + private GenDatasourceConf mapRow(Map row) { + GenDatasourceConf conf = new GenDatasourceConf(); + conf.setId(strVal(row.get("ID"))); + conf.setDriver(strVal(row.get("DRIVER"))); + conf.setUrl(strVal(row.get("URL"))); + conf.setUsername(strVal(row.get("USERNAME"))); + conf.setPassword(strVal(row.get("PASSWORD"))); + conf.setInternal(intVal(row.get("INTERNAL"))); + conf.setOrderIndex(intVal(row.get("ORDER_INDEX"))); + conf.setFilterContent(strVal(row.get("FILTER_CONTENT"))); + conf.setRecordUser(strVal(row.get("RECORD_USER"))); + conf.setRecordTime(dateVal(row.get("RECORD_TIME"))); + conf.setModifyUser(strVal(row.get("MODIFY_USER"))); + conf.setModifyTime(dateVal(row.get("MODIFY_TIME"))); + conf.setIsDeleted(intVal(row.get("IS_DELETED"))); + conf.setDeleteUser(strVal(row.get("DELETE_USER"))); + conf.setDeleteTime(dateVal(row.get("DELETE_TIME"))); + conf.setCreateDate(dateVal(row.get("CREATE_DATE"))); + conf.setUpdateDate(dateVal(row.get("UPDATE_DATE"))); + conf.setDelFlag(strVal(row.get("DEL_FLAG"))); + // 扩展字段 + conf.setDsKey(strVal(row.get("DS_KEY"))); + conf.setEnabled(intVal(row.get("ENABLED"))); + conf.setDsName(strVal(row.get("DS_NAME"))); + conf.setDsDesc(strVal(row.get("DS_DESC"))); + conf.setConnTimeout(longVal(row.get("CONN_TIMEOUT"))); + conf.setMaxActive(intVal(row.get("MAX_ACTIVE"))); + return conf; + } + + private String strVal(Object obj) { + return obj == null ? null : obj.toString(); + } + + private Integer intVal(Object obj) { + if (obj == null) return null; + if (obj instanceof Number) return ((Number) obj).intValue(); + try { return Integer.parseInt(obj.toString()); } catch (Exception e) { return null; } + } + + private Long longVal(Object obj) { + if (obj == null) return null; + if (obj instanceof Number) return ((Number) obj).longValue(); + try { return Long.parseLong(obj.toString()); } catch (Exception e) { return null; } + } + + private java.util.Date dateVal(Object obj) { + if (obj == null) return null; + if (obj instanceof java.util.Date) return (java.util.Date) obj; + if (obj instanceof java.sql.Date) return new java.util.Date(((java.sql.Date) obj).getTime()); + if (obj instanceof java.sql.Timestamp) return new java.util.Date(((java.sql.Timestamp) obj).getTime()); + return null; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/yfd/platform/datasource/GenDatasourceConf.java b/backend/src/main/java/com/yfd/platform/datasource/GenDatasourceConf.java new file mode 100644 index 00000000..80eec524 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/datasource/GenDatasourceConf.java @@ -0,0 +1,125 @@ +package com.yfd.platform.datasource; + +import java.util.Date; + +/** + * GEN_DATASOURCE_CONF 数据源配置表实体,用于动态数据源注册 + *

+ * 字段与数据库表一一对应,使用 {@link com.yfd.platform.common.MicroservicDynamicSQLMapper#getAllList} + * 动态 SQL 查询 / 手动映射,不依赖 MyBatis-Plus 实体扫描。 + */ +public class GenDatasourceConf { + + private String id; + private String driver; + private String url; + private String username; + private String password; + private Integer internal; + private Integer orderIndex; + private String filterContent; + private String recordUser; + private Date recordTime; + private String modifyUser; + private Date modifyTime; + private Integer isDeleted; + private String deleteUser; + private Date deleteTime; + private Date createDate; + private Date updateDate; + private String delFlag; + + // ========== 扩展字段 ========== + + /** 数据源路由 key,全局唯一 */ + private String dsKey; + + /** 是否启用:0=禁用 1=启用 */ + private Integer enabled; + + /** 数据源显示名称 */ + private String dsName; + + /** 数据源描述 */ + private String dsDesc; + + /** 连接超时(毫秒),默认30000 */ + private Long connTimeout; + + /** 最大活跃连接数,默认20 */ + private Integer maxActive; + + // ==================== Getters & Setters ==================== + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getDriver() { return driver; } + public void setDriver(String driver) { this.driver = driver; } + + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + public Integer getInternal() { return internal; } + public void setInternal(Integer internal) { this.internal = internal; } + + public Integer getOrderIndex() { return orderIndex; } + public void setOrderIndex(Integer orderIndex) { this.orderIndex = orderIndex; } + + public String getFilterContent() { return filterContent; } + public void setFilterContent(String filterContent) { this.filterContent = filterContent; } + + public String getRecordUser() { return recordUser; } + public void setRecordUser(String recordUser) { this.recordUser = recordUser; } + + public Date getRecordTime() { return recordTime; } + public void setRecordTime(Date recordTime) { this.recordTime = recordTime; } + + public String getModifyUser() { return modifyUser; } + public void setModifyUser(String modifyUser) { this.modifyUser = modifyUser; } + + public Date getModifyTime() { return modifyTime; } + public void setModifyTime(Date modifyTime) { this.modifyTime = modifyTime; } + + public Integer getIsDeleted() { return isDeleted; } + public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; } + + public String getDeleteUser() { return deleteUser; } + public void setDeleteUser(String deleteUser) { this.deleteUser = deleteUser; } + + public Date getDeleteTime() { return deleteTime; } + public void setDeleteTime(Date deleteTime) { this.deleteTime = deleteTime; } + + public Date getCreateDate() { return createDate; } + public void setCreateDate(Date createDate) { this.createDate = createDate; } + + public Date getUpdateDate() { return updateDate; } + public void setUpdateDate(Date updateDate) { this.updateDate = updateDate; } + + public String getDelFlag() { return delFlag; } + public void setDelFlag(String delFlag) { this.delFlag = delFlag; } + + public String getDsKey() { return dsKey; } + public void setDsKey(String dsKey) { this.dsKey = dsKey; } + + public Integer getEnabled() { return enabled; } + public void setEnabled(Integer enabled) { this.enabled = enabled; } + + public String getDsName() { return dsName; } + public void setDsName(String dsName) { this.dsName = dsName; } + + public String getDsDesc() { return dsDesc; } + public void setDsDesc(String dsDesc) { this.dsDesc = dsDesc; } + + public Long getConnTimeout() { return connTimeout; } + public void setConnTimeout(Long connTimeout) { this.connTimeout = connTimeout; } + + public Integer getMaxActive() { return maxActive; } + public void setMaxActive(Integer maxActive) { this.maxActive = maxActive; } +} \ No newline at end of file diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index c9f2713d..da5b1bb7 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -69,70 +69,6 @@ spring: pool-prepared-statements: true max-open-prepared-statements: 100 max-pool-prepared-statement-per-connection-size: 100 - # ==================== Oracle 数据源 ==================== - # enabled=false 时不创建连接池,也不注册到路由数据源(按需开启) - oracle-master: - enabled: false - driverClassName: oracle.jdbc.OracleDriver - url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}" - username: "${DB_ORACLE_MASTER_USERNAME:SDLY_QX}" - password: "${DB_ORACLE_MASTER_PASSWORD:jNHnqv3hH7}" - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 30000 - async-init: true - keep-alive-between-time-millis: 120000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 180000 - max-evictable-idle-time-millis: 300000 - phy-timeout-millis: 25200000 - validation-query: SELECT 1 FROM DUAL - validation-query-timeout: 3 - test-while-idle: true - test-on-borrow: false - test-on-return: false - keep-alive: true - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - break-after-acquire-failure: true - time-between-connect-error-millis: 30000 - pool-prepared-statements: true - max-open-prepared-statements: 100 - max-pool-prepared-statement-per-connection-size: 100 - connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000 - oracle-slave: - enabled: false - driverClassName: oracle.jdbc.OracleDriver - url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}" - username: "${DB_ORACLE_SLAVE_USERNAME:SDLY_QX}" - password: "${DB_ORACLE_SLAVE_PASSWORD:jNHnqv3hH7}" - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 30000 - async-init: true - keep-alive-between-time-millis: 120000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 180000 - max-evictable-idle-time-millis: 300000 - phy-timeout-millis: 25200000 - validation-query: SELECT 1 FROM DUAL - validation-query-timeout: 3 - test-while-idle: true - test-on-borrow: false - test-on-return: false - keep-alive: true - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - break-after-acquire-failure: true - time-between-connect-error-millis: 30000 - pool-prepared-statements: true - max-open-prepared-statements: 100 - max-pool-prepared-statement-per-connection-size: 100 - connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000 filter: stat: enabled: true diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index 5d3ae588..4427106e 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -69,70 +69,6 @@ spring: pool-prepared-statements: true max-open-prepared-statements: 100 max-pool-prepared-statement-per-connection-size: 100 - # ==================== Oracle 数据源 ==================== - # enabled=false 时不创建连接池,也不注册到路由数据源(按需开启) - oracle-master: - enabled: false - driverClassName: oracle.jdbc.OracleDriver - url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}" - username: "${DB_ORACLE_MASTER_USERNAME:SDLY_QX}" - password: "${DB_ORACLE_MASTER_PASSWORD:jNHnqv3hH7}" - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 30000 - async-init: true - keep-alive-between-time-millis: 120000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 180000 - max-evictable-idle-time-millis: 300000 - phy-timeout-millis: 25200000 - validation-query: SELECT 1 FROM DUAL - validation-query-timeout: 3 - test-while-idle: true - test-on-borrow: false - test-on-return: false - keep-alive: true - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - break-after-acquire-failure: true - time-between-connect-error-millis: 30000 - pool-prepared-statements: true - max-open-prepared-statements: 100 - max-pool-prepared-statement-per-connection-size: 100 - connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000 - oracle-slave: - enabled: false - driverClassName: oracle.jdbc.OracleDriver - url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}" - username: "${DB_ORACLE_SLAVE_USERNAME:SDLY_QX}" - password: "${DB_ORACLE_SLAVE_PASSWORD:jNHnqv3hH7}" - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 30000 - async-init: true - keep-alive-between-time-millis: 120000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 180000 - max-evictable-idle-time-millis: 300000 - phy-timeout-millis: 25200000 - validation-query: SELECT 1 FROM DUAL - validation-query-timeout: 3 - test-while-idle: true - test-on-borrow: false - test-on-return: false - keep-alive: true - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - break-after-acquire-failure: true - time-between-connect-error-millis: 30000 - pool-prepared-statements: true - max-open-prepared-statements: 100 - max-pool-prepared-statement-per-connection-size: 100 - connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000 filter: stat: enabled: true