完善 yudao-spring-boot-starter-env 组件,完成 feign 组件
							parent
							
								
									f879c4aa2b
								
							
						
					
					
						commit
						d0ce24a2f6
					
				|  | @ -46,6 +46,21 @@ | |||
|             <artifactId>jakarta.servlet-api</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- RPC 相关 --> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.cloud</groupId> | ||||
|             <artifactId>spring-cloud-loadbalancer</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>io.github.openfeign</groupId> | ||||
|             <artifactId>feign-core</artifactId> | ||||
|         </dependency> | ||||
| 
 | ||||
|         <!-- Registry 注册中心相关 --> | ||||
|         <dependency> | ||||
|             <groupId>com.alibaba.cloud</groupId> | ||||
|             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
| </project> | ||||
|  |  | |||
|  | @ -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<List<LoadBalancerClientSpecification>> configurations) { | ||||
|         EnvLoadBalancerClientFactory clientFactory = new EnvLoadBalancerClientFactory(properties); | ||||
|         clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList)); | ||||
|         return clientFactory; | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public EnvRequestInterceptor envRequestInterceptor() { | ||||
|         return new EnvRequestInterceptor(); | ||||
|     } | ||||
| 
 | ||||
|     // ========== Dubbo 相关 ==========
 | ||||
| 
 | ||||
| } | ||||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; | ||||
|     /** | ||||
|      * 需要获取的服务实例名 | ||||
|      * | ||||
|      * 暂时用于打印 logger 日志 | ||||
|      */ | ||||
|     private final String serviceId; | ||||
|     /** | ||||
|      * 被代理的 ReactiveLoadBalancer 对象 | ||||
|      */ | ||||
|     private final ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer; | ||||
| 
 | ||||
|     @Override | ||||
|     public Mono<Response<ServiceInstance>> 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<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, String tag) { | ||||
|         // 如果服务实例为空,则直接返回
 | ||||
|         if (CollUtil.isEmpty(instances)) { | ||||
|             log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); | ||||
|             return new EmptyResponse(); | ||||
|         } | ||||
| 
 | ||||
|         // 筛选满足条件的实例列表
 | ||||
|         List<ServiceInstance> 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)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<ServiceInstance> getInstance(String serviceId) { | ||||
|         ReactiveLoadBalancer<ServiceInstance> 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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -35,8 +35,16 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { | |||
| 
 | ||||
|     private static final String VERSION = "version"; | ||||
| 
 | ||||
|     private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; // 用于获取 serviceId 对应的服务实例的列表
 | ||||
|     private final String serviceId; // 服务名,暂时用于打印 logger 日志
 | ||||
|     /** | ||||
|      * 用于获取 serviceId 对应的服务实例的列表 | ||||
|      */ | ||||
|     private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; | ||||
|     /** | ||||
|      * 需要获取的服务实例名 | ||||
|      * | ||||
|      * 暂时用于打印 logger 日志 | ||||
|      */ | ||||
|     private final String serviceId; | ||||
| 
 | ||||
|     @Override | ||||
|     public Mono<Response<ServiceInstance>> choose(Request request) { | ||||
|  | @ -50,9 +58,7 @@ public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer { | |||
|     private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) { | ||||
|         // 如果服务实例为空,则直接返回
 | ||||
|         if (CollUtil.isEmpty(instances)) { | ||||
|             if (log.isWarnEnabled()) { | ||||
|                 log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); | ||||
|             } | ||||
|             log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId); | ||||
|             return new EmptyResponse(); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV