cl
2026-04-17 86fd4ec5fd97081f212e4c35523ee3d44f233fb6
配置多数据源
7个文件已添加
8个文件已修改
358 ■■■■ 已修改文件
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/OpenAsrsServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/config/CusItemSyncDataSourceConfig.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/config/PrimaryDataSourceConfig.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/DataSourceContextHolder.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/DataSourceNames.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/HttpAuditDataSourceAspect.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/RoutingDataSource.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/UseDataSource.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/UseDataSourceAspect.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/CusItemSyncViewQueryService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HttpAuditLogController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HttpAuditRuleController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HttpAuditLogServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-prod.yml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/OpenAsrsServiceImpl.java
@@ -15,6 +15,7 @@
import com.vincent.rsf.server.manager.service.LocService;
import com.vincent.rsf.server.manager.service.TaskItemService;
import com.vincent.rsf.server.manager.service.TaskService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
rsf-server/src/main/java/com/vincent/rsf/server/common/config/CusItemSyncDataSourceConfig.java
@@ -3,7 +3,7 @@
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -11,27 +11,16 @@
import javax.sql.DataSource;
/**
 * cus_item_sync_view 专用副库连接;未配置 cus-item-sync.datasource.url 时不创建 Bean,查询回退主库 Mapper
 * cus_item_sync_view 专用副库连接
 */
@Configuration
@ConditionalOnProperty(prefix = "cus-item-sync.datasource", name = "url")
@EnableConfigurationProperties(CusItemSyncDataSourceProperties.class)
@ConditionalOnProperty(prefix = "spring.datasource.cus-item-sync", name = "url")
public class CusItemSyncDataSourceConfig {
    @Bean(name = "cusItemSyncDataSource")
    public DataSource cusItemSyncDataSource(CusItemSyncDataSourceProperties p) {
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(p.getUrl());
        ds.setUsername(p.getUsername());
        ds.setPassword(p.getPassword());
        ds.setDriverClassName(p.getDriverClassName());
        ds.setInitialSize(2);
        ds.setMinIdle(2);
        ds.setMaxActive(10);
        ds.setMaxWait(30000);
        ds.setTestWhileIdle(true);
        ds.setValidationQuery("SELECT 'x'");
        return ds;
    @ConfigurationProperties(prefix = "spring.datasource.cus-item-sync")
    public DataSource cusItemSyncDataSource() {
        return new DruidDataSource();
    }
    @Bean(name = "cusItemSyncJdbcTemplate")
rsf-server/src/main/java/com/vincent/rsf/server/common/config/PrimaryDataSourceConfig.java
New file
@@ -0,0 +1,59 @@
package com.vincent.rsf.server.common.config;
import com.vincent.rsf.server.common.datasource.DataSourceNames;
import com.vincent.rsf.server.common.datasource.RoutingDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 主数据源配置
 */
@Configuration
public class PrimaryDataSourceConfig {
    @Bean(name = "primaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSourceProperties primaryDataSourceProperties() {
        return new DataSourceProperties();
    }
    @Bean(name = "primaryDataSource")
    public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
    @Bean(name = "dataSource")
    @Primary
    public DataSource dataSource(
            @Qualifier("primaryDataSource") DataSource primaryDataSource,
            @Qualifier("cusItemSyncDataSource") ObjectProvider<DataSource> cusItemSyncDataSourceProvider) {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceNames.PRIMARY, primaryDataSource);
        DataSource cusItemSyncDataSource = cusItemSyncDataSourceProvider.getIfAvailable();
        if (cusItemSyncDataSource != null) {
            map.put(DataSourceNames.CUS_ITEM_SYNC, cusItemSyncDataSource);
        }
        routingDataSource.setDefaultTargetDataSource(primaryDataSource);
        routingDataSource.setTargetDataSources(map);
        routingDataSource.afterPropertiesSet();
        return routingDataSource;
    }
    @Bean(name = "jdbcTemplate")
    @Primary
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/DataSourceContextHolder.java
New file
@@ -0,0 +1,35 @@
package com.vincent.rsf.server.common.datasource;
import java.util.ArrayDeque;
import java.util.Deque;
/**
 * 数据源上下文
 */
public final class DataSourceContextHolder {
    private static final ThreadLocal<Deque<String>> CONTEXT = ThreadLocal.withInitial(ArrayDeque::new);
    private DataSourceContextHolder() {
    }
    public static void push(String dataSource) {
        CONTEXT.get().push(dataSource);
    }
    public static String peek() {
        Deque<String> deque = CONTEXT.get();
        return deque.isEmpty() ? null : deque.peek();
    }
    public static void poll() {
        Deque<String> deque = CONTEXT.get();
        if (!deque.isEmpty()) {
            deque.pop();
        }
        if (deque.isEmpty()) {
            CONTEXT.remove();
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/DataSourceNames.java
New file
@@ -0,0 +1,11 @@
package com.vincent.rsf.server.common.datasource;
/**
 * 数据源名称
 */
public interface DataSourceNames {
    String PRIMARY = "primary";
    String CUS_ITEM_SYNC = "cus-item-sync";
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/HttpAuditDataSourceAspect.java
New file
@@ -0,0 +1,40 @@
package com.vincent.rsf.server.common.datasource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * http-audit 数据源切换
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpAuditDataSourceAspect {
    @Value("${http-audit.datasource:primary}")
    private String dataSource;
    @Around("execution(* com.vincent.rsf.httpaudit..*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String selected = resolveDataSource();
        DataSourceContextHolder.push(selected);
        try {
            return joinPoint.proceed();
        } finally {
            DataSourceContextHolder.poll();
        }
    }
    private String resolveDataSource() {
        if ("cus-item-sync".equalsIgnoreCase(dataSource)) {
            return DataSourceNames.CUS_ITEM_SYNC;
        }
        return DataSourceNames.PRIMARY;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/RoutingDataSource.java
New file
@@ -0,0 +1,16 @@
package com.vincent.rsf.server.common.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 动态路由数据源
 */
public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DataSourceContextHolder.peek();
        return dataSource == null ? DataSourceNames.PRIMARY : dataSource;
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/UseDataSource.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.server.common.datasource;
import java.lang.annotation.*;
/**
 * 指定数据源
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
    String value() default DataSourceNames.PRIMARY;
}
rsf-server/src/main/java/com/vincent/rsf/server/common/datasource/UseDataSourceAspect.java
New file
@@ -0,0 +1,46 @@
package com.vincent.rsf.server.common.datasource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 注解数据源切换
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class UseDataSourceAspect {
    @Around("@annotation(com.vincent.rsf.server.common.datasource.UseDataSource) || @within(com.vincent.rsf.server.common.datasource.UseDataSource)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        UseDataSource useDataSource = resolveAnnotation(joinPoint);
        if (useDataSource == null) {
            return joinPoint.proceed();
        }
        DataSourceContextHolder.push(useDataSource.value());
        try {
            return joinPoint.proceed();
        } finally {
            DataSourceContextHolder.poll();
        }
    }
    private UseDataSource resolveAnnotation(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        UseDataSource annotation = method.getAnnotation(UseDataSource.class);
        if (annotation != null) {
            return annotation;
        }
        Class<?> targetClass = joinPoint.getTarget().getClass();
        return targetClass.getAnnotation(UseDataSource.class);
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/CusItemSyncViewQueryService.java
@@ -1,12 +1,16 @@
package com.vincent.rsf.server.manager.service;
import com.vincent.rsf.server.common.datasource.DataSourceNames;
import com.vincent.rsf.server.common.datasource.UseDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -20,27 +24,32 @@
@Slf4j
public class CusItemSyncViewQueryService {
    @Autowired(required = false)
    @Qualifier("cusItemSyncJdbcTemplate")
    private JdbcTemplate cusItemSyncJdbcTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    @Qualifier("cusItemSyncDataSource")
    private ObjectProvider<DataSource> cusItemSyncDataSourceProvider;
    /** 当前视图查询实际使用的数据源说明 */
    public String effectiveDataSourceLabel() {
        return cusItemSyncJdbcTemplate != null ? "cus-item-sync" : "none";
        return cusItemSyncDataSourceProvider.getIfAvailable() != null ? "cus-item-sync" : "none";
    }
    /**
     * 取视图前若干行
     */
    @UseDataSource(DataSourceNames.CUS_ITEM_SYNC)
    public List<Map<String, Object>> probeSample(int limit) {
        if (cusItemSyncJdbcTemplate == null) {
        if (cusItemSyncDataSourceProvider.getIfAvailable() == null) {
            return Collections.emptyList();
        }
        int n = Math.min(50, Math.max(1, limit));
        return cusItemSyncJdbcTemplate.queryForList(
        return jdbcTemplate.queryForList(
                "SELECT item_no, item_spec, unit_no FROM cus_item_sync_view LIMIT " + n);
    }
    @UseDataSource(DataSourceNames.CUS_ITEM_SYNC)
    public List<Map<String, Object>> listByItemNos(Collection<String> itemNos) {
        if (itemNos == null || itemNos.isEmpty()) {
            return Collections.emptyList();
@@ -55,12 +64,12 @@
        if (codes.isEmpty()) {
            return Collections.emptyList();
        }
        if (cusItemSyncJdbcTemplate == null) {
        if (cusItemSyncDataSourceProvider.getIfAvailable() == null) {
            log.warn("cus-item-sync 数据源未配置,跳过视图查询");
            return Collections.emptyList();
        }
        String placeholders = String.join(",", Collections.nCopies(codes.size(), "?"));
        String sql = "SELECT item_no, item_spec, unit_no FROM cus_item_sync_view WHERE item_no IN (" + placeholders + ")";
        return cusItemSyncJdbcTemplate.queryForList(sql, codes.toArray());
        return jdbcTemplate.queryForList(sql, codes.toArray());
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HttpAuditLogController.java
@@ -6,6 +6,7 @@
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.system.service.HttpAuditLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -13,6 +14,7 @@
import java.util.Map;
@RestController
@ConditionalOnProperty(prefix = "http-audit", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HttpAuditLogController extends BaseController {
    @Autowired
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/HttpAuditRuleController.java
@@ -8,6 +8,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.PageParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -20,6 +21,7 @@
import java.util.Set;
@RestController
@ConditionalOnProperty(prefix = "http-audit", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HttpAuditRuleController extends BaseController {
    private static final Set<String> RULE_TYPES = new HashSet<>(Arrays.asList(
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/HttpAuditLogServiceImpl.java
@@ -4,8 +4,10 @@
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.mapper.HttpAuditLogMapper;
import com.vincent.rsf.server.system.service.HttpAuditLogService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnProperty(prefix = "http-audit", name = "enabled", havingValue = "true", matchIfMissing = true)
public class HttpAuditLogServiceImpl extends ServiceImpl<HttpAuditLogMapper, HttpAuditLog> implements HttpAuditLogService {
}
rsf-server/src/main/resources/application-dev.yml
@@ -21,14 +21,14 @@
    password: 12345
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 30000
      initial-size: 3
      min-idle: 8
      max-active: 40
      max-wait: 10000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800
@@ -43,6 +43,27 @@
        login-username: admin
        login-password: admin
        enabled: true
    # 多数据源:cus_item_sync_view 专用库
    cus-item-sync:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 12345
      url: jdbc:mysql://127.0.0.1:3306/ilc_wms_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
      initial-size: 1
      min-idle: 2
      max-active: 12
      max-wait: 10000
#    erp:
#      type: com.alibaba.druid.pool.DruidDataSource
#      driver-class-name: com.mysql.cj.jdbc.Driver
#      username: root
#      password: 12345
#      url: jdbc:mysql://127.0.0.1:3306/rsf_jdxaj?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#      initial-size: 1
#      min-idle: 2
#      max-active: 12
#      max-wait: 10000
  servlet:
    multipart:
      maxFileSize: 100MB
@@ -71,13 +92,6 @@
  timeout: 5000
  index: 15
# 仅 cus_item_sync_view;url 不配置时仍查主库。库名按 108 实例实际库名修改
cus-item-sync:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/rsf_jdxaj?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 12345
    driver-class-name: com.mysql.cj.jdbc.Driver
#平台接口信息配置(如:ERP, QMS, WCS等)
platform:
@@ -126,7 +140,10 @@
# whitelist-only=true:仅 sys_http_audit_rule 命中规则才写审计;无规则时不落库。false:排除路径外全量记录。
# rule-cache-refresh-ms:规则表缓存刷新间隔(毫秒)
http-audit:
  enabled: true
  # enabled: true
  enabled: false
  # 审计数据源:primary / cus-item-sync
  datasource: primary
  whitelist-only: true
  # false:/httpAuditLog、/httpAuditRule 也会被 Filter 记录(调试用;生产建议 true)
  exclude-audit-self-paths: false
rsf-server/src/main/resources/application-prod.yml
@@ -21,14 +21,14 @@
    password: xltys1995
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 30000
      initial-size: 3
      min-idle: 8
      max-active: 40
      max-wait: 10000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800
@@ -43,6 +43,21 @@
        login-username: admin
        login-password: admin
        enabled: true
    # 多数据源:cus_item_sync_view 专用库
    cus-item-sync:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
#      url: jdbc:mysql://127.0.0.1:3306/rsf_jdxaj?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#      username: root
#      password: xltys1995
#      url: jdbc:mysql://192.168.10.108:3306/ilc_wms_prod?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
      url: jdbc:mysql://192.168.10.108:3306/ilc_wms_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: "Gsyywm@2020.com"
      initial-size: 1
      min-idle: 2
      max-active: 12
      max-wait: 10000
  servlet:
    multipart:
      maxFileSize: 100MB
@@ -72,17 +87,6 @@
  database: 3
  index: 3
# 仅 cus_item_sync_view;url 不配置时仍查主库。库名与账号按生产环境修改
cus-item-sync:
  datasource:
#    url: jdbc:mysql://127.0.0.1:3306/rsf_jdxaj?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#    username: root
#    password: xltys1995
#    url: jdbc:mysql://192.168.10.108:3306/ilc_wms_prod?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://192.168.10.108:3306/ilc_wms_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: "Gsyywm@2020.com"
    driver-class-name: com.mysql.cj.jdbc.Driver
#平台接口信息配置(如:ERP, QMS, WCS等)
platform:
@@ -132,6 +136,8 @@
# rule-cache-refresh-ms:规则表缓存刷新间隔(毫秒)
http-audit:
  enabled: true
  # 审计数据源:primary / cus-item-sync
  datasource: primary
  whitelist-only: true
  # false:/httpAuditLog、/httpAuditRule 也会被 Filter 记录(调试用;生产建议 true)
  exclude-audit-self-paths: false