diff --git a/docker-compose.yml b/docker-compose.yml
index 5b5eac5..794354e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -184,8 +184,8 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m
- # 租户组 - 共享实例(空值,所有租户可用)
- TENANT_GROUP: ""
+ # 租户 ID - 共享实例(空值,所有租户可用)
+ TENANT_ID: ""
ports:
- "8100:8100"
depends_on:
@@ -227,8 +227,8 @@ services:
REDIS_HOST: redis
REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m
- # 租户组 - VIP_001 专属实例
- TENANT_GROUP: "TENANT_VIP_001"
+ # 租户 ID - VIP_001 专属实例
+ TENANT_ID: "VIP_001"
ports:
- "8101:8101"
depends_on:
@@ -270,8 +270,8 @@ services:
# REDIS_HOST: redis
# REDIS_PORT: 6379
# JAVA_OPTS: -Xms256m -Xmx512m
- # # 租户组 - VIP_002 专属实例
- # TENANT_GROUP: "TENANT_VIP_002"
+ # # 租户 ID - VIP_002 专属实例
+ # TENANT_ID: "VIP_002"
# ports:
# - "8102:8102"
# depends_on:
diff --git a/fund-common/src/main/java/com/fundplatform/common/config/TenantRoutingProperties.java b/fund-common/src/main/java/com/fundplatform/common/config/TenantRoutingProperties.java
index 3e5e514..cf5b29b 100644
--- a/fund-common/src/main/java/com/fundplatform/common/config/TenantRoutingProperties.java
+++ b/fund-common/src/main/java/com/fundplatform/common/config/TenantRoutingProperties.java
@@ -9,17 +9,17 @@ import java.util.List;
/**
* 多租户路由配置属性
*
- *
租户路由基于 Nacos 服务实例的 metadata.tenant-group 元数据进行匹配
+ * 租户路由基于 Nacos 服务实例的 metadata.tenant-id 元数据进行匹配
*
* 工作原理:
*
- * 1. 服务实例注册到 Nacos 时,在 metadata 中声明 tenant-group
- * - 共享实例: tenant-group 为空或不存在
- * - VIP 实例: tenant-group = "TENANT_VIP_001"
+ * 1. 服务实例注册到 Nacos 时,在 metadata 中声明 tenant-id
+ * - 共享实例: tenant-id 为空或不存在
+ * - VIP 实例: tenant-id = "VIP_001"
*
- * 2. Gateway 从请求中提取租户组,负载均衡器匹配实例元数据
- * - 请求 tenantGroup = "TENANT_VIP_001" → 路由到匹配的 VIP 实例
- * - 无匹配实例 → 回退到共享实例
+ * 2. 负载均衡器根据请求中的 tenantId 匹配实例
+ * - 找到匹配的 tenant-id → VIP 专属实例
+ * - 找不到 → 回退到共享实例
*
*
* 配置示例:
@@ -28,11 +28,7 @@ import java.util.List;
* routing:
* enabled: true
* tenant-header: X-Tenant-Id
- * default-tenant-id: "1"
* fallback-to-shared: true
- * shared-services:
- * - fund-gateway
- * - fund-report
*
*/
@Component
@@ -45,12 +41,6 @@ public class TenantRoutingProperties {
/** 租户 ID 请求头 */
private String tenantHeader = "X-Tenant-Id";
- /** 租户组请求头 */
- private String tenantGroupHeader = "X-Tenant-Group";
-
- /** 服务组分隔符 */
- private String groupSeparator = "TENANT_";
-
/** 默认租户 ID(当未指定时使用) */
private String defaultTenantId = "1";
@@ -80,22 +70,6 @@ public class TenantRoutingProperties {
this.tenantHeader = tenantHeader;
}
- public String getTenantGroupHeader() {
- return tenantGroupHeader;
- }
-
- public void setTenantGroupHeader(String tenantGroupHeader) {
- this.tenantGroupHeader = tenantGroupHeader;
- }
-
- public String getGroupSeparator() {
- return groupSeparator;
- }
-
- public void setGroupSeparator(String groupSeparator) {
- this.groupSeparator = groupSeparator;
- }
-
public String getDefaultTenantId() {
return defaultTenantId;
}
@@ -120,24 +94,8 @@ public class TenantRoutingProperties {
this.fallbackToShared = fallbackToShared;
}
- /**
- * 构建租户组名称
- *
- * @param tenantId 租户 ID
- * @return 租户组名称,如 "TENANT_VIP_001"
- */
- public String buildTenantGroup(String tenantId) {
- if (tenantId == null || tenantId.isEmpty()) {
- return "DEFAULT";
- }
- return getGroupSeparator() + tenantId.toUpperCase();
- }
-
/**
* 判断是否为共享服务
- *
- * @param serviceName 服务名
- * @return 是否为共享服务
*/
public boolean isSharedService(String serviceName) {
return sharedServices != null && sharedServices.contains(serviceName);
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 ff778f8..9f530a3 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
@@ -24,17 +24,21 @@ import java.util.stream.Collectors;
*
* 根据租户 ID 进行服务实例路由,支持:
*
- * - 租户专属实例优先
- * - 共享实例回退
- * - 轮询负载均衡
- * - 混合模式(VIP 租户专属 + 普通租户共享)
+ * - 租户专属实例优先(metadata.tenant-id 匹配)
+ * - 共享实例回退(metadata.tenant-id 为空)
+ * - 随机负载均衡
*
*
- * 使用场景
+ * 路由规则:
*
- * 混合模式部署:
- * - VIP 客户:独立部署服务实例(带 tenant-group 标签)
- * - 普通客户:共享服务实例(无 tenant-group 标签)
+ * 1. 查找 metadata.tenant-id == 请求.tenantId 的实例 → VIP 专属实例
+ * 2. 找不到 → 回退到共享实例(metadata.tenant-id 为空)
+ *
+ *
+ * 实例配置:
+ *
+ * 共享实例: metadata.tenant-id = "" (空)
+ * VIP 实例: metadata.tenant-id = "VIP_001"
*
*/
public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@@ -61,9 +65,8 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
return getDefaultResponse();
}
- // 从请求上下文获取租户信息
+ // 从请求上下文获取租户 ID
String tenantId = getTenantIdFromRequest(request);
- String tenantGroup = buildTenantGroup(tenantId);
if (supplierProvider == null) {
logger.warn("[TenantLB] ServiceInstanceListSupplier 未提供");
@@ -77,7 +80,7 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
}
return supplier.get().next()
- .map(instances -> filterByTenantGroup(instances, tenantGroup))
+ .map(instances -> filterByTenantId(instances, tenantId))
.map(this::getInstanceResponse);
}
@@ -123,34 +126,20 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
}
/**
- * 构建租户组名称
- */
- String buildTenantGroup(String tenantId) {
- if (routingProperties != null) {
- return routingProperties.buildTenantGroup(tenantId);
- }
- // 默认逻辑
- if (tenantId == null || tenantId.isEmpty()) {
- return "DEFAULT";
- }
- return "TENANT_" + tenantId.toUpperCase();
- }
-
- /**
- * 根据租户组过滤服务实例
+ * 根据租户 ID 过滤服务实例
*
* 路由策略:
*
- * - 优先选择租户专属实例(metadata.tenant-group 匹配)
- * - 回退到共享实例(无 tenant-group 标签)
+ * - 优先选择租户专属实例(metadata.tenant-id 匹配)
+ * - 回退到共享实例(metadata.tenant-id 为空或不存在)
*
*/
- List filterByTenantGroup(List instances, String tenantGroup) {
+ List filterByTenantId(List instances, String tenantId) {
if (instances == null || instances.isEmpty()) {
return instances;
}
- logger.debug("[TenantLB] 租户组:{},候选实例数:{}", tenantGroup, instances.size());
+ logger.debug("[TenantLB] 租户 ID:{},候选实例数:{}", tenantId, instances.size());
// 检查是否为共享服务(不需要租户路由)
if (routingProperties != null && routingProperties.isSharedService(serviceId)) {
@@ -159,22 +148,24 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
}
// 优先选择租户专属实例
- List tenantInstances = instances.stream()
- .filter(inst -> {
- Map metadata = inst.getMetadata();
- if (metadata == null) return false;
- String instanceGroup = metadata.get("tenant-group");
- boolean match = tenantGroup.equals(instanceGroup);
- if (match) {
- logger.debug("[TenantLB] 匹配租户实例:{}:{}", inst.getHost(), inst.getPort());
- }
- return match;
- })
- .collect(Collectors.toList());
-
- if (!tenantInstances.isEmpty()) {
- logger.info("[TenantLB] 找到 {} 个租户专属实例,租户组:{}", tenantInstances.size(), tenantGroup);
- return tenantInstances;
+ 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());
+ }
+ return match;
+ })
+ .collect(Collectors.toList());
+
+ if (!tenantInstances.isEmpty()) {
+ logger.info("[TenantLB] 找到 {} 个租户专属实例,租户 ID:{}", tenantInstances.size(), tenantId);
+ return tenantInstances;
+ }
}
// 检查是否启用回退到共享实例
@@ -188,11 +179,11 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
return List.of();
}
- // 回退到共享实例(无 tenant-group 标签)
+ // 回退到共享实例(metadata.tenant-id 为空或不存在)
List sharedInstances = instances.stream()
.filter(inst -> {
Map metadata = inst.getMetadata();
- return metadata == null || !metadata.containsKey("tenant-group");
+ return metadata == null || metadata.get("tenant-id") == null || metadata.get("tenant-id").isEmpty();
})
.collect(Collectors.toList());
@@ -209,10 +200,9 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
return new EmptyResponse();
}
- // 随机选择
int index = ThreadLocalRandom.current().nextInt(instances.size());
ServiceInstance chosen = instances.get(index);
- logger.info("[TenantLB] 选择实例:{}:{} (index={})", chosen.getHost(), chosen.getPort(), index);
+ logger.info("[TenantLB] 选择实例:{}:{}", chosen.getHost(), chosen.getPort());
return new DefaultResponse(chosen);
}
diff --git a/fund-common/src/main/java/com/fundplatform/common/nacos/NacosMetadataConfig.java b/fund-common/src/main/java/com/fundplatform/common/nacos/NacosMetadataConfig.java
index aca6d9f..71b7f0c 100644
--- a/fund-common/src/main/java/com/fundplatform/common/nacos/NacosMetadataConfig.java
+++ b/fund-common/src/main/java/com/fundplatform/common/nacos/NacosMetadataConfig.java
@@ -13,21 +13,21 @@ import java.util.Map;
/**
* Nacos 服务注册元数据配置
*
- * 服务启动时自动注册租户组标签到 Nacos,支持租户感知的负载均衡
+ * 服务启动时自动注册租户 ID 到 Nacos,支持租户感知的负载均衡
*
- * 租户组配置规则:
+ * 租户 ID 配置规则:
*
- * 共享实例: tenant-group 为空(或不配置)→ 所有租户都可使用
- * VIP实例: tenant-group = "TENANT_VIP_001" → 仅该租户组可用
+ * 共享实例: tenant-id 为空(或不配置)→ 所有租户都可使用
+ * VIP实例: tenant-id = "VIP_001" → 仅该租户可用
*
*
* 配置方式:
*
* # 方式1: 环境变量(Docker/K8s 推荐)
- * TENANT_GROUP=TENANT_VIP_001
+ * TENANT_ID=VIP_001
*
* # 方式2: 配置文件
- * spring.cloud.nacos.discovery.metadata.tenant-group=TENANT_VIP_001
+ * spring.cloud.nacos.discovery.metadata.tenant-id=VIP_001
*
*/
@Configuration
@@ -40,11 +40,11 @@ public class NacosMetadataConfig {
private String applicationName;
/**
- * 租户组,优先级:环境变量 > 配置文件
+ * 租户 ID,优先级:环境变量 > 配置文件
* 为空表示共享实例,供所有租户使用
*/
- @Value("${TENANT_GROUP:${spring.cloud.nacos.discovery.metadata.tenant-group:}}")
- private String tenantGroup;
+ @Value("${TENANT_ID:${spring.cloud.nacos.discovery.metadata.tenant-id:}}")
+ private String tenantId;
private final Registration registration;
@@ -57,21 +57,21 @@ public class NacosMetadataConfig {
if (registration != null && registration.getMetadata() != null) {
Map metadata = registration.getMetadata();
- // 添加租户组元数据
- if (tenantGroup != null && !tenantGroup.isEmpty()) {
- metadata.put("tenant-group", tenantGroup);
- logger.info("[Nacos] {} 注册为 VIP 专属实例,租户组:{}", applicationName, tenantGroup);
+ // 添加租户 ID 元数据
+ if (tenantId != null && !tenantId.isEmpty()) {
+ metadata.put("tenant-id", tenantId);
+ logger.info("[Nacos] {} 注册为 VIP 专属实例,租户 ID:{}", applicationName, tenantId);
} else {
logger.info("[Nacos] {} 注册为共享实例,供所有租户使用", applicationName);
}
}
}
- public String getTenantGroup() {
- return tenantGroup;
+ public String getTenantId() {
+ return tenantId;
}
public boolean isVipInstance() {
- return tenantGroup != null && !tenantGroup.isEmpty();
+ return tenantId != null && !tenantId.isEmpty();
}
}
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 6156114..90cacc7 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
@@ -4,13 +4,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
-import org.springframework.cloud.client.loadbalancer.Request;
-import org.springframework.cloud.client.loadbalancer.RequestData;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
/**
* 租户负载均衡器测试
@@ -25,73 +22,70 @@ class TenantAwareLoadBalancerTest {
}
@Test
- void testBuildTenantGroup() {
- // 测试租户组名称构建
- String group1 = invokeBuildTenantGroup(loadBalancer, "1");
- assertEquals("TENANT_1", group1);
-
- String group2 = invokeBuildTenantGroup(loadBalancer, "tenant_001");
- assertEquals("TENANT_TENANT_001", group2);
-
- String group3 = invokeBuildTenantGroup(loadBalancer, null);
- assertEquals("DEFAULT", group3);
-
- String group4 = invokeBuildTenantGroup(loadBalancer, "");
- assertEquals("DEFAULT", group4);
- }
-
- @Test
- void testFilterByTenantGroup() {
+ void testFilterByTenantId() {
// 创建测试实例
List instances = Arrays.asList(
- createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-group", "TENANT_1")),
- createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-group", "TENANT_1")),
- createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-group", "TENANT_2")),
+ 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()) // 共享实例
);
- // 测试租户 1 过滤
- List tenant1Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_1");
- assertEquals(2, tenant1Instances.size());
- assertTrue(tenant1Instances.stream().allMatch(i -> "TENANT_1".equals(i.getMetadata().get("tenant-group"))));
+ // 测试 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"))));
- // 测试租户 2 过滤
- List tenant2Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_2");
- assertEquals(1, tenant2Instances.size());
+ // 测试 VIP_002 过滤
+ List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
+ assertEquals(1, vip2Instances.size());
// 测试未知租户(回退到共享实例)
- List unknownTenantInstances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_UNKNOWN");
- assertEquals(1, unknownTenantInstances.size());
- assertFalse(unknownTenantInstances.get(0).getMetadata().containsKey("tenant-group"));
+ List unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999");
+ assertEquals(1, unknownInstances.size());
+ assertFalse(unknownInstances.get(0).getMetadata().containsKey("tenant-id"));
}
@Test
void testMixedMode() {
// 混合模式:VIP 客户有专属实例,普通客户使用共享实例
List instances = Arrays.asList(
- // VIP 租户 001 的专属实例
- createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-group", "TENANT_VIP_001")),
- createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-group", "TENANT_VIP_001")),
- // VIP 租户 002 的专属实例
- createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-group", "TENANT_VIP_002")),
+ // 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())
);
- // VIP 租户 001 应该路由到专属实例
- List vip1Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_VIP_001");
+ // VIP_001 应该路由到专属实例
+ List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
assertEquals(2, vip1Instances.size());
- // VIP 租户 002 应该路由到专属实例
- List vip2Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_VIP_002");
+ // VIP_002 应该路由到专属实例
+ List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
assertEquals(1, vip2Instances.size());
// 普通租户应该路由到共享实例
- List normalInstances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_NORMAL");
+ List normalInstances = invokeFilterByTenantId(loadBalancer, instances, "NORMAL_001");
assertEquals(2, normalInstances.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())
+ );
+
+ List result = invokeFilterByTenantId(loadBalancer, instances, null);
+ assertEquals(1, result.size());
+ assertFalse(result.get(0).getMetadata().containsKey("tenant-id"));
+ }
+
// 辅助方法:创建服务实例
private ServiceInstance createInstance(String serviceId, String host, int port, Map metadata) {
return new DefaultServiceInstance(
@@ -104,24 +98,13 @@ class TenantAwareLoadBalancerTest {
);
}
- // 辅助方法:调用私有方法 buildTenantGroup
- private String invokeBuildTenantGroup(TenantAwareLoadBalancer lb, String tenantId) {
- try {
- var method = TenantAwareLoadBalancer.class.getDeclaredMethod("buildTenantGroup", String.class);
- method.setAccessible(true);
- return (String) method.invoke(lb, tenantId);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- // 辅助方法:调用私有方法 filterByTenantGroup
+ // 辅助方法:调用私有方法 filterByTenantId
@SuppressWarnings("unchecked")
- private List invokeFilterByTenantGroup(TenantAwareLoadBalancer lb, List instances, String tenantGroup) {
+ private List invokeFilterByTenantId(TenantAwareLoadBalancer lb, List instances, String tenantId) {
try {
- var method = TenantAwareLoadBalancer.class.getDeclaredMethod("filterByTenantGroup", List.class, String.class);
+ var method = TenantAwareLoadBalancer.class.getDeclaredMethod("filterByTenantId", List.class, String.class);
method.setAccessible(true);
- return (List) method.invoke(lb, instances, tenantGroup);
+ return (List) method.invoke(lb, instances, tenantId);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/fund-common/target/classes/com/fundplatform/common/config/TenantRoutingProperties.class b/fund-common/target/classes/com/fundplatform/common/config/TenantRoutingProperties.class
index 354ff20..8c7877f 100644
Binary files a/fund-common/target/classes/com/fundplatform/common/config/TenantRoutingProperties.class and b/fund-common/target/classes/com/fundplatform/common/config/TenantRoutingProperties.class differ
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 18c4901..09a93da 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/classes/com/fundplatform/common/nacos/NacosMetadataConfig.class b/fund-common/target/classes/com/fundplatform/common/nacos/NacosMetadataConfig.class
index 8a993c0..d966fa5 100644
Binary files a/fund-common/target/classes/com/fundplatform/common/nacos/NacosMetadataConfig.class and b/fund-common/target/classes/com/fundplatform/common/nacos/NacosMetadataConfig.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 9c897c6..d2d15a3 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,17 +56,20 @@
-
-
-
+
-
-
+
+
+
+
\ 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 b17f2d0..0d24cd0 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.310 s -- in 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
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 45260cc..ef1af4d 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/fund-sys/src/main/resources/application.yml b/fund-sys/src/main/resources/application.yml
index c38e6e5..f6b2feb 100644
--- a/fund-sys/src/main/resources/application.yml
+++ b/fund-sys/src/main/resources/application.yml
@@ -14,9 +14,9 @@ spring:
username: nacos
password: nacos
# 租户路由元数据
- # tenant-group: 空值=共享实例,有值=VIP专属实例
+ # tenant-id: 空值=共享实例,有值=VIP专属实例
metadata:
- tenant-group: ${TENANT_GROUP:}
+ tenant-id: ${TENANT_ID:}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
diff --git a/fund-sys/target/classes/application.yml b/fund-sys/target/classes/application.yml
index c38e6e5..f6b2feb 100644
--- a/fund-sys/target/classes/application.yml
+++ b/fund-sys/target/classes/application.yml
@@ -14,9 +14,9 @@ spring:
username: nacos
password: nacos
# 租户路由元数据
- # tenant-group: 空值=共享实例,有值=VIP专属实例
+ # tenant-id: 空值=共享实例,有值=VIP专属实例
metadata:
- tenant-group: ${TENANT_GROUP:}
+ tenant-id: ${TENANT_ID:}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
diff --git a/logs/fund-sys-shared-8100.log b/logs/fund-sys-shared-8100.log
index 56db073..87cc114 100644
--- a/logs/fund-sys-shared-8100.log
+++ b/logs/fund-sys-shared-8100.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:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:26: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: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:31:06.988 [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 a21e5d3..1bd03c0 100644
--- a/logs/fund-sys-tenant-vip001-8101.log
+++ b/logs/fund-sys-tenant-vip001-8101.log
@@ -1120,3 +1120,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:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:29: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 97aa294..53e79df 100644
--- a/logs/fund-sys/error.log
+++ b/logs/fund-sys/error.log
@@ -1518,3 +1518,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:26: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:29: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:31:06.988 [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 4362c22..1698e7a 100644
--- a/logs/fund-sys/info.log
+++ b/logs/fund-sys/info.log
@@ -334,3 +334,9 @@
2026-02-19 21:21:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
+2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
+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