mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
重构预发布/灰度发布环境选择
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
- 重构spring cloud gateway网关
|
- 重构spring cloud gateway网关
|
||||||
|
- 重构`预发布/灰度发布环境选择`
|
||||||
- zuul和gateway网关二合一
|
- zuul和gateway网关二合一
|
||||||
- 精简配置文件
|
- 精简配置文件
|
||||||
- 优化文档中心页面
|
- 优化文档中心页面
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
package com.gitee.sop.bridge;
|
package com.gitee.sop.bridge;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
|
@Configuration
|
||||||
@Import(AlipayGatewayConfiguration.class)
|
@Import(AlipayGatewayConfiguration.class)
|
||||||
public class SopGatewayAutoConfiguration {
|
public class SopGatewayAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,9 @@ spring.application.name=sop-gateway
|
|||||||
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||||
|
|
||||||
# 网关入口
|
# 网关入口
|
||||||
gateway.index-path=/
|
sop.gateway-index-path=/
|
||||||
|
|
||||||
|
spring.main.allow-bean-definition-overriding=true
|
||||||
|
|
||||||
# nacos cloud配置
|
# nacos cloud配置
|
||||||
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
||||||
|
@@ -2,13 +2,16 @@ package com.gitee.sop.bridge;
|
|||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
|
@Configuration
|
||||||
@EnableZuulProxy
|
@EnableZuulProxy
|
||||||
@Import(AlipayZuulConfiguration.class)
|
@Import(AlipayZuulConfiguration.class)
|
||||||
public class SopGatewayAutoConfiguration {
|
public class SopGatewayAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,9 @@ package com.gitee.sop.gatewaycommon.bean;
|
|||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
||||||
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.builder.IpGrayUserBuilder;
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||||
@@ -41,6 +44,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -53,6 +57,9 @@ public class ApiConfig {
|
|||||||
private static ApiConfig instance = new ApiConfig();
|
private static ApiConfig instance = new ApiConfig();
|
||||||
|
|
||||||
private ApiConfig() {
|
private ApiConfig() {
|
||||||
|
grayUserBuilders = new ArrayList<>(4);
|
||||||
|
grayUserBuilders.add(new AppIdGrayUserBuilder());
|
||||||
|
grayUserBuilders.add(new IpGrayUserBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,6 +200,13 @@ public class ApiConfig {
|
|||||||
|
|
||||||
private boolean useGateway;
|
private boolean useGateway;
|
||||||
|
|
||||||
|
private List<GrayUserBuilder> grayUserBuilders;
|
||||||
|
|
||||||
|
public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) {
|
||||||
|
grayUserBuilders.add(grayUserBuilder);
|
||||||
|
grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order));
|
||||||
|
}
|
||||||
|
|
||||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
public void addAppSecret(Map<String, String> appSecretPair) {
|
||||||
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
||||||
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface ApiParamAware<T> {
|
||||||
|
ApiParam getApiParam(T t);
|
||||||
|
}
|
@@ -54,4 +54,8 @@ public class SopConstants {
|
|||||||
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
|
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
|
||||||
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
|
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
|
||||||
|
|
||||||
|
public static final String METADATA_ENV_KEY = "env";
|
||||||
|
public static final String METADATA_ENV_PRE_VALUE = "pre";
|
||||||
|
public static final String METADATA_ENV_GRAY_VALUE = "gray";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,9 @@ import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFi
|
|||||||
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
|
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
|
||||||
@@ -17,6 +19,10 @@ import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||||
|
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||||
|
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
@@ -111,4 +117,28 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
GatewayForwardChooser gatewayForwardChooser() {
|
GatewayForwardChooser gatewayForwardChooser() {
|
||||||
return new GatewayForwardChooser();
|
return new GatewayForwardChooser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展默认的负载均衡选择,默认使用的是RibbonLoadBalancerClient。当配置了pre.domain时才生效
|
||||||
|
* @param clientFactory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
LoadBalancerClient sopLoadBalancerClient(SpringClientFactory clientFactory) {
|
||||||
|
return new SopLoadBalancerClient(clientFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展默认的负载均衡过滤器,默认是LoadBalancerClientFilter。当配置了pre.domain时才生效
|
||||||
|
* @param sopLoadBalancerClient SopLoadBalancerClient
|
||||||
|
* @param loadBalancerProperties loadBalancerProperties
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient sopLoadBalancerClient, LoadBalancerProperties loadBalancerProperties) {
|
||||||
|
return new SopLoadBalancerClientFilter(sopLoadBalancerClient, loadBalancerProperties);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -43,7 +43,7 @@ public class IndexFilter implements WebFilter {
|
|||||||
private static final String REST_PATH_PREFIX = "/rest";
|
private static final String REST_PATH_PREFIX = "/rest";
|
||||||
private static final String SOP_PATH_PREFIX = "/sop";
|
private static final String SOP_PATH_PREFIX = "/sop";
|
||||||
|
|
||||||
@Value("${gateway.index-path:/}")
|
@Value("${sop.gateway-index-path:/}")
|
||||||
private String indexPath;
|
private String indexPath;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.filter;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||||
|
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||||
|
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展负载均衡过滤器
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class SopLoadBalancerClientFilter extends LoadBalancerClientFilter {
|
||||||
|
public SopLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
|
||||||
|
super(loadBalancer, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ServiceInstance choose(ServerWebExchange exchange) {
|
||||||
|
if (loadBalancer instanceof SopLoadBalancerClient) {
|
||||||
|
SopLoadBalancerClient sopLoadBalancerClient = (SopLoadBalancerClient)loadBalancer;
|
||||||
|
return sopLoadBalancerClient.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), exchange);
|
||||||
|
} else {
|
||||||
|
return super.choose(exchange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class GatewayLoadBalanceServerChooser extends LoadBalanceServerChooser<ServerWebExchange, ServiceInstance> {
|
||||||
|
|
||||||
|
public GatewayLoadBalanceServerChooser(SpringClientFactory clientFactory) {
|
||||||
|
this.setClientFactory(clientFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost(ServerWebExchange exchange) {
|
||||||
|
return exchange.getRequest().getURI().getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||||
|
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||||
|
|
||||||
|
import com.netflix.loadbalancer.Server;
|
||||||
|
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class NacosServerIntrospector extends DefaultServerIntrospector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getMetadata(Server server) {
|
||||||
|
if (server instanceof NacosServer) {
|
||||||
|
NacosServer discoveryServer = (NacosServer)server;
|
||||||
|
return discoveryServer.getInstance().getMetadata();
|
||||||
|
} else {
|
||||||
|
return super.getMetadata(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,107 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.netflix.client.config.IClientConfig;
|
||||||
|
import com.netflix.loadbalancer.Server;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.RibbonUtils;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写负载均衡处理。
|
||||||
|
* 默认使用的是RibbonLoadBalancerClient类,详见org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements ServerChooserContext<ServerWebExchange> {
|
||||||
|
|
||||||
|
private final SpringClientFactory clientFactory;
|
||||||
|
private GatewayLoadBalanceServerChooser loadBalanceServerChooser;
|
||||||
|
|
||||||
|
public SopLoadBalancerClient(SpringClientFactory clientFactory) {
|
||||||
|
super(clientFactory);
|
||||||
|
this.clientFactory = clientFactory;
|
||||||
|
this.loadBalanceServerChooser = new GatewayLoadBalanceServerChooser(clientFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New: Select a server using a 'key'.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ServiceInstance choose(String serviceId, Object hint) {
|
||||||
|
return loadBalanceServerChooser.choose(
|
||||||
|
serviceId
|
||||||
|
, (ServerWebExchange) hint
|
||||||
|
, this.getLoadBalancer(serviceId)
|
||||||
|
, () -> super.choose(serviceId, hint)
|
||||||
|
, (servers) -> getRibbonServer(serviceId, servers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||||
|
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost(ServerWebExchange exchange) {
|
||||||
|
return exchange.getRequest().getURI().getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RibbonServer getRibbonServer(String serviceId, List<Server> servers) {
|
||||||
|
Server server = this.chooseRandomServer(servers);
|
||||||
|
if (server == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RibbonServer(
|
||||||
|
serviceId
|
||||||
|
, server
|
||||||
|
, isSecure(server, serviceId)
|
||||||
|
, serverIntrospector(serviceId).getMetadata(server)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机选取一台实例
|
||||||
|
*
|
||||||
|
* @param servers 服务列表
|
||||||
|
* @return 返回实例,没有返回null
|
||||||
|
*/
|
||||||
|
private Server chooseRandomServer(List<Server> servers) {
|
||||||
|
if (servers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int serverCount = servers.size();
|
||||||
|
// 随机选取一台实例
|
||||||
|
int index = chooseRandomInt(serverCount);
|
||||||
|
return servers.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int chooseRandomInt(int serverCount) {
|
||||||
|
return ThreadLocalRandom.current().nextInt(serverCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerIntrospector serverIntrospector(String serviceId) {
|
||||||
|
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
|
||||||
|
ServerIntrospector.class);
|
||||||
|
if (serverIntrospector == null) {
|
||||||
|
serverIntrospector = new DefaultServerIntrospector();
|
||||||
|
}
|
||||||
|
return serverIntrospector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSecure(Server server, String serviceId) {
|
||||||
|
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
|
||||||
|
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
|
||||||
|
return RibbonUtils.isSecure(config, serverIntrospector, server);
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@ public class GatewayForwardChooser extends BaseForwardChooser<ServerWebExchange>
|
|||||||
private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
|
private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ApiParam getApiParam(ServerWebExchange exchange) {
|
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||||
return ServerWebExchangeUtil.getApiParam(exchange);
|
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
|
|
||||||
import com.netflix.loadbalancer.Server;
|
|
||||||
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
|
||||||
*
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
public class EnvironmentServerChooser extends BaseServerChooser {
|
|
||||||
|
|
||||||
private static final String MEDATA_KEY_ENV = "env";
|
|
||||||
private static final String ENV_PRE_VALUE = "pre";
|
|
||||||
private static final String ENV_GRAY_VALUE = "gray";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预发布机器域名
|
|
||||||
*/
|
|
||||||
private static final String PRE_DOMAIN = "localhost";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isPreServer(Server server) {
|
|
||||||
String env = getEnvValue(server);
|
|
||||||
return ENV_PRE_VALUE.equals(env);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isGrayServer(Server server) {
|
|
||||||
String env = getEnvValue(server);
|
|
||||||
return ENV_GRAY_VALUE.equals(env);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getEnvValue(Server server) {
|
|
||||||
// eureka存储的metadata
|
|
||||||
Map<String, String> metadata = getMetada(server);
|
|
||||||
return metadata.get(MEDATA_KEY_ENV);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Map<String, String> getMetada(Server server) {
|
|
||||||
return ((NacosServer) server).getMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过判断hostname来确定是否是预发布请求,可修改此方法实现自己想要的
|
|
||||||
*
|
|
||||||
* @param request request
|
|
||||||
* @return 返回true:可以进入到预发环境
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean canVisitPre(Server server, HttpServletRequest request) {
|
|
||||||
String serverName = request.getServerName();
|
|
||||||
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN);
|
|
||||||
return domain.equals(serverName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
|
||||||
|
|
||||||
import com.netflix.loadbalancer.Server;
|
|
||||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
public class EurekaEnvironmentServerChooser extends EnvironmentServerChooser {
|
|
||||||
@Override
|
|
||||||
protected Map<String, String> getMetada(Server server) {
|
|
||||||
return ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class LoadBalanceConfig {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,127 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||||
|
import com.netflix.loadbalancer.ILoadBalancer;
|
||||||
|
import com.netflix.loadbalancer.Server;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预发布、灰度发布服务器选择
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public abstract class LoadBalanceServerChooser<T, R> implements ServerChooserContext<T> {
|
||||||
|
|
||||||
|
private SpringClientFactory clientFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择服务器
|
||||||
|
*
|
||||||
|
* @param serviceId serviceId,仅gateway网关有作用
|
||||||
|
* @param exchange 请求上下文
|
||||||
|
* @param loadBalancer loadBalancer
|
||||||
|
* @param superChooser 父类默认的选择
|
||||||
|
* @param serverChooserFunction 执行选择操作
|
||||||
|
* @return 返回服务器实例
|
||||||
|
*/
|
||||||
|
public R choose(
|
||||||
|
String serviceId
|
||||||
|
, T exchange
|
||||||
|
, ILoadBalancer loadBalancer
|
||||||
|
, Supplier<R> superChooser
|
||||||
|
, Function<List<Server>, R> serverChooserFunction) {
|
||||||
|
// 获取所有服务实例
|
||||||
|
List<Server> servers = loadBalancer.getReachableServers();
|
||||||
|
|
||||||
|
// 存放预发服务器
|
||||||
|
List<Server> preServers = new ArrayList<>(4);
|
||||||
|
// 存放灰度发布服务器
|
||||||
|
List<Server> grayServers = new ArrayList<>(4);
|
||||||
|
// 存放非预发服务器
|
||||||
|
List<Server> notPreServers = new ArrayList<>(4);
|
||||||
|
|
||||||
|
for (Server server : servers) {
|
||||||
|
// 获取实例metadata
|
||||||
|
Map<String, String> metadata = getMetadata(serviceId, server);
|
||||||
|
// 是否开启了预发模式
|
||||||
|
if (this.isPreServer(metadata)) {
|
||||||
|
preServers.add(server);
|
||||||
|
} else if (this.isGrayServer(metadata)) {
|
||||||
|
grayServers.add(server);
|
||||||
|
} else {
|
||||||
|
notPreServers.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notPreServers.addAll(grayServers);
|
||||||
|
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
|
||||||
|
if (preServers.isEmpty() && grayServers.isEmpty()) {
|
||||||
|
return superChooser.get();
|
||||||
|
}
|
||||||
|
// 如果是从预发布域名访问过来,则认为是预发布请求
|
||||||
|
if (this.isRequestFromPreDomain(exchange)) {
|
||||||
|
return serverChooserFunction.apply(preServers);
|
||||||
|
}
|
||||||
|
// 如果是灰度请求
|
||||||
|
if (this.isRequestGrayServer(exchange)) {
|
||||||
|
return serverChooserFunction.apply(grayServers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
|
||||||
|
return serverChooserFunction.apply(notPreServers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, String> getMetadata(String serviceId, Server server) {
|
||||||
|
return serverIntrospector(serviceId).getMetadata(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SpringClientFactory getSpringClientFactory() {
|
||||||
|
if (clientFactory == null) {
|
||||||
|
clientFactory = SpringContext.getBean(SpringClientFactory.class);
|
||||||
|
}
|
||||||
|
return clientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientFactory(SpringClientFactory clientFactory) {
|
||||||
|
this.clientFactory = clientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerIntrospector serverIntrospector(String serviceId) {
|
||||||
|
ServerIntrospector serverIntrospector = getSpringClientFactory().getInstance(serviceId,
|
||||||
|
ServerIntrospector.class);
|
||||||
|
if (serverIntrospector == null) {
|
||||||
|
serverIntrospector = new DefaultServerIntrospector();
|
||||||
|
}
|
||||||
|
return serverIntrospector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是预发布服务器
|
||||||
|
*
|
||||||
|
* @param metadata metadata
|
||||||
|
* @return true:是
|
||||||
|
*/
|
||||||
|
private boolean isPreServer(Map<String, String> metadata) {
|
||||||
|
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_PRE_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是灰度发布服务器
|
||||||
|
*
|
||||||
|
* @param metadata metadata
|
||||||
|
* @return true:是
|
||||||
|
*/
|
||||||
|
private boolean isGrayServer(Map<String, String> metadata) {
|
||||||
|
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_GRAY_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import org.apache.commons.lang.ArrayUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface ServerChooserContext<T> extends ApiParamAware<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过判断hostname来确定是否是预发布请求
|
||||||
|
*
|
||||||
|
* @param t t
|
||||||
|
* @return 返回true:可以进入到预发环境
|
||||||
|
*/
|
||||||
|
default boolean isRequestFromPreDomain(T t) {
|
||||||
|
String domain = EnvironmentKeys.PRE_DOMAIN.getValue();
|
||||||
|
if (StringUtils.isEmpty(domain)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] domains = domain.split("\\,");
|
||||||
|
return ArrayUtils.contains(domains, getHost(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isRequestGrayServer(T t) {
|
||||||
|
ApiParam apiParam = getApiParam(t);
|
||||||
|
return apiParam.isGrayRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getHost(T t);
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class AppIdGrayUserBuilder implements GrayUserBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildGrayUserKey(ApiParam apiParam) {
|
||||||
|
return apiParam.fetchAppKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int order() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public interface GrayUserBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取灰度用户key
|
||||||
|
*
|
||||||
|
* @param apiParam apiParam
|
||||||
|
* @return 返回用户key
|
||||||
|
*/
|
||||||
|
String buildGrayUserKey(ApiParam apiParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级,数字小优先
|
||||||
|
*
|
||||||
|
* @return 返回数字
|
||||||
|
*/
|
||||||
|
int order();
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class IpGrayUserBuilder implements GrayUserBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildGrayUserKey(ApiParam apiParam) {
|
||||||
|
return apiParam.fetchIp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int order() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,15 +4,16 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
|||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector;
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
|
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
|
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
|
||||||
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
|
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
||||||
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
||||||
@@ -23,6 +24,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
|||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||||
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
|
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||||
|
import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
@@ -159,6 +162,26 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
|||||||
return createCorsFilter();
|
return createCorsFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责获取nacos实例的metadata
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
|
||||||
|
ServerIntrospector nacosServerIntrospector() {
|
||||||
|
return new NacosServerIntrospector();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责获取eureka实例的metadata
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
|
||||||
|
ServerIntrospector eurekaServerIntrospector() {
|
||||||
|
return new EurekaServerIntrospector();
|
||||||
|
}
|
||||||
|
|
||||||
protected CorsFilter createCorsFilter() {
|
protected CorsFilter createCorsFilter() {
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
this.registerCorsConfiguration(source);
|
this.registerCorsConfiguration(source);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.gitee.sop.gatewaycommon.manager;
|
package com.gitee.sop.gatewaycommon.manager;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.EnvironmentServerChooser;
|
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.EnvironmentServerChooser;
|
||||||
|
|
||||||
public enum EnvironmentKeys {
|
public enum EnvironmentKeys {
|
||||||
SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"),
|
SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"),
|
||||||
@@ -9,7 +9,7 @@ public enum EnvironmentKeys {
|
|||||||
*/
|
*/
|
||||||
SPRING_APPLICATION_NAME("spring.application.name"),
|
SPRING_APPLICATION_NAME("spring.application.name"),
|
||||||
/**
|
/**
|
||||||
* 指定负载均衡规则类,默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
|
* 指定负载均衡规则类
|
||||||
*/
|
*/
|
||||||
ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()),
|
ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()),
|
||||||
|
|
||||||
@@ -35,8 +35,11 @@ public enum EnvironmentKeys {
|
|||||||
/**
|
/**
|
||||||
* 排除其它微服务,正则形式,多个用英文逗号隔开
|
* 排除其它微服务,正则形式,多个用英文逗号隔开
|
||||||
*/
|
*/
|
||||||
SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex")
|
SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex"),
|
||||||
;
|
/**
|
||||||
|
* 预发布域名
|
||||||
|
*/
|
||||||
|
PRE_DOMAIN("pre.domain");
|
||||||
|
|
||||||
private String key;
|
private String key;
|
||||||
private String defaultValue;
|
private String defaultValue;
|
||||||
@@ -57,4 +60,8 @@ public enum EnvironmentKeys {
|
|||||||
public String getValue() {
|
public String getValue() {
|
||||||
return EnvironmentContext.getValue(key, defaultValue);
|
return EnvironmentContext.getValue(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getValue(String defaultValue) {
|
||||||
|
return EnvironmentContext.getValue(key, defaultValue);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -31,6 +31,8 @@ public class ApiParam extends JSONObject implements Param {
|
|||||||
|
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
|
private boolean isGrayRequest;
|
||||||
|
|
||||||
private transient UploadContext uploadContext;
|
private transient UploadContext uploadContext;
|
||||||
|
|
||||||
public void fitNameVersion() {
|
public void fitNameVersion() {
|
||||||
@@ -248,4 +250,12 @@ public class ApiParam extends JSONObject implements Param {
|
|||||||
public String fetchIp() {
|
public String fetchIp() {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGrayRequest() {
|
||||||
|
return isGrayRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGrayRequest(boolean grayRequest) {
|
||||||
|
isGrayRequest = grayRequest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,28 @@
|
|||||||
package com.gitee.sop.gatewaycommon.route;
|
package com.gitee.sop.gatewaycommon.route;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
public abstract class BaseForwardChooser<T> implements ForwardChooser<T>, ApiParamAware<T> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private EnvGrayManager envGrayManager;
|
private EnvGrayManager envGrayManager;
|
||||||
|
|
||||||
protected abstract ApiParam getApiParam(T t);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ForwardInfo getForwardInfo(T t) {
|
public ForwardInfo getForwardInfo(T t) {
|
||||||
ApiParam apiParam = getApiParam(t);
|
ApiParam apiParam = getApiParam(t);
|
||||||
@@ -30,10 +35,11 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
|||||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||||
String grayVersion = envGrayManager.getVersion(serviceId, nameVersion);
|
String grayVersion = envGrayManager.getVersion(serviceId, nameVersion);
|
||||||
// 如果是灰度环境
|
// 如果是灰度环境
|
||||||
if (grayVersion != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) {
|
if (grayVersion != null && isGrayUser(serviceId, apiParam)) {
|
||||||
String newNameVersion = apiParam.fetchName() + grayVersion;
|
String newNameVersion = apiParam.fetchName() + grayVersion;
|
||||||
TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion);
|
TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion);
|
||||||
if (targetRouteDest != null) {
|
if (targetRouteDest != null) {
|
||||||
|
apiParam.setGrayRequest(true);
|
||||||
if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) {
|
if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) {
|
||||||
version = grayVersion;
|
version = grayVersion;
|
||||||
} else {
|
} else {
|
||||||
@@ -46,4 +52,15 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
|||||||
return new ForwardInfo(path, version);
|
return new ForwardInfo(path, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isGrayUser(String serviceId, ApiParam apiParam) {
|
||||||
|
List<GrayUserBuilder> grayUserBuilders = ApiConfig.getInstance().getGrayUserBuilders();
|
||||||
|
for (GrayUserBuilder grayUserBuilder : grayUserBuilders) {
|
||||||
|
String userKey = grayUserBuilder.buildGrayUserKey(apiParam);
|
||||||
|
if (envGrayManager.containsKey(serviceId, userKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package com.gitee.sop.gatewaycommon.route;
|
package com.gitee.sop.gatewaycommon.route;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.EurekaEnvironmentServerChooser;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
|
||||||
import com.netflix.appinfo.InstanceInfo;
|
import com.netflix.appinfo.InstanceInfo;
|
||||||
import com.netflix.discovery.shared.Application;
|
import com.netflix.discovery.shared.Application;
|
||||||
import com.netflix.discovery.shared.Applications;
|
import com.netflix.discovery.shared.Applications;
|
||||||
@@ -25,10 +23,6 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public class EurekaRegistryListener extends BaseRegistryListener {
|
public class EurekaRegistryListener extends BaseRegistryListener {
|
||||||
|
|
||||||
static {
|
|
||||||
System.setProperty(EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getKey(), EurekaEnvironmentServerChooser.class.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<ServiceHolder> cacheServices = new HashSet<>();
|
private Set<ServiceHolder> cacheServices = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
|
||||||
import com.netflix.loadbalancer.ILoadBalancer;
|
|
||||||
import com.netflix.loadbalancer.Server;
|
|
||||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
|
||||||
import com.netflix.zuul.context.RequestContext;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务实例选择器
|
|
||||||
*
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public abstract class BaseServerChooser extends ZoneAvoidanceRule {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否是预发布服务器
|
|
||||||
* @param server 服务器
|
|
||||||
* @return true:是
|
|
||||||
*/
|
|
||||||
protected abstract boolean isPreServer(Server server);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否是灰度发布服务器
|
|
||||||
* @param server 服务器
|
|
||||||
* @return true:是
|
|
||||||
*/
|
|
||||||
protected abstract boolean isGrayServer(Server server);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 能否访问预发布
|
|
||||||
* @param server 预发布服务器
|
|
||||||
* @param request request
|
|
||||||
* @return true:能
|
|
||||||
*/
|
|
||||||
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Server choose(Object key) {
|
|
||||||
ILoadBalancer lb = getLoadBalancer();
|
|
||||||
// 获取服务实例列表
|
|
||||||
List<Server> allServers = lb.getAllServers();
|
|
||||||
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
|
|
||||||
|
|
||||||
List<Server> preServers = allServers.stream()
|
|
||||||
.filter(this::isPreServer)
|
|
||||||
.filter(server -> canVisitPre(server, request))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!preServers.isEmpty()) {
|
|
||||||
return this.doChoose(preServers, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Server> grayServers = allServers.stream()
|
|
||||||
.filter(this::isGrayServer)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (!grayServers.isEmpty()) {
|
|
||||||
return doChoose(grayServers, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.choose(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Server doChoose(List<Server> servers, Object key) {
|
|
||||||
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
|
|
||||||
if (server.isPresent()) {
|
|
||||||
return server.get();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -0,0 +1,55 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.netflix.loadbalancer.Server;
|
||||||
|
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||||
|
import com.netflix.zuul.context.RequestContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class EnvironmentServerChooser extends ZoneAvoidanceRule implements ServerChooserContext<HttpServletRequest> {
|
||||||
|
|
||||||
|
private ZuulLoadBalanceServerChooser loadBalanceServerChooser = new ZuulLoadBalanceServerChooser();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost(HttpServletRequest request) {
|
||||||
|
return request.getServerName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiParam getApiParam(HttpServletRequest request) {
|
||||||
|
return ZuulContext.getApiParam();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Server choose(Object key) {
|
||||||
|
return loadBalanceServerChooser.choose(
|
||||||
|
String.valueOf(key)
|
||||||
|
, RequestContext.getCurrentContext().getRequest()
|
||||||
|
, getLoadBalancer()
|
||||||
|
, () -> super.choose(key)
|
||||||
|
, (servers) -> this.doChoose(servers, key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Server doChoose(List<Server> servers, Object key) {
|
||||||
|
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
|
||||||
|
if (server.isPresent()) {
|
||||||
|
return server.get();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||||
|
import com.netflix.loadbalancer.Server;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public class ZuulLoadBalanceServerChooser extends LoadBalanceServerChooser<HttpServletRequest, Server> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost(HttpServletRequest request) {
|
||||||
|
return request.getServerName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiParam getApiParam(HttpServletRequest request) {
|
||||||
|
return ZuulContext.getApiParam();
|
||||||
|
}
|
||||||
|
}
|
@@ -11,7 +11,7 @@ import com.netflix.zuul.context.RequestContext;
|
|||||||
public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> {
|
public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ApiParam getApiParam(RequestContext requestContext) {
|
public ApiParam getApiParam(RequestContext requestContext) {
|
||||||
return ZuulContext.getApiParam();
|
return ZuulContext.getApiParam();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,7 +19,6 @@ import java.lang.reflect.Method;
|
|||||||
*/
|
*/
|
||||||
public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered {
|
public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered {
|
||||||
|
|
||||||
private static StringValueResolver stringValueResolver = new ApiMappingStringValueResolver();
|
|
||||||
private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC();
|
private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Reference in New Issue
Block a user