org.springframework.boot
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/ZdxtGatewayApplication.java b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/ZdxtGatewayApplication.java
index 1496d095..c2dcf1b4 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/ZdxtGatewayApplication.java
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/ZdxtGatewayApplication.java
@@ -13,9 +13,7 @@ import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
*/
@SpringBootApplication
@LoadBalancerClients({
- @LoadBalancerClient(name = "ent-ep-service", configuration = LoadBalancerConfig.class),
- @LoadBalancerClient(name = "enf-ep-service", configuration = LoadBalancerConfig.class),
- @LoadBalancerClient(name = "sup-ep-service", configuration = LoadBalancerConfig.class)
+ @LoadBalancerClient(name = "zdxt-admin-service", configuration = LoadBalancerConfig.class)
})
public class ZdxtGatewayApplication {
public static void main(String[] args) {
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/LoadBalancerConfig.java b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/LoadBalancerConfig.java
index 1245116b..daaab0a1 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/LoadBalancerConfig.java
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/LoadBalancerConfig.java
@@ -1,7 +1,6 @@
package com.zdxt.gateway.webserver.config;
import org.springframework.cloud.client.ServiceInstance;
-import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
@@ -9,18 +8,11 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
/**
- * 负载均衡配置 - 优先级策略
- *
- * 始终优先选择配置列表中的第一个健康实例,
- * 首选实例不可用时按配置顺序从剩余健康实例中选取。
+ * 负载均衡配置 - 优先角色策略(基于 Nacos 实例 metadata)
*
- * 通过 {@link SimpleDiscoveryProperties} 读取原始配置顺序,
- * 确保健康检查并发返回结果后仍能正确识别首选实例。
+ * 通过路由 metadata.role-order 声明角色优先级,PreferredRoleFilter 将其注入到请求头,
+ * PreferredFirstLoadBalancer 从头读取并按顺序在 Nacos 实例 metadata(zdxt.role)中匹配。
*
* @author zdxt
*/
@@ -30,21 +22,11 @@ public class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer preferredFirstLoadBalancer(
Environment environment,
- LoadBalancerClientFactory loadBalancerClientFactory,
- SimpleDiscoveryProperties discoveryProperties) {
+ LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
-
- // 从配置中获取该服务的原始实例顺序(即优先级)
- List preferredOrder = discoveryProperties.getInstances()
- .getOrDefault(name, Collections.emptyList())
- .stream()
- .map(instance -> instance.getHost() + ":" + instance.getPort())
- .collect(Collectors.toList());
-
return new PreferredFirstLoadBalancer(
- loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
- name,
- preferredOrder
+ loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
+ name
);
}
}
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/PreferredFirstLoadBalancer.java b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/PreferredFirstLoadBalancer.java
index 8d534cc2..bc5db6bf 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/PreferredFirstLoadBalancer.java
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/java/com/zdxt/gateway/webserver/config/PreferredFirstLoadBalancer.java
@@ -6,74 +6,104 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
+import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
- * 优先级负载均衡器 - 首选优先策略
+ * 优先级负载均衡器 - 按角色顺序选择实例
*
- * 根据配置文件中实例的声明顺序确定优先级,
- * 始终选择优先级最高的健康实例。
+ * 从请求 header {@value #HEADER_ROLE_ORDER} 读取角色优先级列表(由 PreferredRoleFilter 从路由 metadata 注入),
+ * 依次在 Nacos 拉到的健康实例中匹配 metadata {@value #METADATA_ROLE_KEY},命中即返回;
+ * 若列表中所有角色均未命中,则回退取第一个健康实例作为兜底。
*
- * 健康检查并发执行后返回结果顺序不确定,
- * 因此通过 preferredOrder 显式匹配配置中的优先级顺序。
+ * 完全替代原有静态 SimpleDiscoveryProperties 方案,同时保留“首选优先 + 兜底容错”语义。
*
* @author zdxt
*/
@Slf4j
public class PreferredFirstLoadBalancer implements ReactorServiceInstanceLoadBalancer {
+ /** 路由 metadata 中优先级顺序注入到请求头的名称 */
+ public static final String HEADER_ROLE_ORDER = "X-Preferred-Role-Order";
+
+ /** Nacos 实例 metadata 中标识角色的 key(与 admin 服务 application.yml 保持一致)
+ * 注意:不使用点号(Spring Boot 会把点号视为嵌套路径拆分 Map key) */
+ public static final String METADATA_ROLE_KEY = "zdxt-role";
+
private final String serviceId;
private final ObjectProvider serviceInstanceListSupplierProvider;
- /** 配置中声明的实例顺序(host:port),索引越小优先级越高 */
- private final List preferredOrder;
public PreferredFirstLoadBalancer(
ObjectProvider serviceInstanceListSupplierProvider,
- String serviceId,
- List preferredOrder) {
+ String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
- this.preferredOrder = preferredOrder;
- log.info("[{}] 优先级顺序: {}", serviceId, preferredOrder);
+ log.info("[{}] PreferredFirstLoadBalancer 已启用,将按请求头 {} 指定的角色顺序优先选择实例",
+ serviceId, HEADER_ROLE_ORDER);
}
@Override
public Mono> choose(Request request) {
+ List roleOrder = extractRoleOrder(request);
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request)
.next()
- .map(this::selectInstance);
+ .map(instances -> selectByRoleOrder(instances, roleOrder));
}
- private Response selectInstance(List instances) {
+ private Response selectByRoleOrder(List instances, List roleOrder) {
if (instances.isEmpty()) {
log.warn("[{}] 无可用服务实例", serviceId);
return new EmptyResponse();
}
- // 按配置优先级顺序遍历,找到第一个在健康列表中的实例
- for (String preferred : preferredOrder) {
+ // 按 roleOrder 顺序遍历,找到第一个匹配角色且健康的实例
+ for (String role : roleOrder) {
for (ServiceInstance instance : instances) {
- String key = instance.getHost() + ":" + instance.getPort();
- if (preferred.equals(key)) {
- log.debug("[{}] 选择优先实例: {}, 健康实例数: {}",
- serviceId, key, instances.size());
+ String instanceRole = instance.getMetadata().get(METADATA_ROLE_KEY);
+ if (role.equals(instanceRole)) {
+ log.debug("[{}] 命中优先角色 [{}] -> {}:{} (metadata={})",
+ serviceId, role, instance.getHost(), instance.getPort(), instance.getMetadata());
return new DefaultResponse(instance);
}
}
}
- // 如果配置顺序中的实例都不在健康列表中(不应发生),取健康列表第一个
+ // 兜底:所有优先角色都不健康,取列表首个健康实例
ServiceInstance fallback = instances.get(0);
- log.warn("[{}] 未匹配到配置优先级实例,回退使用: {}:{}",
- serviceId, fallback.getHost(), fallback.getPort());
+ log.warn("[{}] roleOrder={} 未匹配到任何健康实例,回退到首个健康实例 {}:{} (role={})",
+ serviceId, roleOrder, fallback.getHost(), fallback.getPort(),
+ fallback.getMetadata().get(METADATA_ROLE_KEY));
return new DefaultResponse(fallback);
}
+
+ /**
+ * 从请求头 X-Preferred-Role-Order 提取角色优先级列表(逗号分隔)
+ */
+ private List extractRoleOrder(Request> request) {
+ Object context = request.getContext();
+ if (context instanceof RequestDataContext rdc) {
+ HttpHeaders headers = rdc.getClientRequest().getHeaders();
+ if (headers != null) {
+ String raw = headers.getFirst(HEADER_ROLE_ORDER);
+ if (raw != null && !raw.isBlank()) {
+ return Arrays.stream(raw.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .toList();
+ }
+ }
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-dev.yml b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-dev.yml
index 944ff93b..12cb645b 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-dev.yml
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-dev.yml
@@ -5,6 +5,13 @@ spring:
application:
name: zdxt-gateway
cloud:
+ nacos:
+ discovery:
+ server-addr: 127.0.0.1:8848
+ namespace: public
+ group: DEFAULT_GROUP
+ username: nacos
+ password: nacos
gateway:
# 全局CORS配置
globalcors:
@@ -20,26 +27,33 @@ spring:
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
- # 路由配置
+ # 路由配置 - 通过 metadata.role-order 声明每条路由的角色优先级
+ # (由 PreferredRoleFilter + PreferredFirstLoadBalancer 生效,后端在 Nacos 中注册为 zdxt-admin-service)
routes:
- id: enterprise-service
- uri: http://localhost:18000
+ uri: lb://zdxt-admin-service
predicates:
- Path=/ent-ep/**
+ metadata:
+ role-order: ent-ep,enf-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: supervision-service
- uri: http://localhost:18001
+ uri: lb://zdxt-admin-service
predicates:
- Path=/sup-ep/**
+ metadata:
+ role-order: sup-ep,ent-ep,enf-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: enforcement-service
- uri: http://localhost:18002
+ uri: lb://zdxt-admin-service
predicates:
- Path=/enf-ep/**
+ metadata:
+ role-order: enf-ep,ent-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
@@ -66,5 +80,7 @@ logging:
level:
root: info
org.springframework.cloud.gateway: info
+ org.springframework.cloud.loadbalancer: debug
com.zdxt.gateway: info
- com.zdxt.gateway.webserver.filter: info
+ com.zdxt.gateway.webserver.filter: debug
+ com.zdxt.gateway.webserver.config: debug
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-prod.yml b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-prod.yml
index 79cbbb4d..c0f384d3 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-prod.yml
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application-prod.yml
@@ -5,31 +5,15 @@ spring:
application:
name: zdxt-gateway
cloud:
- # 静态服务实例列表(无需注册中心,配置顺序即优先级)
- discovery:
- client:
- simple:
- instances:
- ent-ep-service:
- - uri: http://10.76.108.7:18000
- - uri: http://10.76.108.8:18002
- - uri: http://10.76.108.9:18001
- enf-ep-service:
- - uri: http://10.76.108.8:18002
- - uri: http://10.76.108.7:18000
- - uri: http://10.76.108.9:18001
- sup-ep-service:
- - uri: http://10.76.108.9:18001
- - uri: http://10.76.108.7:18000
- - uri: http://10.76.108.8:18002
- # LoadBalancer 健康检查配置
+ # LoadBalancer 配置
+ # 健康检查交给 Nacos 心跳(gRPC),不启用 Spring Cloud LoadBalancer 自带的 HTTP 探测,
+ # 否则会因后端 /actuator/health 被 Sa-Token 拦截返回 401 而把实例误判为不健康。
loadbalancer:
health-check:
initial-delay: 0s
interval: 10s
path:
default: /actuator/health
- configurations: health-check
gateway:
# 全局CORS配置
globalcors:
@@ -45,26 +29,32 @@ spring:
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
- # 路由配置 - 使用 lb:// 协议启用优先级负载均衡
+ # 路由配置 - 后端统一在 Nacos 注册为 zdxt-admin-service,通过 metadata.role-order 声明每条路由的角色优先级
routes:
- id: enterprise-service
- uri: lb://ent-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/ent-ep/**
+ metadata:
+ role-order: ent-ep,enf-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: supervision-service
- uri: lb://sup-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/sup-ep/**
+ metadata:
+ role-order: sup-ep,ent-ep,enf-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: enforcement-service
- uri: lb://enf-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/enf-ep/**
+ metadata:
+ role-order: enf-ep,ent-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
diff --git a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application.yml b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application.yml
index cf55ca89..e943aa8a 100644
--- a/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application.yml
+++ b/zdxt-web-server/zdxt-gateway-web-server/src/main/resources/application.yml
@@ -5,31 +5,17 @@ spring:
application:
name: zdxt-gateway
cloud:
- # 静态服务实例列表(无需注册中心,配置顺序即优先级)
- discovery:
- client:
- simple:
- instances:
- ent-ep-service:
- - uri: http://localhost:18000
- - uri: http://localhost:18002
- - uri: http://localhost:18001
- enf-ep-service:
- - uri: http://localhost:18002
- - uri: http://localhost:18000
- - uri: http://localhost:18001
- sup-ep-service:
- - uri: http://localhost:18001
- - uri: http://localhost:18000
- - uri: http://localhost:18002
- # LoadBalancer 健康检查配置
+ # LoadBalancer 配置
+ # 健康检查交给 Nacos 心跳(gRPC),不启用 Spring Cloud LoadBalancer 自带的 HTTP 探测,
+ # 否则会因后端 /actuator/health 被 Sa-Token 拦截返回 401 而把实例误判为不健康,
+ # 导致 LoadBalancer 拉到的实例列表残缺,PreferredFirstLoadBalancer 无法命中正确角色。
loadbalancer:
+ # 保留 health-check 相关默认值(未启用时不生效),便于将来在 /actuator 免鉴权后重新开启
health-check:
initial-delay: 0s
interval: 10s
path:
default: /actuator/health
- configurations: health-check
gateway:
# 全局CORS配置
globalcors:
@@ -45,26 +31,32 @@ spring:
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
- # 路由配置 - 使用 lb:// 协议启用优先级负载均衡
+ # 路由配置 - 后端统一在 Nacos 注册为 zdxt-admin-service,通过 metadata.role-order 声明每条路由的角色优先级
routes:
- id: enterprise-service
- uri: lb://ent-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/ent-ep/**
+ metadata:
+ role-order: ent-ep,enf-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: supervision-service
- uri: lb://sup-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/sup-ep/**
+ metadata:
+ role-order: sup-ep,ent-ep,enf-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
- id: enforcement-service
- uri: lb://enf-ep-service
+ uri: lb://zdxt-admin-service
predicates:
- Path=/enf-ep/**
+ metadata:
+ role-order: enf-ep,ent-ep,sup-ep
filters:
- StripPrefix=1
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE