完善 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