mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
重构预发布/灰度发布环境选择
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## 3.0.0
|
||||
|
||||
- 重构spring cloud gateway网关
|
||||
- 重构`预发布/灰度发布环境选择`
|
||||
- zuul和gateway网关二合一
|
||||
- 精简配置文件
|
||||
- 优化文档中心页面
|
||||
|
@@ -1,12 +1,14 @@
|
||||
package com.gitee.sop.bridge;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||
* @author tanghc
|
||||
*/
|
||||
@Configuration
|
||||
@Import(AlipayGatewayConfiguration.class)
|
||||
public class SopGatewayAutoConfiguration {
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@ spring.application.name=sop-gateway
|
||||
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
||||
# 网关入口
|
||||
gateway.index-path=/
|
||||
sop.gateway-index-path=/
|
||||
|
||||
spring.main.allow-bean-definition-overriding=true
|
||||
|
||||
# nacos cloud配置
|
||||
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 org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||
* @author tanghc
|
||||
*/
|
||||
@Configuration
|
||||
@EnableZuulProxy
|
||||
@Import(AlipayZuulConfiguration.class)
|
||||
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.limit.DefaultLimitManager;
|
||||
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.DefaultIPBlacklistManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
@@ -41,6 +44,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -53,6 +57,9 @@ public class ApiConfig {
|
||||
private static ApiConfig instance = new 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 List<GrayUserBuilder> grayUserBuilders;
|
||||
|
||||
public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) {
|
||||
grayUserBuilders.add(grayUserBuilder);
|
||||
grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order));
|
||||
}
|
||||
|
||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
||||
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
||||
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_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.LimitFilter;
|
||||
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.loadbalancer.SopLoadBalancerClient;
|
||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
||||
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 org.springframework.beans.factory.ObjectProvider;
|
||||
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.Primary;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -111,4 +117,28 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
GatewayForwardChooser 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 SOP_PATH_PREFIX = "/sop";
|
||||
|
||||
@Value("${gateway.index-path:/}")
|
||||
@Value("${sop.gateway-index-path:/}")
|
||||
private String indexPath;
|
||||
|
||||
@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";
|
||||
|
||||
@Override
|
||||
protected ApiParam getApiParam(ServerWebExchange exchange) {
|
||||
public ApiParam getApiParam(ServerWebExchange 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.BeanInitializer;
|
||||
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.loadbalancer.SopPropertiesFactory;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
||||
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.NacosRegistryListener;
|
||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
||||
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.session.SessionManager;
|
||||
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.cloud.client.discovery.event.HeartbeatEvent;
|
||||
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.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
@@ -159,6 +162,26 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
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() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
this.registerCorsConfiguration(source);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.EnvironmentServerChooser;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.EnvironmentServerChooser;
|
||||
|
||||
public enum EnvironmentKeys {
|
||||
SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"),
|
||||
@@ -9,7 +9,7 @@ public enum EnvironmentKeys {
|
||||
*/
|
||||
SPRING_APPLICATION_NAME("spring.application.name"),
|
||||
/**
|
||||
* 指定负载均衡规则类,默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
|
||||
* 指定负载均衡规则类
|
||||
*/
|
||||
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 defaultValue;
|
||||
@@ -57,4 +60,8 @@ public enum EnvironmentKeys {
|
||||
public String getValue() {
|
||||
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 boolean isGrayRequest;
|
||||
|
||||
private transient UploadContext uploadContext;
|
||||
|
||||
public void fitNameVersion() {
|
||||
@@ -248,4 +250,12 @@ public class ApiParam extends JSONObject implements Param {
|
||||
public String fetchIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public boolean isGrayRequest() {
|
||||
return isGrayRequest;
|
||||
}
|
||||
|
||||
public void setGrayRequest(boolean grayRequest) {
|
||||
isGrayRequest = grayRequest;
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,28 @@
|
||||
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.ApiParamAware;
|
||||
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.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
||||
public abstract class BaseForwardChooser<T> implements ForwardChooser<T>, ApiParamAware<T> {
|
||||
|
||||
@Autowired
|
||||
private EnvGrayManager envGrayManager;
|
||||
|
||||
protected abstract ApiParam getApiParam(T t);
|
||||
|
||||
@Override
|
||||
public ForwardInfo getForwardInfo(T t) {
|
||||
ApiParam apiParam = getApiParam(t);
|
||||
@@ -30,10 +35,11 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||
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;
|
||||
TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion);
|
||||
if (targetRouteDest != null) {
|
||||
apiParam.setGrayRequest(true);
|
||||
if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) {
|
||||
version = grayVersion;
|
||||
} else {
|
||||
@@ -46,4 +52,15 @@ public abstract class BaseForwardChooser<T> implements ForwardChooser<T> {
|
||||
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;
|
||||
|
||||
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.discovery.shared.Application;
|
||||
import com.netflix.discovery.shared.Applications;
|
||||
@@ -25,10 +23,6 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class EurekaRegistryListener extends BaseRegistryListener {
|
||||
|
||||
static {
|
||||
System.setProperty(EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getKey(), EurekaEnvironmentServerChooser.class.getName());
|
||||
}
|
||||
|
||||
private Set<ServiceHolder> cacheServices = new HashSet<>();
|
||||
|
||||
@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> {
|
||||
|
||||
@Override
|
||||
protected ApiParam getApiParam(RequestContext requestContext) {
|
||||
public ApiParam getApiParam(RequestContext requestContext) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,6 @@ import java.lang.reflect.Method;
|
||||
*/
|
||||
public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered {
|
||||
|
||||
private static StringValueResolver stringValueResolver = new ApiMappingStringValueResolver();
|
||||
private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC();
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user