✨ Introducing new features. 支持全局的服务灰度路由

This commit is contained in:
冷冷 committed 2020-01-12 15:57:04 +08:00
1 parent 006cbbb953
commit 93fdad4dd0
36 files changed
+566 -449

No files matched your search

+5
View File
@@ -68,6 +68,11 @@
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-sentinel</artifactId>
</dependency>
<!--路由控制-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-gray</artifactId>
</dependency>
<!--JDBC相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
+5
View File
@@ -42,6 +42,11 @@
<artifactId>pigx-common-gateway</artifactId>
<version>${pigx.version}</version>
</dependency>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-gray</artifactId>
<version>${pigx.version}</version>
</dependency>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-datasource</artifactId>
@@ -51,8 +51,8 @@ import java.nio.charset.StandardCharsets;
@Slf4j
@UtilityClass
public class WebUtils extends org.springframework.web.util.WebUtils {
private final String BASIC_ = "Basic ";
private final String UNKNOWN = "unknown";
private final String BASIC_ = "Basic ";
private final String UNKNOWN = "unknown";
/**
* 判断是否ajax请求
@@ -122,7 +122,11 @@ public class WebUtils extends org.springframework.web.util.WebUtils {
* @return {HttpServletRequest}
*/
public HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
} catch (IllegalStateException e) {
return null;
}
}
/**
+4
View File
@@ -50,5 +50,9 @@
<artifactId>spring-cloud-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,55 @@
/*
* 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.rule.GrayLoadBalancer;
import com.pig4cloud.pigx.common.gateway.rule.VersionGrayLoadBalancer;
import com.pig4cloud.pigx.common.gateway.filter.GrayReactiveLoadBalancerClientFilter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
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
@EnableConfigurationProperties(LoadBalancerProperties.class)
@ConditionalOnProperty(value = "gray.rule.enabled", havingValue = "true")
@AutoConfigureBefore(GatewayReactiveLoadBalancerClientAutoConfiguration.class)
public class GrayLoadBalancerClientConfiguration {
@Bean
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(GrayLoadBalancer grayLoadBalancer,
LoadBalancerProperties properties) {
return new GrayReactiveLoadBalancerClientFilter(properties, grayLoadBalancer);
}
@Bean
public GrayLoadBalancer grayLoadBalancer(DiscoveryClient discoveryClient) {
return new VersionGrayLoadBalancer(discoveryClient);
}
}
@@ -1,76 +0,0 @@
/*
* 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.alibaba.cloud.nacos.ribbon.NacosServer;
import com.pig4cloud.pigx.common.gateway.filter.GrayLoadBalancerClientFilter;
import com.pig4cloud.pigx.common.gateway.rule.AbstractDiscoveryEnabledRule;
import com.pig4cloud.pigx.common.gateway.rule.MetadataAwareRule;
import com.pig4cloud.pigx.common.gateway.support.PigxRibbonRuleProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.RibbonClientConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* Mica ribbon rule auto configuration.
*
* @author L.cm
* @link https://github.com/lets-mica/mica
*/
@Configuration
@RequiredArgsConstructor
@ConditionalOnClass(NacosServer.class)
@AutoConfigureBefore(RibbonClientConfiguration.class)
@EnableConfigurationProperties(PigxRibbonRuleProperties.class)
@ConditionalOnProperty(value = "ribbon.rule.enabled", matchIfMissing = true)
public class PigxRibbonRuleAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AbstractDiscoveryEnabledRule metadataAwareRule() {
return new MetadataAwareRule();
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(SpringClientFactory springClientFactory) {
return new RibbonLoadBalancerClient(springClientFactory);
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClientFilter.class)
public LoadBalancerClientFilter loadBalancerClientFilter(RibbonLoadBalancerClient client
, LoadBalancerProperties properties) {
return new GrayLoadBalancerClientFilter(client, properties);
}
}
@@ -1,49 +0,0 @@
/*
* 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 org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
/**
* @author Spencer Gibb
* @author Tim Ysewyn
*/
public class GrayLoadBalancerClientFilter extends LoadBalancerClientFilter {
private final RibbonLoadBalancerClient loadBalancer;
public GrayLoadBalancerClientFilter(RibbonLoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
this.loadBalancer = loadBalancer;
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), headers);
}
}
@@ -0,0 +1,108 @@
/*
* 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.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
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 LoadBalancerProperties properties;
private GrayLoadBalancer grayLoadBalancer;
public GrayReactiveLoadBalancerClientFilter(LoadBalancerProperties properties, GrayLoadBalancer grayLoadBalancer) {
super(null, properties);
this.properties = properties;
this.grayLoadBalancer = grayLoadBalancer;
}
@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(properties.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));
}
}
@@ -1,51 +0,0 @@
/*
* 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.predicate;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.netflix.loadbalancer.AbstractServerPredicate;
import com.netflix.loadbalancer.PredicateKey;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
/**
* 过滤服务
*
* @author L.cm
*/
public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate {
/**
* {@inheritDoc}
*/
@Override
public boolean apply(@Nullable PredicateKey input) {
return input != null
&& input.getServer() instanceof NacosServer
&& apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey());
}
/**
* Returns whether the specific {@link NacosServer} matches this predicate.
*
* @param server the discovered server
* @param headers 请求头
* @return whether the server matches the predicate
*/
abstract boolean apply(NacosServer server, HttpHeaders headers);
}
@@ -1,67 +0,0 @@
/*
* 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.predicate;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.gateway.support.PigxRibbonRuleProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import java.util.Map;
/**
* 基于 Metadata version 的服务筛选
*
* @author L.cm
* @author lengleng
*/
@Slf4j
public class GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate {
@Override
protected boolean apply(NacosServer server, HttpHeaders headers) {
PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);
if (!ribbonProperties.isGrayEnabled()) {
log.debug("gray closed,GrayMetadataAwarePredicate return true");
return true;
}
final Map<String, String> metadata = server.getMetadata();
String version = metadata.get(CommonConstants.VERSION);
// 判断Nacos服务是否有版本标签
if (StrUtil.isBlank(version)) {
log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true");
return true;
}
// 判断请求中是否有版本
String target = headers.getFirst(CommonConstants.VERSION);
if (StrUtil.isBlank(target)) {
log.debug("request headers version is blank,GrayMetadataAwarePredicate return true");
return true;
}
log.debug("请求版本:{} ,当前服务版本:{}", target, version);
return target.equals(version);
}
}
@@ -1,76 +0,0 @@
/*
* 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 com.google.common.base.Optional;
import com.netflix.loadbalancer.*;
import com.pig4cloud.pigx.common.gateway.predicate.AbstractDiscoveryEnabledPredicate;
import org.springframework.util.Assert;
import java.util.List;
/**
* ribbon 路由规则
*
* @author L.cm
*/
public abstract class AbstractDiscoveryEnabledRule extends PredicateBasedRule {
private final CompositePredicate predicate;
public AbstractDiscoveryEnabledRule(AbstractDiscoveryEnabledPredicate discoveryEnabledPredicate) {
Assert.notNull(discoveryEnabledPredicate, "Parameter 'discoveryEnabledPredicate' can't be null");
this.predicate = createCompositePredicate(discoveryEnabledPredicate, new AvailabilityPredicate(this, null));
}
/**
* {@inheritDoc}
*/
@Override
public AbstractServerPredicate getPredicate() {
return predicate;
}
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
List<Server> allServers = lb.getAllServers();
// 过滤服务列表
List<Server> serverList = filterServers(allServers);
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(serverList, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
/**
* 过滤服务
*
* @param serverList 服务列表
* @return 服务列表
*/
public abstract List<Server> filterServers(List<Server> serverList);
private CompositePredicate createCompositePredicate(AbstractDiscoveryEnabledPredicate discoveryEnabledPredicate, AvailabilityPredicate availabilityPredicate) {
return CompositePredicate.withPredicates(discoveryEnabledPredicate, availabilityPredicate)
.build();
}
}
@@ -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);
}
@@ -1,91 +0,0 @@
/*
* 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.util.ObjectUtil;
import cn.hutool.system.HostInfo;
import cn.hutool.system.SystemUtil;
import com.netflix.loadbalancer.Server;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.gateway.predicate.AbstractDiscoveryEnabledPredicate;
import com.pig4cloud.pigx.common.gateway.predicate.GrayMetadataAwarePredicate;
import com.pig4cloud.pigx.common.gateway.support.PigxRibbonRuleProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.PatternMatchUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* ribbon 路由规则器
*
* @author dream.lu
*/
@Slf4j
public class MetadataAwareRule extends AbstractDiscoveryEnabledRule {
public MetadataAwareRule() {
this(new GrayMetadataAwarePredicate());
}
public MetadataAwareRule(AbstractDiscoveryEnabledPredicate predicate) {
super(predicate);
}
@Override
public List<Server> filterServers(List<Server> serverList) {
PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);
List<String> priorIpPattern = ribbonProperties.getPriorIpPattern();
if (ribbonProperties.isGrayEnabled()) {
return serverList;
}
// 1. 查找是否有本机 ip
HostInfo hostInfo = SystemUtil.getHostInfo();
String hostIp = hostInfo.getAddress();
// 优先的 ip 规则
boolean hasPriorIpPattern = !priorIpPattern.isEmpty();
String[] priorIpPatterns = priorIpPattern.toArray(new String[0]);
List<Server> priorServerList = new ArrayList<>();
for (Server server : serverList) {
String host = server.getHost();
// 2. 优先本地 ip 的服务
if (!hasPriorIpPattern && ObjectUtil.equal(hostIp, host)) {
log.debug("{} 不存在优先配置,本地路由条件", hostIp);
return Collections.singletonList(server);
}
// 3. 优先的 ip 服务
if (hasPriorIpPattern && PatternMatchUtils.simpleMatch(priorIpPatterns, host)) {
log.debug("{} 存在优先配置,ribbon 强制路由", priorIpPatterns);
priorServerList.add(server);
}
}
// 4. 如果优先的有数据直接返回
if (!priorServerList.isEmpty()) {
return priorServerList;
}
return Collections.unmodifiableList(serverList);
}
}
@@ -0,0 +1,81 @@
/*
* 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.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import lombok.AllArgsConstructor;
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.List;
import java.util.Map;
/**
* @author lengleng
* @date 2020/1/12
* <p>
* 基于客户端版本号灰度路由
*/
@Slf4j
@AllArgsConstructor
public class VersionGrayLoadBalancer implements GrayLoadBalancer {
private DiscoveryClient discoveryClient;
/**
* 根据serviceId 筛选可用服务
*
* @param serviceId 服务ID
* @param request 当前请求
* @return
*/
@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)) {
return instances.get(RandomUtil.randomInt(instances.size()));
}
// 遍历可以实例元数据,若匹配则返回此实例
for (ServiceInstance instance : instances) {
Map<String, String> metadata = instance.getMetadata();
String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);
if (reqVersion.equalsIgnoreCase(targetVersion)) {
log.debug("gray requst match success :{} {}", reqVersion, instance);
return instance;
}
}
return instances.get(RandomUtil.randomInt(instances.size()));
}
}
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~
~ 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)
~
-->
<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>3.6.0</version>
</parent>
<artifactId>pigx-common-gray</artifactId>
<packaging>jar</packaging>
<description>pigx 灰度路由</description>
<dependencies>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
</dependencies>
</project>
@@ -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.gray;
import com.pig4cloud.pigx.common.gray.feign.GrayFeignRequestInterceptor;
import com.pig4cloud.pigx.common.gray.rule.GrayRibbonLoadBalancerRule;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lengleng
* @date 2020/1/12
*/
@Configuration
@ConditionalOnProperty(value = "gray.rule.enabled", havingValue = "true")
public class GrayRibbonLoadBalancerConfiguration {
@Bean
public GrayRibbonLoadBalancerRule ribbonLoadBalancerRule() {
return new GrayRibbonLoadBalancerRule();
}
@Bean
public RequestInterceptor pigxFeignTenantInterceptor() {
return new GrayFeignRequestInterceptor();
}
}
@@ -0,0 +1,50 @@
/*
* 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.gray.feign;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
/**
* @author lengleng
* @date 2020/1/12
* <p>
* feign 请求VERSION 传递
*/
@Slf4j
public class GrayFeignRequestInterceptor implements RequestInterceptor {
/**
* Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
*
* @param template
*/
@Override
public void apply(RequestTemplate template) {
String reqVersion = WebUtils.getRequest() != null
? WebUtils.getRequest().getHeader(CommonConstants.VERSION) : null;
if (StrUtil.isNotBlank(reqVersion)) {
log.debug("feign gray add header version :{}", reqVersion);
template.header(CommonConstants.VERSION, reqVersion);
}
}
}
@@ -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.gray.rule;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* @author lengleng
* @date 2020/1/12
*/
@Slf4j
public class GrayRibbonLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
List<Server> reachableServers = lb.getReachableServers();
//注册中心无可用实例 抛出异常
if (CollUtil.isEmpty(reachableServers)) {
log.warn("No instance available for {}", key);
return null;
}
// 获取请求version,无则随机返回可用实例
String reqVersion = WebUtils.getRequest() != null
? WebUtils.getRequest().getHeader(CommonConstants.VERSION) : null;
if (StrUtil.isBlank(reqVersion)) {
return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
}
// 遍历可以实例元数据,若匹配则返回此实例
for (Server server : reachableServers) {
NacosServer nacosServer = (NacosServer) server;
Map<String, String> metadata = nacosServer.getMetadata();
String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);
if (reqVersion.equalsIgnoreCase(targetVersion)) {
log.debug("gray requst match success :{} {}", reqVersion, nacosServer);
return nacosServer;
}
}
return reachableServers.get(RandomUtil.randomInt(reachableServers.size()));
}
}
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pigx.common.gray.GrayRibbonLoadBalancerConfiguration
@@ -15,14 +15,14 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job;
package com.pig4cloud.pigx.common.gray;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.pig4cloud.pigx.common.job.jobinit.DataflowJobInitialization;
import com.pig4cloud.pigx.common.job.jobinit.ScriptJobInitialization;
import com.pig4cloud.pigx.common.job.jobinit.SimpleJobInitialization;
import com.pig4cloud.pigx.common.job.properties.ElasticJobProperties;
import com.pig4cloud.pigx.common.gray.jobinit.DataflowJobInitialization;
import com.pig4cloud.pigx.common.gray.jobinit.ScriptJobInitialization;
import com.pig4cloud.pigx.common.gray.jobinit.SimpleJobInitialization;
import com.pig4cloud.pigx.common.gray.properties.ElasticJobProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -15,9 +15,9 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job;
package com.pig4cloud.pigx.common.gray;
import com.pig4cloud.pigx.common.job.properties.XxlJobProperties;
import com.pig4cloud.pigx.common.gray.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -32,7 +32,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@EnableAutoConfiguration
@ComponentScan("com.pig4cloud.pigx.common.job.properties")
@ComponentScan("com.pig4cloud.pigx.common.gray.properties")
public class XxlJobAutoConfiguration {
@Bean(initMethod = "start", destroyMethod = "destroy")
@@ -15,9 +15,9 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.annotation;
package com.pig4cloud.pigx.common.gray.annotation;
import com.pig4cloud.pigx.common.job.ElasticJobAutoConfiguration;
import com.pig4cloud.pigx.common.gray.ElasticJobAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@@ -15,9 +15,9 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.annotation;
package com.pig4cloud.pigx.common.gray.annotation;
import com.pig4cloud.pigx.common.job.XxlJobAutoConfiguration;
import com.pig4cloud.pigx.common.gray.XxlJobAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@@ -15,7 +15,7 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.jobinit;
package com.pig4cloud.pigx.common.gray.jobinit;
import com.dangdang.ddframe.job.api.ElasticJob;
import com.dangdang.ddframe.job.api.JobType;
@@ -30,7 +30,7 @@ import com.dangdang.ddframe.job.lite.api.listener.ElasticJobListener;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.pig4cloud.pigx.common.job.properties.ElasticJobProperties;
import com.pig4cloud.pigx.common.gray.properties.ElasticJobProperties;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -46,7 +46,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.pig4cloud.pigx.common.job.ElasticJobAutoConfiguration.DEFAULT_REGISTRY_CENTER_NAME;
import static com.pig4cloud.pigx.common.gray.ElasticJobAutoConfiguration.DEFAULT_REGISTRY_CENTER_NAME;
/**
@@ -15,12 +15,12 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.jobinit;
package com.pig4cloud.pigx.common.gray.jobinit;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.JobTypeConfiguration;
import com.dangdang.ddframe.job.config.dataflow.DataflowJobConfiguration;
import com.pig4cloud.pigx.common.job.properties.ElasticJobProperties;
import com.pig4cloud.pigx.common.gray.properties.ElasticJobProperties;
import java.util.Map;
@@ -15,12 +15,12 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.jobinit;
package com.pig4cloud.pigx.common.gray.jobinit;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.JobTypeConfiguration;
import com.dangdang.ddframe.job.config.script.ScriptJobConfiguration;
import com.pig4cloud.pigx.common.job.properties.ElasticJobProperties;
import com.pig4cloud.pigx.common.gray.properties.ElasticJobProperties;
import java.util.Map;
@@ -15,12 +15,12 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.jobinit;
package com.pig4cloud.pigx.common.gray.jobinit;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.JobTypeConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.pig4cloud.pigx.common.job.properties.ElasticJobProperties;
import com.pig4cloud.pigx.common.gray.properties.ElasticJobProperties;
import java.util.Map;
@@ -15,7 +15,7 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.properties;
package com.pig4cloud.pigx.common.gray.properties;
import com.dangdang.ddframe.job.api.JobType;
import lombok.Getter;
@@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.pig4cloud.pigx.common.job.ElasticJobAutoConfiguration.DEFAULT_REGISTRY_CENTER_NAME;
import static com.pig4cloud.pigx.common.gray.ElasticJobAutoConfiguration.DEFAULT_REGISTRY_CENTER_NAME;
/**
@@ -15,7 +15,7 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.properties;
package com.pig4cloud.pigx.common.gray.properties;
import lombok.Data;
@@ -15,7 +15,7 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.properties;
package com.pig4cloud.pigx.common.gray.properties;
import lombok.Data;
@@ -15,7 +15,7 @@
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.job.properties;
package com.pig4cloud.pigx.common.gray.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -22,14 +22,12 @@ import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -62,14 +60,8 @@ public class PigxUserDetailsServiceImpl implements PigxUserDetailsService {
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String username) {
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
if (cache != null && cache.get(username) != null) {
return (PigxUser) cache.get(username).get();
}
R<UserInfo> result = remoteUserService.info(username, SecurityConstants.FROM_IN);
UserDetails userDetails = getUserDetails(result);
cache.put(username, userDetails);
return userDetails;
}
+1
View File
@@ -38,6 +38,7 @@
<module>pigx-common-data</module>
<module>pigx-common-datasource</module>
<module>pigx-common-gateway</module>
<module>pigx-common-gray</module>
<module>pigx-common-job</module>
<module>pigx-common-log</module>
<module>pigx-common-minio</module>
+4
View File
@@ -47,6 +47,10 @@
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-gray</artifactId>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
@@ -5,6 +5,9 @@ spring:
application:
name: @artifactId@
cloud:
loadbalancer:
ribbon:
enabled: false
nacos:
discovery:
server-addr: ${NACOS-HOST:pigx-register}:${NACOS-PORT:8848}
@@ -13,4 +16,4 @@ spring:
file-extension: yml
shared-dataids: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
profiles:
active: @profiles.active@
active: @profiles.active@
@@ -17,7 +17,7 @@
package com.pig4cloud.pigx.daemon.elastic;
import com.pig4cloud.pigx.common.job.annotation.EnablePigxXxlJob;
import com.pig4cloud.pigx.common.gray.annotation.EnablePigxXxlJob;
import com.pig4cloud.pigx.common.security.annotation.EnablePigxFeignClients;
import com.pig4cloud.pigx.common.security.annotation.EnablePigxResourceServer;
import com.pig4cloud.pigx.common.swagger.annotation.EnablePigxSwagger2;