diff --git a/docker-compose.yml b/docker-compose.yml
index 794354e..3880a0b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -185,7 +185,10 @@ services:
REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m
# 租户 ID - 共享实例(空值,所有租户可用)
- TENANT_ID: ""
+ # TENANT_ID: ""
+
+ # 租户 ID - 多租户专属实例(逗号分隔,服务多个租户)
+ TENANT_ID: "VIP_001,VIP_002,VIP_003"
ports:
- "8100:8100"
depends_on:
@@ -227,8 +230,8 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m
- # 租户 ID - VIP_001 专属实例
- TENANT_ID: "VIP_001"
+ # 租户 ID - 单租户专属实例
+ TENANT_ID: "VIP_004"
ports:
- "8101:8101"
depends_on:
diff --git a/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java b/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java
index 9f530a3..646aabb 100644
--- a/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java
+++ b/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java
@@ -14,6 +14,8 @@ import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBal
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
@@ -31,14 +33,15 @@ import java.util.stream.Collectors;
*
*
路由规则:
*
- * 1. 查找 metadata.tenant-id == 请求.tenantId 的实例 → VIP 专属实例
+ * 1. 查找 metadata.tenant-id 包含请求 tenantId 的实例 → 专属实例
* 2. 找不到 → 回退到共享实例(metadata.tenant-id 为空)
*
*
* 实例配置:
*
- * 共享实例: metadata.tenant-id = "" (空)
- * VIP 实例: metadata.tenant-id = "VIP_001"
+ * 共享实例: metadata.tenant-id = "" (空)
+ * 单租户专属: metadata.tenant-id = "VIP_001"
+ * 多租户专属: metadata.tenant-id = "VIP_001,VIP_002,VIP_003"
*
*/
public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@@ -130,9 +133,11 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
*
* 路由策略:
*
- * - 优先选择租户专属实例(metadata.tenant-id 匹配)
+ * - 优先选择租户专属实例(metadata.tenant-id 包含请求的 tenantId)
* - 回退到共享实例(metadata.tenant-id 为空或不存在)
*
+ *
+ * tenant-id 支持逗号分隔的多个租户 ID
*/
List filterByTenantId(List instances, String tenantId) {
if (instances == null || instances.isEmpty()) {
@@ -151,14 +156,13 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
if (tenantId != null && !tenantId.isEmpty()) {
List tenantInstances = instances.stream()
.filter(inst -> {
- Map metadata = inst.getMetadata();
- if (metadata == null) return false;
- String instanceTenantId = metadata.get("tenant-id");
- boolean match = tenantId.equals(instanceTenantId);
- if (match) {
- logger.debug("[TenantLB] 匹配租户专属实例:{}:{}", inst.getHost(), inst.getPort());
+ List allowedTenants = parseTenantIds(inst);
+ if (allowedTenants.contains(tenantId)) {
+ logger.debug("[TenantLB] 匹配租户专属实例:{}:{} (允许租户: {})",
+ inst.getHost(), inst.getPort(), allowedTenants);
+ return true;
}
- return match;
+ return false;
})
.collect(Collectors.toList());
@@ -169,21 +173,18 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
}
// 检查是否启用回退到共享实例
- boolean fallbackEnabled = true;
- if (routingProperties != null) {
- fallbackEnabled = routingProperties.isFallbackToShared();
- }
+ boolean fallbackEnabled = routingProperties == null || routingProperties.isFallbackToShared();
if (!fallbackEnabled) {
logger.warn("[TenantLB] 服务 {} 未启用共享实例回退,返回空列表", serviceId);
- return List.of();
+ return Collections.emptyList();
}
// 回退到共享实例(metadata.tenant-id 为空或不存在)
List sharedInstances = instances.stream()
.filter(inst -> {
- Map metadata = inst.getMetadata();
- return metadata == null || metadata.get("tenant-id") == null || metadata.get("tenant-id").isEmpty();
+ List allowedTenants = parseTenantIds(inst);
+ return allowedTenants.isEmpty(); // 空列表表示共享实例
})
.collect(Collectors.toList());
@@ -191,6 +192,30 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
return sharedInstances;
}
+ /**
+ * 解析实例元数据中的租户 ID 列表
+ *
+ * @param inst 服务实例
+ * @return 租户 ID 列表,空列表表示共享实例
+ */
+ private List parseTenantIds(ServiceInstance inst) {
+ Map metadata = inst.getMetadata();
+ if (metadata == null) {
+ return Collections.emptyList();
+ }
+
+ String tenantIdStr = metadata.get("tenant-id");
+ if (tenantIdStr == null || tenantIdStr.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // 支持逗号分隔的多个租户 ID
+ return Arrays.stream(tenantIdStr.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+ }
+
/**
* 从实例列表中选择一个(随机)
*/
diff --git a/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java b/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java
index 90cacc7..ab4f87d 100644
--- a/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java
+++ b/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java
@@ -22,70 +22,140 @@ class TenantAwareLoadBalancerTest {
}
@Test
- void testFilterByTenantId() {
- // 创建测试实例
+ void testSingleTenantInstance() {
+ // 单租户专属实例
List instances = Arrays.asList(
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
- createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_001")),
- createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "VIP_002")),
- createInstance("fund-sys", "192.168.1.4", 8103, Collections.emptyMap()) // 共享实例
+ createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_002")),
+ createInstance("fund-sys", "192.168.1.3", 8102, Collections.emptyMap()) // 共享实例
);
- // 测试 VIP_001 过滤
+ // VIP_001 匹配专属实例
List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
- assertEquals(2, vip1Instances.size());
- assertTrue(vip1Instances.stream().allMatch(i -> "VIP_001".equals(i.getMetadata().get("tenant-id"))));
+ assertEquals(1, vip1Instances.size());
+ assertEquals("VIP_001", vip1Instances.get(0).getMetadata().get("tenant-id"));
- // 测试 VIP_002 过滤
+ // VIP_002 匹配专属实例
List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
assertEquals(1, vip2Instances.size());
- // 测试未知租户(回退到共享实例)
+ // 未知租户回退到共享实例
List unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999");
assertEquals(1, unknownInstances.size());
assertFalse(unknownInstances.get(0).getMetadata().containsKey("tenant-id"));
}
@Test
- void testMixedMode() {
- // 混合模式:VIP 客户有专属实例,普通客户使用共享实例
+ void testMultiTenantInstance() {
+ // 多租户专属实例(一个实例服务多个租户)
List instances = Arrays.asList(
- // VIP_001 的专属实例
- createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
- createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_001")),
- // VIP_002 的专属实例
- createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "VIP_002")),
- // 共享实例(普通租户使用)
- createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap()),
- createInstance("fund-sys", "192.168.1.11", 8111, Collections.emptyMap())
+ // 实例1:服务 VIP_001, VIP_002, VIP_003
+ createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001,VIP_002,VIP_003")),
+ // 实例2:服务 VIP_004, VIP_005
+ createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_004,VIP_005")),
+ // 实例3:共享实例
+ createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap())
);
- // VIP_001 应该路由到专属实例
+ // VIP_001 匹配实例1
List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
- assertEquals(2, vip1Instances.size());
+ assertEquals(1, vip1Instances.size());
+ assertEquals("8100", String.valueOf(vip1Instances.get(0).getPort()));
- // VIP_002 应该路由到专属实例
+ // VIP_003 也匹配实例1
+ List vip3Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_003");
+ assertEquals(1, vip3Instances.size());
+ assertEquals("8100", String.valueOf(vip3Instances.get(0).getPort()));
+
+ // VIP_004 匹配实例2
+ List vip4Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_004");
+ assertEquals(1, vip4Instances.size());
+ assertEquals("8101", String.valueOf(vip4Instances.get(0).getPort()));
+
+ // VIP_005 匹配实例2
+ List vip5Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_005");
+ assertEquals(1, vip5Instances.size());
+ assertEquals("8101", String.valueOf(vip5Instances.get(0).getPort()));
+
+ // 未知租户回退到共享实例
+ List unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999");
+ assertEquals(1, unknownInstances.size());
+ assertEquals("8110", String.valueOf(unknownInstances.get(0).getPort()));
+ }
+
+ @Test
+ void testMixedMode() {
+ // 混合模式:单租户实例 + 多租户实例 + 共享实例
+ List instances = Arrays.asList(
+ // 单租户专属实例
+ createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
+ // 多租户专属实例(VIP_002, VIP_003)
+ createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_002,VIP_003")),
+ // 共享实例
+ createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap())
+ );
+
+ // VIP_001 走单租户实例
+ List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
+ assertEquals(1, vip1Instances.size());
+ assertEquals(8100, vip1Instances.get(0).getPort());
+
+ // VIP_002 走多租户实例
List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
assertEquals(1, vip2Instances.size());
+ assertEquals(8101, vip2Instances.get(0).getPort());
- // 普通租户应该路由到共享实例
+ // VIP_003 也走多租户实例
+ List vip3Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_003");
+ assertEquals(1, vip3Instances.size());
+ assertEquals(8101, vip3Instances.get(0).getPort());
+
+ // 普通租户走共享实例
List normalInstances = invokeFilterByTenantId(loadBalancer, instances, "NORMAL_001");
- assertEquals(2, normalInstances.size());
+ assertEquals(1, normalInstances.size());
+ assertEquals(8110, normalInstances.get(0).getPort());
+ }
+
+ @Test
+ void testSharedInstanceFallback() {
+ // 测试回退到共享实例
+ List instances = Arrays.asList(
+ createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
+ createInstance("fund-sys", "192.168.1.2", 8101, Collections.emptyMap()),
+ createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "")) // 空字符串也是共享
+ );
+
+ // 未知租户应该找到 2 个共享实例
+ List sharedInstances = invokeFilterByTenantId(loadBalancer, instances, "UNKNOWN");
+ assertEquals(2, sharedInstances.size());
}
@Test
void testNullTenantId() {
- // 测试空租户 ID(应该回退到共享实例)
List instances = Arrays.asList(
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
createInstance("fund-sys", "192.168.1.2", 8101, Collections.emptyMap())
);
+ // null 租户 ID 应该回退到共享实例
List result = invokeFilterByTenantId(loadBalancer, instances, null);
assertEquals(1, result.size());
assertFalse(result.get(0).getMetadata().containsKey("tenant-id"));
}
+ @Test
+ void testTenantIdWithSpaces() {
+ // 测试带空格的租户 ID 配置
+ List instances = Arrays.asList(
+ createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001, VIP_002 , VIP_003"))
+ );
+
+ // 应该能正确匹配(去除空格)
+ assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_001").size());
+ assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_002").size());
+ assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_003").size());
+ }
+
// 辅助方法:创建服务实例
private ServiceInstance createInstance(String serviceId, String host, int port, Map metadata) {
return new DefaultServiceInstance(
diff --git a/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class b/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class
index 09a93da..52fe28d 100644
Binary files a/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class and b/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class differ
diff --git a/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml b/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml
index d2d15a3..40bef56 100644
--- a/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml
+++ b/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml
@@ -1,5 +1,5 @@
-
+
@@ -13,7 +13,7 @@
-
+
@@ -29,7 +29,7 @@
-
+
@@ -56,20 +56,39 @@
-
-
+
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt b/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt
index 0d24cd0..5c9c301 100644
--- a/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt
+++ b/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt
@@ -1,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
-------------------------------------------------------------------------------
-Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.206 s -- in com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
+Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.505 s -- in com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
diff --git a/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class b/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class
index ef1af4d..6493fa4 100644
Binary files a/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class and b/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class differ
diff --git a/logs/fund-sys-shared-8100.log b/logs/fund-sys-shared-8100.log
index 87cc114..bbe95d6 100644
--- a/logs/fund-sys-shared-8100.log
+++ b/logs/fund-sys-shared-8100.log
@@ -1174,3 +1174,21 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:36:06.989 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
diff --git a/logs/fund-sys-tenant-vip001-8101.log b/logs/fund-sys-tenant-vip001-8101.log
index 1bd03c0..395045c 100644
--- a/logs/fund-sys-tenant-vip001-8101.log
+++ b/logs/fund-sys-tenant-vip001-8101.log
@@ -1138,3 +1138,39 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:34:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:39:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
diff --git a/logs/fund-sys/error.log b/logs/fund-sys/error.log
index 53e79df..1cad01a 100644
--- a/logs/fund-sys/error.log
+++ b/logs/fund-sys/error.log
@@ -1566,3 +1566,51 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:34:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:36:06.989 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
+2026-02-19 21:39:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
+java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
+ at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
+ at java.base/java.lang.reflect.Method.invoke(Method.java:580)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
+ at io.micrometer.observation.Observation.observe(Observation.java:499)
+ at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
+ at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
+ at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
+ at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
+ at java.base/java.lang.Thread.run(Thread.java:1583)
diff --git a/logs/fund-sys/info.log b/logs/fund-sys/info.log
index 1698e7a..13c83f5 100644
--- a/logs/fund-sys/info.log
+++ b/logs/fund-sys/info.log
@@ -340,3 +340,9 @@
2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool