diff --git a/yudao-framework/yudao-spring-boot-starter-env/pom.xml b/yudao-framework/yudao-spring-boot-starter-env/pom.xml
index c502a7267..3b5ca0771 100644
--- a/yudao-framework/yudao-spring-boot-starter-env/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-env/pom.xml
@@ -46,6 +46,21 @@
jakarta.servlet-api
+
+
+ org.springframework.cloud
+ spring-cloud-loadbalancer
+
+
+ io.github.openfeign
+ feign-core
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvRpcAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvRpcAutoConfiguration.java
new file mode 100644
index 000000000..28e7148b3
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvRpcAutoConfiguration.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.framework.env.config;
+
+import cn.iocoder.yudao.framework.env.core.fegin.EnvLoadBalancerClientFactory;
+import cn.iocoder.yudao.framework.env.core.fegin.EnvRequestInterceptor;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
+import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 多环境的 RPC 组件的自动配置
+ *
+ * @author 芋道源码
+ */
+@Configuration
+public class YudaoEnvRpcAutoConfiguration {
+
+ // ========== Feign 相关 ==========
+
+ // TODO @芋艿:由于 loadBalancerClientFactoryBeanPostProcessor 拦截不到 LoadBalancerClientFactory,所以采用 loadBalancerClientFactory 实现
+// @Bean
+// public BeanPostProcessor loadBalancerClientFactoryBeanPostProcessor(LoadBalancerClientsProperties properties) {
+// return new BeanPostProcessor() {
+// @Override
+// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+// if (!(bean instanceof LoadBalancerClientFactory)) {
+// return bean;
+// }
+// return bean;
+// }
+// };
+// }
+
+ /**
+ * 创建 {@link EnvLoadBalancerClientFactory} Bean
+ *
+ * 参考 {@link org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration#loadBalancerClientFactory(LoadBalancerClientsProperties)} 方法
+ */
+ @Bean
+ public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,
+ ObjectProvider> configurations) {
+ EnvLoadBalancerClientFactory clientFactory = new EnvLoadBalancerClientFactory(properties);
+ clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
+ return clientFactory;
+ }
+
+ @Bean
+ public EnvRequestInterceptor envRequestInterceptor() {
+ return new EnvRequestInterceptor();
+ }
+
+ // ========== Dubbo 相关 ==========
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvWebAutoConfiguration.java
index ad21b80b5..a730a6851 100644
--- a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvWebAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/config/YudaoEnvWebAutoConfiguration.java
@@ -7,6 +7,11 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+/**
+ * 多环境的 Web 组件的自动配置
+ *
+ * @author 芋道源码
+ */
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class YudaoEnvWebAutoConfiguration {
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClient.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClient.java
new file mode 100644
index 000000000..950aa8223
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClient.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.framework.env.core.fegin;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.env.core.context.EnvContextHolder;
+import cn.iocoder.yudao.framework.env.core.util.EnvUtils;
+import com.alibaba.cloud.nacos.balancer.NacosBalancer;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+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.Response;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+/**
+ * 多环境的 {@link org.springframework.cloud.client.loadbalancer.LoadBalancerClient} 实现类
+ * 在从服务实例列表选择时,优先选择 tag 匹配的服务实例
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Slf4j
+public class EnvLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {
+
+ /**
+ * 用于获取 serviceId 对应的服务实例的列表
+ */
+ private final ObjectProvider serviceInstanceListSupplierProvider;
+ /**
+ * 需要获取的服务实例名
+ *
+ * 暂时用于打印 logger 日志
+ */
+ private final String serviceId;
+ /**
+ * 被代理的 ReactiveLoadBalancer 对象
+ */
+ private final ReactiveLoadBalancer reactiveLoadBalancer;
+
+ @Override
+ public Mono> choose(Request request) {
+ String tag = EnvContextHolder.getTag();
+ if (StrUtil.isEmpty(tag)) {
+ return Mono.from(reactiveLoadBalancer.choose(request));
+ }
+ // 选择实例
+ ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
+ return supplier.get(request).next().map(list -> getInstanceResponse(list, tag));
+ }
+
+ private Response getInstanceResponse(List instances, String tag) {
+ // 如果服务实例为空,则直接返回
+ if (CollUtil.isEmpty(instances)) {
+ log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId);
+ return new EmptyResponse();
+ }
+
+ // 筛选满足条件的实例列表
+ List chooseInstances = CollectionUtils.filterList(instances, instance -> tag.equals(EnvUtils.getTag(instance)));
+ if (CollUtil.isEmpty(chooseInstances)) {
+ log.warn("[getInstanceResponse][serviceId({}) 没有满足 tag({}) 的服务实例列表,直接使用所有服务实例列表]", serviceId, tag);
+ chooseInstances = instances;
+ }
+
+ // TODO 芋艿:https://juejin.cn/post/7056770721858469896 想通网段
+
+ // 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法
+ return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances));
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClientFactory.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClientFactory.java
new file mode 100644
index 000000000..ebaeca97e
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvLoadBalancerClientFactory.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.framework.env.core.fegin;
+
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+
+/**
+ * 多环境的 {@link LoadBalancerClientFactory} 实现类
+ * 目的:在创建 {@link ReactiveLoadBalancer} 时,会额外增加 {@link EnvLoadBalancerClient} 代理,用于 tag 过滤服务实例
+ *
+ * @author 芋道源码
+ */
+public class EnvLoadBalancerClientFactory extends LoadBalancerClientFactory {
+
+ public EnvLoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
+ super(properties);
+ }
+
+ @Override
+ public ReactiveLoadBalancer getInstance(String serviceId) {
+ ReactiveLoadBalancer reactiveLoadBalancer = super.getInstance(serviceId);
+ // 参考 {@link com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration#nacosLoadBalancer(Environment, LoadBalancerClientFactory, NacosDiscoveryProperties)} 方法
+ return new EnvLoadBalancerClient(super.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
+ serviceId, reactiveLoadBalancer);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvRequestInterceptor.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvRequestInterceptor.java
new file mode 100644
index 000000000..4c60eb556
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/fegin/EnvRequestInterceptor.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.framework.env.core.fegin;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.env.core.context.EnvContextHolder;
+import cn.iocoder.yudao.framework.env.core.util.EnvUtils;
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+
+/**
+ * 多环境的 {@link RequestInterceptor} 实现类:Feign 请求时,将 tag 设置到 header 中,继续透传给被调用的服务
+ *
+ * @author 芋道源码
+ */
+public class EnvRequestInterceptor implements RequestInterceptor {
+
+ @Override
+ public void apply(RequestTemplate requestTemplate) {
+ String tag = EnvContextHolder.getTag();
+ if (StrUtil.isNotEmpty(tag)) {
+ EnvUtils.setTag(requestTemplate, tag);
+ }
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/util/EnvUtils.java b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/util/EnvUtils.java
index e7fb36c91..f867b9ab3 100644
--- a/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/util/EnvUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/java/cn/iocoder/yudao/framework/env/core/util/EnvUtils.java
@@ -1,6 +1,8 @@
package cn.iocoder.yudao.framework.env.core.util;
import cn.hutool.core.net.NetUtil;
+import feign.RequestTemplate;
+import org.springframework.cloud.client.ServiceInstance;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
@@ -12,15 +14,23 @@ import java.util.Objects;
*/
public class EnvUtils {
- private static final String HEADER_DUBBO_TAG = "tag";
+ private static final String HEADER_TAG = "tag";
private static final String HOST_NAME_VALUE = "${HOSTNAME}";
public static String getTag(HttpServletRequest request) {
- String tag = request.getHeader(HEADER_DUBBO_TAG);
+ String tag = request.getHeader(HEADER_TAG);
// 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名
// 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做
return Objects.equals(tag, HOST_NAME_VALUE) ? NetUtil.getLocalHostName() : tag;
}
+ public static String getTag(ServiceInstance instance) {
+ return instance.getMetadata().get(HEADER_TAG);
+ }
+
+ public static void setTag(RequestTemplate requestTemplate, String tag) {
+ requestTemplate.header(HEADER_TAG, tag);
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-env/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-env/src/main/resources/META-INF/spring.factories
index c5751ebde..ffb232030 100644
--- a/yudao-framework/yudao-spring-boot-starter-env/src/main/resources/META-INF/spring.factories
+++ b/yudao-framework/yudao-spring-boot-starter-env/src/main/resources/META-INF/spring.factories
@@ -1,2 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- cn.iocoder.yudao.framework.env.config.YudaoEnvWebAutoConfiguration
+ cn.iocoder.yudao.framework.env.config.YudaoEnvWebAutoConfiguration,\
+ cn.iocoder.yudao.framework.env.config.YudaoEnvRpcAutoConfiguration
diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/grey/GrayLoadBalancer.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/grey/GrayLoadBalancer.java
index a0902cef7..1ef2d01ab 100644
--- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/grey/GrayLoadBalancer.java
+++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/filter/grey/GrayLoadBalancer.java
@@ -35,8 +35,16 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final String VERSION = "version";
- private final ObjectProvider serviceInstanceListSupplierProvider; // 用于获取 serviceId 对应的服务实例的列表
- private final String serviceId; // 服务名,暂时用于打印 logger 日志
+ /**
+ * 用于获取 serviceId 对应的服务实例的列表
+ */
+ private final ObjectProvider serviceInstanceListSupplierProvider;
+ /**
+ * 需要获取的服务实例名
+ *
+ * 暂时用于打印 logger 日志
+ */
+ private final String serviceId;
@Override
public Mono> choose(Request request) {
@@ -50,9 +58,7 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private Response getInstanceResponse(List instances, HttpHeaders headers) {
// 如果服务实例为空,则直接返回
if (CollUtil.isEmpty(instances)) {
- if (log.isWarnEnabled()) {
- log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId);
- }
+ log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId);
return new EmptyResponse();
}