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 客户:独立部署服务实例(带 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 过滤服务实例 * *

路由策略:

*
    - *
  1. 优先选择租户专属实例(metadata.tenant-group 匹配)
  2. - *
  3. 回退到共享实例(无 tenant-group 标签)
  4. + *
  5. 优先选择租户专属实例(metadata.tenant-id 匹配)
  6. + *
  7. 回退到共享实例(metadata.tenant-id 为空或不存在)
  8. *
*/ - 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