feat: initial iShare project code
This commit is contained in:
44
pigx-common/pigx-common-gateway/pom.xml
Normal file
44
pigx-common/pigx-common-gateway/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pigx-common-gateway</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<description>pigx gateway</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pigx-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
</dependency>
|
||||
<!--网关 swagger 聚合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-gateway-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.annotation;
|
||||
|
||||
import com.pig4cloud.pigx.common.gateway.configuration.DynamicRouteAutoConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/11/5
|
||||
* <p>
|
||||
* 开启pigx 动态路由
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(DynamicRouteAutoConfiguration.class)
|
||||
public @interface EnablePigxDynamicRoute {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.configuration;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import com.pig4cloud.pigx.common.gateway.support.DynamicRouteHealthIndicator;
|
||||
import com.pig4cloud.pigx.common.gateway.support.RouteCacheHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/11/5
|
||||
* <p>
|
||||
* 动态路由配置类
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ComponentScan("com.pig4cloud.pigx.common.gateway")
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
public class DynamicRouteAutoConfiguration {
|
||||
|
||||
/**
|
||||
* redis 监听配置
|
||||
* @param redisConnectionFactory redis 配置
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(redisConnectionFactory);
|
||||
container.addMessageListener((message, bytes) -> {
|
||||
log.warn("接收到重新JVM 重新加载路由事件");
|
||||
RouteCacheHolder.removeRouteList();
|
||||
// 发送刷新路由事件
|
||||
SpringContextHolder.publishEvent(new RefreshRoutesEvent(this));
|
||||
}, new ChannelTopic(CacheConstants.ROUTE_JVM_RELOAD_TOPIC));
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态路由监控检查
|
||||
* @param redisTemplate redis
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public DynamicRouteHealthIndicator healthIndicator(RedisTemplate redisTemplate) {
|
||||
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
if (!redisTemplate.hasKey(CacheConstants.ROUTE_KEY)) {
|
||||
log.info("路由信息未初始化,网关路由失败");
|
||||
}
|
||||
|
||||
return new DynamicRouteHealthIndicator(redisTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.pig4cloud.pigx.common.gateway.configuration;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/5/23
|
||||
* <p>
|
||||
* 网关异常通用处理器,只作用在webflux 环境下 , 优先级低于 {@link ResponseStatusExceptionHandler} 执行
|
||||
*/
|
||||
@Slf4j
|
||||
@Order(-1)
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (response.isCommitted()) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
// header set
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
if (ex instanceof ResponseStatusException) {
|
||||
response.setStatusCode(((ResponseStatusException) ex).getStatus());
|
||||
}
|
||||
|
||||
return response.writeWith(Mono.fromSupplier(() -> {
|
||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
||||
try {
|
||||
log.error("Error rquest :{} Error Spring Cloud Gateway : {}", exchange.getRequest().getPath(),
|
||||
ex.getMessage());
|
||||
return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
log.warn("Error writing response", ex);
|
||||
return bufferFactory.wrap(new byte[0]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.configuration;
|
||||
|
||||
import com.pig4cloud.pigx.common.gateway.filter.GrayReactiveLoadBalancerClientFilter;
|
||||
import com.pig4cloud.pigx.common.gateway.rule.GrayLoadBalancer;
|
||||
import com.pig4cloud.pigx.common.gateway.rule.VersionGrayLoadBalancer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Mica ribbon rule auto configuration.
|
||||
*
|
||||
* @author L.cm
|
||||
* @link https://github.com/lets-mica/mica
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "gray.rule.enabled", havingValue = "true")
|
||||
@AutoConfigureBefore(GatewayReactiveLoadBalancerClientAutoConfiguration.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
public class GrayLoadBalancerClientConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(GrayLoadBalancer grayLoadBalancer,
|
||||
LoadBalancerProperties properties, GatewayLoadBalancerProperties loadBalancerProperties) {
|
||||
return new GrayReactiveLoadBalancerClientFilter(loadBalancerProperties, properties, grayLoadBalancer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GrayLoadBalancer grayLoadBalancer(DiscoveryClient discoveryClient) {
|
||||
return new VersionGrayLoadBalancer(discoveryClient);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.filter;
|
||||
|
||||
import com.pig4cloud.pigx.common.gateway.rule.GrayLoadBalancer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
|
||||
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/1/11
|
||||
*/
|
||||
@Slf4j
|
||||
public class GrayReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter {
|
||||
|
||||
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
|
||||
|
||||
private GatewayLoadBalancerProperties loadBalancerProperties;
|
||||
|
||||
private GrayLoadBalancer grayLoadBalancer;
|
||||
|
||||
public GrayReactiveLoadBalancerClientFilter(GatewayLoadBalancerProperties loadBalancerProperties,
|
||||
LoadBalancerProperties properties, GrayLoadBalancer grayLoadBalancer) {
|
||||
super(null, loadBalancerProperties);
|
||||
this.grayLoadBalancer = grayLoadBalancer;
|
||||
this.loadBalancerProperties = loadBalancerProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
|
||||
String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
|
||||
if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
// preserve the original url
|
||||
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
|
||||
}
|
||||
|
||||
return choose(exchange).doOnNext(response -> {
|
||||
|
||||
if (!response.hasServer()) {
|
||||
throw NotFoundException.create(loadBalancerProperties.isUse404(),
|
||||
"Unable to find instance for " + url.getHost());
|
||||
}
|
||||
|
||||
URI uri = exchange.getRequest().getURI();
|
||||
|
||||
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
|
||||
// if the loadbalancer doesn't provide one.
|
||||
String overrideScheme = null;
|
||||
if (schemePrefix != null) {
|
||||
overrideScheme = url.getScheme();
|
||||
}
|
||||
|
||||
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(),
|
||||
overrideScheme);
|
||||
|
||||
URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
|
||||
}
|
||||
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
|
||||
}).then(chain.filter(exchange));
|
||||
}
|
||||
|
||||
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
|
||||
URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
|
||||
ServiceInstance serviceInstance = grayLoadBalancer.choose(uri.getHost(), exchange.getRequest());
|
||||
return Mono.just(new DefaultResponse(serviceInstance));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.rule;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/1/12
|
||||
* <p>
|
||||
* 灰度路由
|
||||
*/
|
||||
public interface GrayLoadBalancer {
|
||||
|
||||
/**
|
||||
* 根据serviceId 筛选可用服务
|
||||
* @param serviceId 服务ID
|
||||
* @param request 当前请求
|
||||
* @return
|
||||
*/
|
||||
ServiceInstance choose(String serviceId, ServerHttpRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.rule;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.nacos.client.naming.utils.Chooser;
|
||||
import com.alibaba.nacos.client.naming.utils.Pair;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/1/12
|
||||
* <p>
|
||||
* 基于客户端版本号灰度路由,增加基于nacos权重获取服务
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class VersionGrayLoadBalancer implements GrayLoadBalancer {
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
/**
|
||||
* 根据serviceId 筛选可用服务
|
||||
* @param serviceId 服务ID
|
||||
* @param request 当前请求
|
||||
* @return 服务实例:ServiceInstance
|
||||
*/
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId, ServerHttpRequest request) {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
||||
// 注册中心无实例 抛出异常
|
||||
if (CollUtil.isEmpty(instances)) {
|
||||
log.warn("No instance available for {}", serviceId);
|
||||
throw new NotFoundException("No instance available for " + serviceId);
|
||||
}
|
||||
// 获取请求version,无则随机返回可用实例
|
||||
String reqVersion = request.getHeaders().getFirst(CommonConstants.VERSION);
|
||||
if (StrUtil.isBlank(reqVersion)) {
|
||||
// 过滤出不含VERSION实例
|
||||
List<ServiceInstance> versionInstanceList = instances.stream()
|
||||
.filter(instance -> !instance.getMetadata().containsKey(CommonConstants.VERSION))
|
||||
.collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(versionInstanceList)) {
|
||||
// 根据权重获取实例
|
||||
return randomByWeight(instances);
|
||||
}
|
||||
// 根据权重获取实例
|
||||
return randomByWeight(versionInstanceList);
|
||||
}
|
||||
// 遍历可以实例元数据,若匹配则返回此实例
|
||||
List<ServiceInstance> availableList = instances.stream()
|
||||
.filter(instance -> reqVersion
|
||||
.equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstants.VERSION)))
|
||||
.collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(availableList)) {
|
||||
// 根据权重获取实例
|
||||
return randomByWeight(instances);
|
||||
}
|
||||
// 根据权重获取实例
|
||||
return randomByWeight(availableList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据nacos设置的权重返回实例
|
||||
* @param serviceInstances 服务实例集合
|
||||
* @return 服务实例:ServiceInstance
|
||||
*/
|
||||
protected ServiceInstance randomByWeight(final List<ServiceInstance> serviceInstances) {
|
||||
if (serviceInstances.size() == 1) {
|
||||
return serviceInstances.get(0);
|
||||
}
|
||||
else {
|
||||
List<Pair<ServiceInstance>> hostsWithWeight = new ArrayList<>();
|
||||
for (ServiceInstance serviceInstance : serviceInstances) {
|
||||
if ("true".equals(serviceInstance.getMetadata().getOrDefault("nacos.healthy", "true"))) {
|
||||
hostsWithWeight.add(new Pair<>(serviceInstance,
|
||||
Double.parseDouble(serviceInstance.getMetadata().getOrDefault("nacos.weight", "1"))));
|
||||
}
|
||||
}
|
||||
Chooser<String, ServiceInstance> vipChooser = new Chooser<>("www.taobao.com", hostsWithWeight);
|
||||
return vipChooser.randomWithWeight();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.pig4cloud.pigx.common.gateway.support;
|
||||
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/11/19
|
||||
* <p>
|
||||
* 动态路由检查检查
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DynamicRouteHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) {
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
if (redisTemplate.hasKey(CacheConstants.ROUTE_KEY)) {
|
||||
builder.up();
|
||||
}
|
||||
else {
|
||||
log.warn("动态路由监控检查失败,当前无路由配置");
|
||||
builder.down();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.support;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/11/5
|
||||
* <p>
|
||||
* 路由初始化事件
|
||||
*/
|
||||
public class DynamicRouteInitEvent extends ApplicationEvent {
|
||||
|
||||
public DynamicRouteInitEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.support;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.gateway.vo.RouteDefinitionVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/10/31
|
||||
* <p>
|
||||
* redis 保存路由信息,优先级比配置文件高
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisRouteDefinitionWriter implements RouteDefinitionRepository {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(Mono<RouteDefinition> route) {
|
||||
return route.flatMap(r -> {
|
||||
RouteDefinitionVo vo = new RouteDefinitionVo();
|
||||
BeanUtils.copyProperties(r, vo);
|
||||
log.info("保存路由信息{}", vo);
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, r.getId(), vo);
|
||||
redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "新增路由信息,网关缓存更新");
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(Mono<String> routeId) {
|
||||
routeId.subscribe(id -> {
|
||||
log.info("删除路由信息{}", id);
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.opsForHash().delete(CacheConstants.ROUTE_KEY, id);
|
||||
});
|
||||
redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "删除路由信息,网关缓存更新");
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态路由入口
|
||||
* <p>
|
||||
* 1. 先从内存中获取 2. 为空加载Redis中数据 3. 更新内存
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
List<RouteDefinitionVo> routeList = RouteCacheHolder.getRouteList();
|
||||
if (CollUtil.isNotEmpty(routeList)) {
|
||||
log.debug("内存 中路由定义条数: {}, {}", routeList.size(), routeList);
|
||||
return Flux.fromIterable(routeList);
|
||||
}
|
||||
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
|
||||
List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CacheConstants.ROUTE_KEY);
|
||||
log.info("redis 中路由定义条数: {}, {}", values.size(), values);
|
||||
|
||||
RouteCacheHolder.setRouteList(values);
|
||||
return Flux.fromIterable(values);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.support;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import com.pig4cloud.pigx.common.gateway.vo.RouteDefinitionVo;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019-08-16
|
||||
* <p>
|
||||
* 路由缓存工具类
|
||||
*/
|
||||
@UtilityClass
|
||||
public class RouteCacheHolder {
|
||||
|
||||
private Cache<String, RouteDefinitionVo> cache = CacheUtil.newLFUCache(200);
|
||||
|
||||
/**
|
||||
* 获取缓存的全部对象
|
||||
* @return routeList
|
||||
*/
|
||||
public List<RouteDefinitionVo> getRouteList() {
|
||||
List<RouteDefinitionVo> routeList = new ArrayList<>();
|
||||
cache.forEach(route -> routeList.add(route));
|
||||
return routeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存
|
||||
* @param routeList 缓存列表
|
||||
*/
|
||||
public void setRouteList(List<RouteDefinitionVo> routeList) {
|
||||
routeList.forEach(route -> cache.put(route.getId(), route));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
public void removeRouteList() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pigx.common.gateway.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/10/31
|
||||
* <p>
|
||||
* 扩展此类支持序列化a See RouteDefinition.class
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class RouteDefinitionVo extends RouteDefinition implements Serializable {
|
||||
|
||||
/**
|
||||
* 路由名称
|
||||
*/
|
||||
private String routeName;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user