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