This commit is contained in:
tanghc
2020-01-21 14:03:27 +08:00
138 changed files with 2969 additions and 1529 deletions

View File

@@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>2.5.12-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
@@ -22,7 +22,7 @@
<!-- Test -->
<junit.version>4.11</junit.version>
<fastjson.version>1.2.60</fastjson.version>
<fastjson.version>1.2.62</fastjson.version>
<commons-io.version>2.5</commons-io.version>
<commons-fileupload.version>1.3.3</commons-fileupload.version>
<commons-collection.version>3.2.2</commons-collection.version>
@@ -36,7 +36,9 @@
<modules>
<module>sop-gateway-common</module>
<module>sop-service-common</module>
</modules>
<module>sop-bridge-zuul</module>
<module>sop-bridge-gateway</module>
</modules>
<dependencyManagement>
<dependencies>
@@ -117,6 +119,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- provided -->
<dependency>
<groupId>org.projectlombok</groupId>
@@ -132,6 +140,14 @@
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<build>
<plugins>
<!-- 打包时跳过测试 -->

View File

@@ -1,5 +1,7 @@
# sop-common
- sop-bridge-gateway网关桥接器供sop-gateway依赖依赖后使用spring cloud gateway网关
- sop-bridge-zuul网关桥接器供sop-gateway依赖依赖后使用spring cloud zuul网关
- sop-gateway-common提供给网关使用
- sop-service-common提供给微服务端使用需要打成jar

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sop-common</artifactId>
<groupId>com.gitee.sop</groupId>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>3.0.0-SNAPSHOT</version>
<artifactId>sop-bridge-gateway</artifactId>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.bridge;
import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration;
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
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)
@AutoConfigureBefore(RibbonAutoConfiguration.class)
public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration {
}

View File

@@ -0,0 +1,39 @@
# 固定不变,不能改
spring.application.name=sop-gateway
# 不用改,如果要改,请全局替换修改
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
# 网关入口
sop.gateway-index-path=/
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
# https://blog.csdn.net/qq_36872046/article/details/81058045
# 路由转发超时时间毫秒默认值1000详见RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
# 如果微服务端 处理时间过长会导致ribbon read超时解决办法将这个值调大一点
ribbon.ReadTimeout=2000
# 设置为true默认false则所有请求都重试默认只支持get请求重试
# 请谨慎设置因为post请求大多都是写入请求如果要支持重试确保服务的幂等性
ribbon.OkToRetryOnAllOperations=false
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
spring.cloud.gateway.discovery.locator.enabled=true
# 不用改
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
# 文件上传配置
spring.servlet.multipart.enabled=true
# 这里设置大一点没关系真实大小由upload.max-file-size控制
spring.servlet.multipart.max-file-size=50MB
# 允许上传文件大小不能超过这个值单位B,KB,MB
upload.max-file-size=2MB

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sop-common</artifactId>
<groupId>com.gitee.sop</groupId>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>3.0.0-SNAPSHOT</version>
<artifactId>sop-bridge-zuul</artifactId>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
package com.gitee.sop.bridge;
import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration;
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
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)
// 在ErrorMvcAutoConfiguration之前加载
// 如果不加会出现basicErrorController和zuulErrorController冲突
// zuulErrorController是SOP中的提前加载后basicErrorController就不会加载
@AutoConfigureBefore({ErrorMvcAutoConfiguration.class})
public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration {
}

View File

@@ -0,0 +1,44 @@
# 固定不变,不能改
spring.application.name=sop-gateway
# 入口地址,不用改,默认是/zuul
zuul.servlet-path=/api
# 禁用默认的过滤器,不能删,不用改
zuul.FormBodyWrapperFilter.pre.disable=true
zuul.Servlet30WrapperFilter.pre.disable=true
# 不用改,如果要改,请全局替换修改
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
# zuul优化配置
zuul.host.max-per-route-connections=1000
zuul.host.max-total-connections=1000
zuul.semaphore.max-semaphores=1000
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
# https://blog.csdn.net/qq_36872046/article/details/81058045
# 路由转发超时时间毫秒默认值1000详见RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
# 如果微服务端 处理时间过长会导致ribbon read超时解决办法将这个值调大一点
ribbon.ReadTimeout=5000
# 设置为true默认false则所有请求都重试默认只支持get请求重试
# 请谨慎设置因为post请求大多都是写入请求如果要支持重试确保服务的幂等性
ribbon.OkToRetryOnAllOperations=false
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
# 不用改
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
# 文件上传配置
spring.servlet.multipart.enabled=true
# 这里设置大一点没关系真实大小由upload.max-file-size控制
spring.servlet.multipart.max-file-size=50MB
# 允许上传文件大小不能超过这个值单位B,KB,MB
upload.max-file-size=2MB

View File

@@ -5,11 +5,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>2.5.12-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-gateway-common</artifactId>
<version>2.5.12-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-gateway-common</name>

View File

@@ -1,10 +1,11 @@
package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder;
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
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;
@@ -17,12 +18,12 @@ import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
import com.gitee.sop.gatewaycommon.result.ResultAppender;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway;
import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul;
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
@@ -34,15 +35,14 @@ import com.gitee.sop.gatewaycommon.validate.Encrypter;
import com.gitee.sop.gatewaycommon.validate.Signer;
import com.gitee.sop.gatewaycommon.validate.TokenValidator;
import com.gitee.sop.gatewaycommon.validate.Validator;
import com.gitee.sop.gatewaycommon.zuul.configuration.ZuulErrorController;
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController;
import com.gitee.sop.gatewaycommon.zuul.param.ZuulParamBuilder;
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
import com.netflix.zuul.context.RequestContext;
import lombok.Data;
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;
@@ -55,17 +55,20 @@ public class ApiConfig {
private static ApiConfig instance = new ApiConfig();
private ApiConfig() {
grayUserBuilders = new ArrayList<>(4);
grayUserBuilders.add(new AppIdGrayUserBuilder());
grayUserBuilders.add(new IpGrayUserBuilder());
}
/**
* gateway合并结果处理
*/
private ResultExecutor<ServerWebExchange, GatewayResult> gatewayResultExecutor = new GatewayResultExecutor();
private ResultExecutorForGateway gatewayResultExecutor = new GatewayResultExecutor();
/**
* zuul合并结果处理
*/
private ResultExecutor<RequestContext, String> zuulResultExecutor = new ZuulResultExecutor();
private ResultExecutorForZuul zuulResultExecutor = new ZuulResultExecutor();
/**
* isv管理
@@ -82,15 +85,10 @@ public class ApiConfig {
*/
private Signer signer = new ApiSigner();
/**
* 参数解析gateway
*/
private ParamBuilder<ServerWebExchange> gatewayParamBuilder = new GatewayParamBuilder();
/**
* 参数解析zuul
*/
private ParamBuilder<RequestContext> zuulParamBuilder = new ZuulParamBuilder();
private ZuulParamBuilder zuulParamBuilder = new ZuulParamBuilder();
/**
* 验证
@@ -200,6 +198,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()));

View File

@@ -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);
}

View File

@@ -0,0 +1,64 @@
package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.result.ApiResult;
import com.gitee.sop.gatewaycommon.result.JsonResult;
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collection;
/**
* @author tanghc
*/
public abstract class BaseErrorLogController<T> {
TaobaoSigner signer = new TaobaoSigner();
@Value("${sop.secret}")
private String secret;
protected abstract ApiParam getApiParam(T t);
@GetMapping("/sop/listErrors")
public ApiResult listErrors(T request) {
try {
this.check(request);
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
JsonResult apiResult = new JsonResult();
apiResult.setData(allErrors);
return apiResult;
} catch (Exception e) {
ApiResult apiResult = new ApiResult();
apiResult.setCode("505050");
apiResult.setMsg(e.getMessage());
return apiResult;
}
}
@GetMapping("/sop/clearErrors")
public ApiResult clearErrors(T request) {
try {
this.check(request);
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
serviceErrorManager.clear();
return new ApiResult();
} catch (Exception e) {
ApiResult apiResult = new ApiResult();
apiResult.setCode("505050");
apiResult.setMsg(e.getMessage());
return apiResult;
}
}
protected void check(T request) {
ApiParam apiParam = getApiParam(request);
boolean right = signer.checkSign(apiParam, secret);
if (!right) {
throw new RuntimeException("签名校验失败");
}
}
}

View File

@@ -75,4 +75,9 @@ public class RouteDefinition {
* 是否需要token
*/
private int needToken;
/**
* 是否是兼容模式,即使用了@ApiAbility注解
*/
private int compatibleMode;
}

View File

@@ -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";
}

View File

@@ -0,0 +1,48 @@
package com.gitee.sop.gatewaycommon.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
@ConfigurationProperties(prefix = "sop.api-config")
public class ApiConfigProperties {
private List<String> i18nModules = new ArrayList<>();
/**
* 忽略验证设置true则所有接口不会进行签名校验
*/
private boolean ignoreValidate;
/**
* 是否对结果进行合并。<br>
* 默认情况下是否合并结果由微服务端决定,一旦指定该值,则由该值决定,不管微服务端如何设置。
*/
private Boolean mergeResult;
/**
* 请求超时时间默认5分钟即允许在5分钟内重复请求
*/
private int timeoutSeconds = 300;
/**
* 是否开启限流功能
*/
private boolean openLimit = true;
/**
* 显示返回sign
*/
private boolean showReturnSign = true;
/**
* 保存错误信息容器的容量
*/
private int storeErrorCapacity = 20;
}

View File

@@ -0,0 +1,29 @@
package com.gitee.sop.gatewaycommon.config;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import javax.annotation.PostConstruct;
/**
* @author tanghc
*/
@Slf4j
@EnableConfigurationProperties(ApiConfigProperties.class)
public class BaseGatewayAutoConfiguration {
@Autowired
private ApiConfigProperties apiConfigProperties;
@PostConstruct
public void after() {
log.info("网关基本配置:{}", JSON.toJSONString(apiConfigProperties));
ApiConfig apiConfig = ApiConfig.getInstance();
BeanUtils.copyProperties(apiConfigProperties, apiConfig);
}
}

View File

@@ -0,0 +1,39 @@
package com.gitee.sop.gatewaycommon.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.util.Properties;
/**
* 自定义环境处理在运行SpringApplication之前加载任意配置文件到Environment环境中
*/
public class SopGatewayEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final Properties properties = new Properties();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Resource resource = new ClassPathResource("sop-bridge.properties");
// 加载成PropertySource对象并添加到Environment环境中
environment.getPropertySources().addLast(loadProfiles(resource));
}
private PropertySource<?> loadProfiles(Resource resource) {
if (resource == null || !resource.exists()) {
throw new IllegalArgumentException("资源" + resource + "不存在");
}
try {
properties.load(resource.getInputStream());
return new PropertiesPropertySource(resource.getFilename(), properties);
} catch (IOException ex) {
throw new IllegalStateException("加载配置文件失败" + resource, ex);
}
}
}

View File

@@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.message.Error;
import com.gitee.sop.gatewaycommon.result.ApiResult;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul;
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
import com.netflix.zuul.context.RequestContext;
@@ -13,7 +13,7 @@ import java.util.Optional;
/**
* @author tanghc
*/
public class EasyopenResultExecutor implements ResultExecutor<RequestContext, String> {
public class EasyopenResultExecutor implements ResultExecutorForZuul {
boolean onlyReturnData;

View File

@@ -1,45 +1,167 @@
package com.gitee.sop.gatewaycommon.gateway;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage;
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_FOR_MAP;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY;
/**
* @author tanghc
*/
@Slf4j
public class ServerWebExchangeUtil {
private static final String THROWABLE_KEY = "sop.throwable";
private static final String UNKNOWN_PATH = "/sop/unknown";
private static final String REST_PATH = "/rest";
private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
private static final List<HttpMessageReader<?>> messageReaders;
static {
messageReaders = HandlerStrategies.withDefaults().messageReaders();
}
/**
* 重定向
*
* @param exchange exchange
* @param forwardInfo forwardInfo
* @return 返回新的ServerWebExchange配合chain.filter(newExchange);使用
*/
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, ForwardInfo forwardInfo) {
ServerHttpRequest.Builder builder = exchange.getRequest()
.mutate();
ServerHttpRequest newRequest = builder
.header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion())
.path(forwardInfo.getPath()).build();
return exchange.mutate().request(newRequest).build();
}
/**
* 构建一个接受请求体的request
*
* @param exchange exchange
* @return 返回ServerRequest
*/
public static ServerRequest createReadBodyRequest(ServerWebExchange exchange) {
return ServerRequest.create(exchange, messageReaders);
}
public static ServerWebExchange getRestfulExchange(ServerWebExchange exchange, String path) {
int index = path.indexOf(REST_PATH);
// 取"/rest"的后面部分
String newPath = path.substring(index + REST_PATH.length());
ApiParam apiParam = new ApiParam();
apiParam.setName(newPath);
apiParam.setVersion("");
setApiParam(exchange, apiParam);
return getForwardExchange(exchange, newPath);
}
/**
* 重定向
*
* @param exchange exchange
* @param forwardPath 重定向path
* @return 返回新的ServerWebExchange配合chain.filter(newExchange);使用
*/
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) {
ServerHttpRequest newRequest = exchange.getRequest()
.mutate()
.path(forwardPath).build();
return exchange.mutate().request(newRequest).build();
}
public static Mono<Void> forwardUnknown(ServerWebExchange exchange, WebFilterChain chain) {
// 非法访问
ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, UNKNOWN_PATH);
return chain.filter(newExchange);
}
public static ApiParam getApiParam(ServerWebExchange exchange, String body) {
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
ApiParam apiParam = new ApiParam();
String ip = Optional.ofNullable(exchange.getRequest().getRemoteAddress())
.map(address -> address.getAddress().getHostAddress())
.orElse("");
apiParam.setIp(ip);
Map<String, ?> params = null;
String contentTypeStr = contentType.toString().toLowerCase();
// 如果是json方式提交
if (StringUtils.containsAny(contentTypeStr, "json", "text")) {
JSONObject jsonObject = JSON.parseObject(body);
apiParam.putAll(jsonObject);
} else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) {
// 如果是文件上传请求
HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, body);
setFileUploadRequest(exchange, fileUploadRequest);
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest);
params = uploadInfo.getUploadParams();
apiParam.setUploadContext(uploadInfo.getUploadContext());
} else {
// APPLICATION_FORM_URLENCODED请求
params = RequestUtil.parseQueryToMap(body);
}
if (params != null) {
apiParam.putAll(params);
}
setApiParam(exchange, apiParam);
return apiParam;
}
public static ApiParam getApiParam(ServerWebExchange exchange, Map<String, String> params) {
ApiParam apiParam = new ApiParam();
apiParam.putAll(params);
setApiParam(exchange, apiParam);
return apiParam;
}
public static void setThrowable(ServerWebExchange exchange, Throwable throwable) {
exchange.getAttributes().put(THROWABLE_KEY, throwable);
}
public static Throwable getThrowable(ServerWebExchange exchange) {
return (Throwable)exchange.getAttribute(THROWABLE_KEY);
}
/**
* 获取请求参数
*
@@ -60,62 +182,6 @@ public class ServerWebExchangeUtil {
exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam);
}
/**
* 获取Spring Cloud Gateway请求的原始参数。前提是要使用ReadBodyRoutePredicateFactory
*
* @param exchange ServerWebExchange
* @return 没有参数返回null
* @see com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory
*/
public static ApiParam getRequestParams(ServerWebExchange exchange) {
ApiParam apiParamExist = exchange.getAttribute(CACHE_REQUEST_BODY_FOR_MAP);
if (apiParamExist != null) {
return apiParamExist;
}
ApiParam apiParam = new ApiParam();
Map<String, ?> params = null;
if (exchange.getRequest().getMethod() == HttpMethod.GET) {
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
params = buildParams(queryParams);
} else {
String cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
if (cachedBody != null) {
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
String contentTypeStr = contentType == null ? "" : contentType.toString().toLowerCase();
// 如果是json方式提交
if (StringUtils.containsAny(contentTypeStr, "json", "text")) {
params = JSON.parseObject(cachedBody);
} else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) {
// 如果是文件上传请求
HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, cachedBody);
setFileUploadRequest(exchange, fileUploadRequest);
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest);
params = uploadInfo.getUploadParams();
apiParam.setUploadContext(uploadInfo.getUploadContext());
} else {
params = RequestUtil.parseQueryToMap(cachedBody);
}
}
}
if (params != null) {
apiParam.putAll(params);
exchange.getAttributes().put(CACHE_REQUEST_BODY_FOR_MAP, apiParam);
}
return apiParam;
}
public static Map<String, String> buildParams(MultiValueMap<String, String> queryParams) {
if (queryParams == null || queryParams.size() == 0) {
return null;
}
Map<String, String> params = new HashMap<>(queryParams.size());
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
params.put(entry.getKey(), entry.getValue().get(0));
}
return params;
}
/**
* 添加header
*

View File

@@ -1,47 +1,35 @@
package com.gitee.sop.gatewaycommon.gateway.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.gateway.filter.EnvGrayFilter;
import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController;
import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController;
import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController;
import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.LoadBalancerClientExtFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.ValidateFilter;
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;
import com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory;
import com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory;
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Collections;
import java.util.List;
@@ -56,8 +44,25 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
ApiConfig.getInstance().setUseGateway(true);
}
@Value("${sop.restful.path:/rest}")
private String restPath;
@Bean
public IndexFilter indexFilter() {
return new IndexFilter();
}
@Bean
public GatewayController gatewayErrorController() {
return new GatewayController();
}
@Bean
public ConfigChannelController configChannelController() {
return new ConfigChannelController();
}
@Bean
public ErrorLogController errorLogController() {
return new ErrorLogController();
}
/**
* 自定义异常处理[@@]注册Bean时依赖的Bean会从容器中直接获取所以直接注入即可
@@ -68,7 +73,7 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
@@ -78,12 +83,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
return jsonExceptionHandler;
}
@Bean
@ConditionalOnMissingBean
ParamBuilder<ServerWebExchange> paramBuilder() {
return ApiConfig.getInstance().getGatewayParamBuilder();
}
/**
* 处理返回结果
*/
@@ -92,24 +91,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
return new GatewayModifyResponseGatewayFilter();
}
/**
* 读取post请求参数
*/
@Bean
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
return new ReadBodyRoutePredicateFactory();
}
@Bean
NameVersionRoutePredicateFactory paramRoutePredicateFactory() {
return new NameVersionRoutePredicateFactory();
}
@Bean
ValidateFilter validateFilter() {
return new ValidateFilter();
}
@Bean
ParameterFormatterFilter parameterFormatterFilter() {
return new ParameterFormatterFilter();
@@ -120,11 +101,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
return new LimitFilter();
}
@Bean
LoadBalancerClientExtFilter loadBalancerClientExtFilter() {
return new LoadBalancerClientExtFilter();
}
@Bean
GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) {
return new GatewayRouteCache(gatewayRouteRepository);
@@ -137,47 +113,30 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
return gatewayRouteRepository;
}
@Bean
EnvGrayFilter envGrayFilter() {
return new EnvGrayFilter();
GatewayForwardChooser gatewayForwardChooser() {
return new GatewayForwardChooser();
}
/**
* 307 Temporary Redirect临时重定向:
* <p>
* 在这种情况下请求应该与另一个URI重复但后续的请求应仍使用原始的URI。
* 与302相反当重新发出原始请求时不允许更改请求方法。 例如应该使用另一个POST请求来重复POST请求
* <p>
* 308 Permanent Redirect (永久重定向):
* <p>
* 请求和所有将来的请求应该使用另一个URI重复。
* 307和308重复302和301的行为但不允许HTTP方法更改。 例如,将表单提交给永久重定向的资源可能会顺利进行。
* <p>
* https://www.cnblogs.com/wuguanglin/p/redirect.html
*
* 扩展默认的负载均衡选择默认使用的是RibbonLoadBalancerClient
* @param clientFactory
* @return
*/
@Bean
@ConditionalOnProperty(value = "sop.restful.enable", havingValue = "true")
RouterFunction<ServerResponse> routerFunction() {
RequestPredicate requestPredicate = RequestPredicates.all()
.and(RequestPredicates.path(restPath + "/**"));
return RouterFunctions.route(requestPredicate, (serverRequest) -> {
String path = serverRequest.path();
int index = path.indexOf(restPath);
// 取/rest的后面部分
String servletPath = path.substring(index + restPath.length());
String query = serverRequest.uri().getQuery();
String appendQuery = ParamNames.API_NAME + "=" + servletPath + "&" + ParamNames.VERSION_NAME + "=";
if (StringUtils.isBlank(query)) {
query = appendQuery;
} else {
query += '&' + appendQuery;
}
return ServerResponse
.temporaryRedirect(URI.create("/?" + query))
.build();
});
LoadBalancerClient loadBalancerClient(SpringClientFactory clientFactory) {
return new SopLoadBalancerClient(clientFactory);
}
/**
* 扩展默认的负载均衡过滤器默认是LoadBalancerClientFilter
* @param sopLoadBalancerClient SopLoadBalancerClient
* @param loadBalancerProperties loadBalancerProperties
* @return
*/
@Bean
LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient sopLoadBalancerClient, LoadBalancerProperties loadBalancerProperties) {
return new SopLoadBalancerClientFilter(sopLoadBalancerClient, loadBalancerProperties);
}
}

View File

@@ -0,0 +1,75 @@
package com.gitee.sop.gatewaycommon.gateway.controller;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.GatewayPushDTO;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Slf4j
@RestController
public class ConfigChannelController {
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16);
static {
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_GRAY, EnvGrayManager.class);
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_IP_BLACKLIST, IPBlacklistManager.class);
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ISV, IsvManager.class);
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_PERMISSION, IsvRoutePermissionManager.class);
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_LIMIT_CONFIG, LimitConfigManager.class);
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
}
@Value("${sop.secret}")
private String secret;
@PostMapping("/sop/configChannelMsg")
public Mono<String> configChannel(ServerWebExchange exchange) {
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange);
// 读取请求体中的内容
return serverRequest.bodyToMono(String.class)
.flatMap(requestJson -> {
String sign = exchange.getRequest().getHeaders().getFirst("sign");
try {
// 签名验证
RequestUtil.checkResponseBody(requestJson, sign, secret);
} catch (Exception e) {
log.error("configChannelMsg错误", e);
return Mono.just(e.getMessage());
}
GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class);
ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO);
channelMsgProcessor.process(gatewayPushDTO.getChannelMsg());
return Mono.just("ok");
});
}
private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) {
String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId();
Class<? extends ChannelMsgProcessor> aClass = processorMap.get(key);
return SpringContext.getBean(aClass);
}
}

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.gatewaycommon.gateway.controller;
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import java.util.Map;
/**
* @author tanghc
*/
@RestController
public class ErrorLogController extends BaseErrorLogController<ServerWebExchange> {
@Override
protected ApiParam getApiParam(ServerWebExchange request) {
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap();
return ApiParam.build(params);
}
}

View File

@@ -0,0 +1,42 @@
package com.gitee.sop.gatewaycommon.gateway.controller;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author tanghc
*/
@RestController
public class GatewayController {
/**
* 处理签名错误返回
*
* @param exchange exchange
* @return 返回最终结果
*/
@RequestMapping("/sop/validateError")
public Mono<String> validateError(ServerWebExchange exchange) {
Throwable throwable = ServerWebExchangeUtil.getThrowable(exchange);
// 合并微服务传递过来的结果,变成最终结果
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable);
return Mono.just(gatewayResult);
}
@RequestMapping("/sop/unknown")
public Mono<String> unknown(ServerWebExchange exchange) {
ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
String gatewayResult = resultExecutor.buildErrorResult(exchange, exception);
return Mono.just(gatewayResult);
}
}

View File

@@ -15,7 +15,10 @@ import reactor.core.publisher.Mono;
/**
* @author tanghc
* @deprecated
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser
*/
@Deprecated
public class EnvGrayFilter implements GlobalFilter, Ordered {
@Autowired
@@ -27,7 +30,7 @@ public class EnvGrayFilter implements GlobalFilter, Ordered {
String nameVersion = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
if (targetRoute == null) {
return null;
return chain.filter(exchange);
}
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
// 如果服务在灰度阶段,返回一个灰度版本号

View File

@@ -0,0 +1,156 @@
package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
import com.gitee.sop.gatewaycommon.validate.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Objects;
/**
* 入口
*
* @author tanghc
*/
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class IndexFilter implements WebFilter {
private static final String REST_PATH_PREFIX = "/rest";
private static final String SOP_PATH_PREFIX = "/sop";
@Value("${sop.gateway-index-path:/}")
private String indexPath;
@Autowired
private Validator validator;
@Autowired
private GatewayForwardChooser gatewayForwardChooser;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// SOP路径直接放行
if (path.startsWith(SOP_PATH_PREFIX)) {
return chain.filter(exchange);
}
// 如果是restful请求直接转发
if (path.startsWith(REST_PATH_PREFIX)) {
String sopRestfulEnableValue = EnvironmentKeys.SOP_RESTFUL_ENABLE.getValue();
if (!Objects.equals("true", sopRestfulEnableValue)) {
log.error("尝试调用restful请求但sop.restful.enable未开启");
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
}
ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path);
return chain.filter(newExchange);
}
if (Objects.equals(path, indexPath)) {
if (request.getMethod() == HttpMethod.POST) {
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange);
// 读取请求体中的内容
Mono<?> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body -> {
// 构建ApiParam
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, body);
// 签名验证
doValidate(exchange, apiParam);
return Mono.just(body);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, (Class) String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// the new content type will be computed by bodyInserter
// and then set in the request decorator
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
ServerWebExchange newExchange = exchange.mutate().request(decorator).build();
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(newExchange, forwardInfo);
return chain.filter(forwardExchange);
}));
} else {
URI uri = exchange.getRequest().getURI();
// 原始参数
String originalQuery = uri.getRawQuery();
// 构建ApiParam
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, originalQuery);
// 签名验证
doValidate(exchange, apiParam);
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo);
return chain.filter(forwardExchange);
}
} else {
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
}
}
private void doValidate(ServerWebExchange exchange, ApiParam apiParam) {
try {
validator.validate(apiParam);
} catch (ApiException e) {
log.error("验证失败ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage());
ServerWebExchangeUtil.setThrowable(exchange, e);
}
}
private ServerHttpRequestDecorator decorate(
ServerWebExchange exchange
, HttpHeaders headers
, CachedBodyOutputMessage outputMessage
) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
}

View File

@@ -1,58 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.util.RouteUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/**
* 在LoadBalancerClientFilter后面处理从Route中找到具体的path然后插入到uri的path中
*
* @author tanghc
*/
@Slf4j
public class LoadBalancerClientExtFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
String path = this.findPath(exchange, route);
if (StringUtils.hasLength(path)) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(url);
uriComponentsBuilder.path(path);
URI requestUrl = uriComponentsBuilder.build(true).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
}
return chain.filter(exchange);
}
protected String findPath(ServerWebExchange exchange, Route route) {
String path = exchange.getAttribute(SopConstants.REDIRECT_PATH_KEY);
if (path != null) {
return path;
}
URI routeUri = route.getUri();
String uriStr = routeUri.toString();
return RouteUtil.findPath(uriStr);
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,48 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.validate.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author tanghc
*/
@Slf4j
public class ValidateFilter implements GlobalFilter, Ordered {
@Autowired
private ParamBuilder<ServerWebExchange> paramBuilder;
@Autowired
private Validator validator;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 解析参数
ApiParam param = paramBuilder.build(exchange);
ServerWebExchangeUtil.setApiParam(exchange, param);
// 验证操作,这里有负责验证签名参数
try {
validator.validate(param);
} catch (ApiException e) {
log.error("验证失败params:{}", param.toJSONString(), e);
throw e;
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 最优先执行
return Orders.VALIDATE_FILTER_ORDER;
}
}

View File

@@ -2,15 +2,15 @@ package com.gitee.sop.gatewaycommon.gateway.handler;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
@@ -33,24 +33,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ResultExecutor<ServerWebExchange, GatewayResult> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
GatewayResult errorResult = resultExecutor.buildErrorResult(exchange, ex);
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
// 错误记录
log.error("gateway网关报错params:{}, errorMsg:{}", apiParam, ex.getMessage(), ex);
// 参考AbstractErrorWebExceptionHandler
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
return RouterFunctions.route(RequestPredicates.all(), (serverRequest) -> this.renderErrorResponse(errorResult)).route(newRequest)
.switchIfEmpty(Mono.error(ex))
.flatMap((handler) -> handler.handle(newRequest))
.flatMap((response) -> write(exchange, response));
}
/**
* MessageReader
@@ -67,6 +49,25 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
*/
private List<ViewResolver> viewResolvers = Collections.emptyList();
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
String errorResult = resultExecutor.buildErrorResult(exchange, ex);
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
// 错误记录
log.error("gateway网关报错params:{}, errorMsg:{}", apiParam, ex.getMessage(), ex);
// 参考AbstractErrorWebExceptionHandler
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
return RouterFunctions.route(RequestPredicates.all(), (serverRequest) -> this.renderErrorResponse(errorResult)).route(newRequest)
.switchIfEmpty(Mono.error(ex))
.flatMap((handler) -> handler.handle(newRequest))
.flatMap((response) -> write(exchange, response));
}
/**
* 参考AbstractErrorWebExceptionHandler
*
@@ -103,11 +104,11 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
* @param result 返回结果
* @return 返回mono
*/
protected Mono<ServerResponse> renderErrorResponse(GatewayResult result) {
protected Mono<ServerResponse> renderErrorResponse(String result) {
return ServerResponse
.status(result.getHttpStatus())
.contentType(result.getContentType())
.body(BodyInserters.fromObject(result.getBody()));
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(result));
}
/**

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -1,27 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.param;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.BaseParamBuilder;
import org.springframework.web.server.ServerWebExchange;
/**
* @author tanghc
*/
public class GatewayParamBuilder extends BaseParamBuilder<ServerWebExchange> {
@Override
public ApiParam buildRequestParams(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getRequestParams(exchange);
}
@Override
public String getIP(ServerWebExchange ctx) {
return ctx.getRequest().getRemoteAddress().getAddress().getHostAddress();
}
@Override
public void setVersionInHeader(ServerWebExchange ctx, String headerName, String version) {
ctx.getRequest().getHeaders().add(headerName, version);
}
}

View File

@@ -4,12 +4,14 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.message.Error;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -21,7 +23,8 @@ import java.util.Map;
* @author tanghc
*/
@Slf4j
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, GatewayResult> {
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, String>
implements ResultExecutorForGateway {
@Override
public int getResponseStatus(ServerWebExchange exchange) {
@@ -46,12 +49,12 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
}
@Override
public Map<String, Object> getApiParam(ServerWebExchange exchange) {
return exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
public ApiParam getApiParam(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getApiParam(exchange);
}
@Override
public GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex) {
public String buildErrorResult(ServerWebExchange exchange, Throwable ex) {
Error error;
if (ex instanceof ApiException) {
ApiException apiException = (ApiException) ex;
@@ -59,11 +62,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
} else {
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
}
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
String body = this.merge(exchange, jsonObject);
return new GatewayResult(HttpStatus.OK, MediaType.APPLICATION_JSON_UTF8, body);
return this.merge(exchange, jsonObject);
}
}

View File

@@ -0,0 +1,29 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.route.BaseForwardChooser;
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
import org.springframework.web.server.ServerWebExchange;
/**
* @author tanghc
*/
public class GatewayForwardChooser extends BaseForwardChooser<ServerWebExchange> {
private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
@Override
public ApiParam getApiParam(ServerWebExchange exchange) {
return ServerWebExchangeUtil.getApiParam(exchange);
}
@Override
public ForwardInfo getForwardInfo(ServerWebExchange exchange) {
// 如果有异常,直接跳转到异常处理
if (ServerWebExchangeUtil.getThrowable(exchange) != null) {
return new ForwardInfo(VALIDATE_ERROR_PATH, "");
}
return super.getForwardInfo(exchange);
}
}

View File

@@ -6,11 +6,8 @@ import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import java.net.URI;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@@ -32,7 +29,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
path = path.replace('{', '?');
path = path.replace('}', '?');
}
targetRoute.setUri(URI.create(routeDefinition.getUri() + "#" + path));
targetRoute.setUri(URI.create(routeDefinition.getUri() + "/" + path));
targetRoute.setOrder(routeDefinition.getOrder());
// 添加过滤器
List<FilterDefinition> filterDefinitionList = routeDefinition.getFilters()
@@ -43,40 +40,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
return filterDefinition;
})
.collect(Collectors.toList());
LinkedList<PredicateDefinition> predicateDefinitionList = routeDefinition.getPredicates()
.stream()
.map(predicate -> {
PredicateDefinition predicateDefinition = new PredicateDefinition();
BeanUtils.copyProperties(predicate, predicateDefinition);
return predicateDefinition;
})
.collect(Collectors.toCollection(LinkedList::new));
// 下面两个自定义的断言添加到顶部注意ReadBody需要放在最前面
// 对应断言:
// NameVersion -> com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory
// ReadBody -> com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory
predicateDefinitionList.addFirst(new PredicateDefinition("NameVersion=" + routeDefinition.getId()));
predicateDefinitionList.addFirst(new PredicateDefinition("ReadBody="));
targetRoute.setFilters(filterDefinitionList);
targetRoute.setPredicates(predicateDefinitionList);
return new GatewayTargetRoute(serviceRouteInfo, routeDefinition, targetRoute);
}
@Override
protected List<RouteDefinition> getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) {
// 在第一个位置放一个没用的路由SpringCloudGateway会从第二个路由开始找原因不详
RouteDefinition routeDefinition = new RouteDefinition();
String name = "_first_route_";
String version = String.valueOf(System.currentTimeMillis());
routeDefinition.setId(name + version);
routeDefinition.setName(name);
routeDefinition.setVersion(version);
routeDefinition.setUri("lb://" + serviceRouteInfo.getServiceId());
routeDefinition.setOrder(Integer.MIN_VALUE);
return Collections.singletonList(routeDefinition);
}
}

View File

@@ -1,15 +1,19 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -25,28 +29,37 @@ import static java.util.Collections.synchronizedMap;
* @author tanghc
*/
@Slf4j
public class GatewayRouteRepository implements RouteDefinitionRepository, RouteRepository<GatewayTargetRoute> {
public class GatewayRouteRepository implements RouteRepository<GatewayTargetRoute>, RouteLocator {
private PathMatcher pathMatcher = new AntPathMatcher();
private final Map<String, GatewayTargetRoute> routes = synchronizedMap(new LinkedHashMap<>());
@Autowired
private RouteLocatorBuilder routeLocatorBuilder;
@Autowired
private ApplicationContext applicationContext;
private RouteLocator routeLocator;
@Override
public Flux<org.springframework.cloud.gateway.route.RouteDefinition> getRouteDefinitions() {
List<org.springframework.cloud.gateway.route.RouteDefinition> list = routes.values().parallelStream()
.map(TargetRoute::getTargetRouteDefinition)
public Flux<Route> getRoutes() {
return routeLocator.getRoutes();
}
public void refresh() {
RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes();
List<RouteDefinition> routeDefinitionList = this.routes.values()
.stream()
.map(AbstractTargetRoute::getRouteDefinition)
.collect(Collectors.toList());
return Flux.fromIterable(list);
}
@Override
public Mono<Void> save(Mono<org.springframework.cloud.gateway.route.RouteDefinition> route) {
return null;
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
routeDefinitionList.forEach(routeDefinition -> builder.route(routeDefinition.getId(),
r -> r.path(routeDefinition.getPath())
.uri(routeDefinition.getUri())));
this.routeLocator = builder.build();
// 触发
applicationContext.publishEvent(new RefreshRoutesEvent(new Object()));
}
/**

View File

@@ -1,105 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
/**
* 此断言决定执行哪个路由
*
* 使用地方:
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache
*
* @author tanghc
*/
@Slf4j
public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFactory<NameVersionRoutePredicateFactory.Config> {
private static final String PARAM_KEY = "param";
private static final String REGEXP_KEY = "regexp";
private PathMatcher pathMatcher = new AntPathMatcher();
public NameVersionRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
}
/**
* config.param为nameVersion即路由id
*
* @param config
* @return 返回断言
*/
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
Map<String, ?> params = ServerWebExchangeUtil.getRequestParams(exchange);
if (CollectionUtils.isEmpty(params)) {
return false;
}
String nameVersion = config.param;
Object name = params.get(ParamNames.API_NAME);
Object version = params.get(ParamNames.VERSION_NAME);
if (name == null || version == null) {
return false;
}
String key = name.toString() + version.toString();
// 如果是通配符
if (nameVersion.contains("{")) {
boolean match = pathMatcher.match(nameVersion, key);
if (match) {
Map<String, Object> attributes = exchange.getAttributes();
attributes.put(SopConstants.REDIRECT_PATH_KEY, key);
}
return match;
} else {
return key.equals(nameVersion);
}
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
private String regexp;
public String getParam() {
return param;
}
public Config setParam(String param) {
this.param = param;
return this;
}
public String getRegexp() {
return regexp;
}
public Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}

View File

@@ -1,202 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import static org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter.CACHED_REQUEST_BODY_KEY;
/**
* 获取请求参数插件兼容getpost使用方式
* &#64;Bean
* ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
* return new ReadBodyRoutePredicateFactory();
* }
*
* @see org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory
* 详见https://blog.51cto.com/thinklili/2329184
*
* 使用地方:
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache
*
* @author tanghc
*/
public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<ReadBodyRoutePredicateFactory.Config> {
protected static final Log LOGGER = LogFactory.getLog(ReadBodyPredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> HTTP_MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();
public ReadBodyRoutePredicateFactory() {
super(ReadBodyRoutePredicateFactory.Config.class);
}
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return exchange -> {
HttpMethod method = exchange.getRequest().getMethod();
if (method == HttpMethod.POST) {
return this.applyForPost(exchange, config);
} else {
return this.applyForGet(exchange, config);
}
};
}
/**
* 获取post表单参数
* @param exchange
* @param config
* @return
*/
protected Mono<Boolean> applyForPost(ServerWebExchange exchange, Config config) {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
// We can only read the body from the request once, once that happens if we try to read the body again an
// exception will be thrown. The below if/else caches the body object as a request attribute in the ServerWebExchange
// so if this filter is run more than once (due to more than one route using it) we do not try to read the
// request body multiple times
if (cachedBody != null) {
try {
boolean test = config.getPredicate().test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not canVisit the cached body object",
e);
}
}
return Mono.just(false);
} else {
//Join all the DataBuffers so we have a single DataBuffer for the body
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
//Update the retain counts so we can read the body twice, once to parse into an object
//that we can test the predicate against and a second time when the HTTP client sends
//the request downstream
//Note: if we end up reading the body twice we will run into a problem, but as of right
//now there is no good use case for doing this
DataBufferUtils.retain(dataBuffer);
//Make a slice for each read so each read has its own read/write indexes
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), HTTP_MESSAGE_READERS)
.bodyToMono(inClass)
.doOnNext(objectValue -> {
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux);
})
.map(objectValue -> config.getPredicate().test(objectValue));
});
}
}
/**
* 获取GET请求参数
* @param exchange
* @param config
* @return
*/
protected Mono<Boolean> applyForGet(ServerWebExchange exchange, Config config) {
// 处理get请求
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
String objectValue = null;
if (queryParams != null && queryParams.size() > 0) {
List<String> params = new ArrayList<>(queryParams.size());
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
params.add(entry.getKey() + "=" + entry.getValue().get(0));
}
objectValue = StringUtils.join(params.toArray());
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
}
return Mono.just(config.getPredicate().test(objectValue));
}
@Override
public Config newConfig() {
Config config = super.newConfig();
config.setInClass(String.class);
config.setPredicate(Objects::nonNull);
return config;
}
@Override
@SuppressWarnings("unchecked")
public Predicate<ServerWebExchange> apply(Config config) {
throw new UnsupportedOperationException(
"ReadBodyPredicateFactory is only async.");
}
public static class Config {
private Class inClass;
private Predicate predicate;
private Map<String, Object> hints;
public Class getInClass() {
return inClass;
}
public Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Predicate getPredicate() {
return predicate;
}
public <T> Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
setInClass(inClass);
this.predicate = predicate;
return this;
}
public Config setPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public Map<String, Object> getHints() {
return hints;
}
public Config setHints(Map<String, Object> hints) {
this.hints = hints;
return this;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,8 @@
package com.gitee.sop.gatewaycommon.loadbalancer;
/**
* @author tanghc
*/
public class LoadBalanceConfig {
}

View File

@@ -0,0 +1,128 @@
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 返回服务器实例没有选到则返回null
*/
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);
}
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
// 注意这里允许走灰度服务器如果不允许走注释notPreServers.addAll(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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -4,25 +4,31 @@ 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;
import com.gitee.sop.gatewaycommon.validate.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
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;
@@ -34,11 +40,20 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.annotation.PostConstruct;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author tanghc
*/
public class AbstractConfiguration implements ApplicationContextAware {
@Slf4j
public class AbstractConfiguration implements ApplicationContextAware, ApplicationRunner {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private volatile boolean isStartupCompleted;
@Autowired
protected Environment environment;
@@ -60,6 +75,17 @@ public class AbstractConfiguration implements ApplicationContextAware {
*/
@EventListener(classes = HeartbeatEvent.class)
public void listenNacosEvent(ApplicationEvent heartbeatEvent) {
// 没有启动完毕先等待
if (!isStartupCompleted) {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
log.error("condition.await() error", e);
} finally {
lock.unlock();
}
}
registryListener.onEvent(heartbeatEvent);
}
@@ -159,6 +185,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);
@@ -177,10 +223,22 @@ public class AbstractConfiguration implements ApplicationContextAware {
return corsConfiguration;
}
@Override
public void run(ApplicationArguments args) throws Exception {
this.isStartupCompleted = true;
lock.lock();
condition.signalAll();
lock.unlock();
after();
}
@PostConstruct
public final void after() {
private void post() {
EnvironmentContext.setEnvironment(environment);
SpringContext.setApplicationContext(applicationContext);
}
public final void after() {
if (RouteRepositoryContext.getRouteRepository() == null) {
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
}
@@ -196,7 +254,6 @@ public class AbstractConfiguration implements ApplicationContextAware {
initMessage();
initBeanInitializer();
doAfter();
}
protected void initBeanInitializer() {

View File

@@ -5,12 +5,9 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -55,21 +52,15 @@ public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoad
}
serviceIdMd5Map.put(serviceId, newMd5);
List<RouteDefinition> extRouteDefinitionList = this.getExtRouteDefinitionList(serviceRouteInfo);
List<RouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
int size = extRouteDefinitionList.size() + routeDefinitionList.size();
List<RouteDefinition> allRoutes = new ArrayList<>(size);
if (CollectionUtils.isNotEmpty(extRouteDefinitionList)) {
allRoutes.addAll(extRouteDefinitionList);
}
allRoutes.addAll(routeDefinitionList);
for (RouteDefinition routeDefinition : allRoutes) {
for (RouteDefinition routeDefinition : routeDefinitionList) {
T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition);
routeRepository.add(targetRoute);
if (log.isDebugEnabled()) {
log.debug("新增路由:{}", JSON.toJSONString(routeDefinition));
}
}
this.routeRepository.refresh();
callback.accept(null);
} catch (Exception e) {
log.error("加载路由信息失败serviceRouteInfo:{}", serviceRouteInfo, e);
@@ -91,14 +82,6 @@ public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoad
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
}
/**
* 返回附加的路由选项
* @return 返回附加的路由选项
*/
protected List<RouteDefinition> getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) {
return Collections.emptyList();
}
@Override
public void remove(String serviceId) {
serviceIdMd5Map.remove(serviceId);

View File

@@ -49,7 +49,7 @@ public class DefaultEnvGrayManager implements EnvGrayManager {
}
private ServiceGrayConfig getGrayConfig(String serviceId) {
if (serviceId == null) {
if (serviceId == null || !instanceIdServiceIdMap.containsValue(serviceId)) {
return null;
}
return serviceGrayConfigMap.get(serviceId);

View File

@@ -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);
}
}

View File

@@ -45,4 +45,9 @@ public interface RouteRepository<T extends TargetRoute> {
* @param serviceId 服务id
*/
void deleteAll(String serviceId);
/**
* 刷新
*/
default void refresh() {}
}

View File

@@ -25,12 +25,14 @@ public class RouteRepositoryContext {
* 检查路由是否存在,不存在报错
* @param routeId 路由id
* @param errorEnum 报错信息
* @return 返回TargetRoute
*/
public static void checkExist(String routeId, ErrorEnum errorEnum) {
public static TargetRoute checkExist(String routeId, ErrorEnum errorEnum) {
TargetRoute targetRoute = routeRepository.get(routeId);
if (targetRoute == null) {
throw errorEnum.getErrorMeta().getException();
}
return targetRoute;
}
}

View File

@@ -14,6 +14,8 @@ import java.util.Map;
*/
public class ApiParam extends JSONObject implements Param {
public ApiParam() {
}
@@ -29,6 +31,8 @@ public class ApiParam extends JSONObject implements Param {
private String ip;
private boolean isGrayRequest;
private transient UploadContext uploadContext;
public void fitNameVersion() {
@@ -246,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;
}
}

View File

@@ -5,14 +5,15 @@ import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.Isv;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants;
@@ -69,7 +70,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
* @param t request
* @return 返回api参数
*/
public abstract Map<String, Object> getApiParam(T t);
public abstract ApiParam getApiParam(T t);
@Override
public String mergeResult(T request, String serviceResult) {
@@ -91,12 +92,12 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
responseData = JSON.parseObject(serviceResult);
responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
} else if(responseStatus == HttpStatus.NOT_FOUND.value()) {
} else if (responseStatus == HttpStatus.NOT_FOUND.value()) {
responseData = JSON.parseObject(serviceResult);
responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode());
responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError().getCode());
} else {
Map<String, Object> params = this.getApiParam(request);
ApiParam params = this.getApiParam(request);
log.error("微服务端报错params:{}, 微服务返回结果:{}", params, serviceResult);
this.storeError(request, ErrorType.UNKNOWN);
// 微服务端有可能返回500错误
@@ -140,8 +141,11 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
if (defaultSetting != null) {
return defaultSetting;
}
ApiInfo apiInfo = this.getApiInfo(request);
RouteDefinition baseRouteDefinition = apiInfo.gatewayRouteDefinition;
ApiParam params = this.getApiParam(request);
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
.map(TargetRoute::getRouteDefinition)
.orElse(null);
return Optional.ofNullable(baseRouteDefinition)
.map(routeDefinition -> {
int mergeResult = baseRouteDefinition.getMergeResult();
@@ -151,11 +155,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
}
protected ApiInfo getApiInfo(T request) {
Map<String, Object> params = this.getApiParam(request);
String name = this.getParamValue(params, ParamNames.API_NAME, SopConstants.UNKNOWN_METHOD);
String version = this.getParamValue(params, ParamNames.VERSION_NAME, SopConstants.UNKNOWN_VERSION);
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version);
ApiParam params = this.getApiParam(request);
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
String serviceId = Optional.ofNullable(targetRoute)
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
@@ -167,8 +168,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
.orElse(null);
ApiInfo apiInfo = new ApiInfo();
apiInfo.name = name;
apiInfo.version = version;
apiInfo.name = params.fetchName();
apiInfo.version = params.fetchVersion();
apiInfo.serviceId = serviceId;
apiInfo.gatewayRouteDefinition = baseRouteDefinition;
return apiInfo;
@@ -190,8 +191,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
public String merge(T exchange, JSONObject responseData) {
JSONObject finalData = new JSONObject(true);
Map<String, Object> params = this.getApiParam(exchange);
String name = this.getParamValue(params, ParamNames.API_NAME, ERROR_METHOD);
ApiParam params = this.getApiParam(exchange);
String name = params.fetchName();
ApiConfig apiConfig = ApiConfig.getInstance();
// 点换成下划线
DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder();

View File

@@ -0,0 +1,9 @@
package com.gitee.sop.gatewaycommon.result;
import org.springframework.web.server.ServerWebExchange;
/**
* @author tanghc
*/
public interface ResultExecutorForGateway extends ResultExecutor<ServerWebExchange, String> {
}

View File

@@ -0,0 +1,9 @@
package com.gitee.sop.gatewaycommon.result;
import com.netflix.zuul.context.RequestContext;
/**
* @author tanghc
*/
public interface ResultExecutorForZuul extends ResultExecutor<RequestContext, String> {
}

View File

@@ -0,0 +1,66 @@
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>, ApiParamAware<T> {
@Autowired
private EnvGrayManager envGrayManager;
@Override
public ForwardInfo getForwardInfo(T t) {
ApiParam apiParam = getApiParam(t);
String nameVersion = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
RouteDefinition routeDefinitionOrig = targetRoute.getRouteDefinition();
String path = routeDefinitionOrig.getPath();
String version = apiParam.fetchVersion();
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
// 如果服务在灰度阶段,返回一个灰度版本号
String grayVersion = envGrayManager.getVersion(serviceId, nameVersion);
// 如果是灰度环境
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 {
// 获取灰度接口
RouteDefinition routeDefinition = targetRouteDest.getRouteDefinition();
path = routeDefinition.getPath();
}
}
}
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;
}
}

View File

@@ -26,6 +26,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
static {
EXCLUDE_SERVICE_ID_LIST.add("sop-gateway");
EXCLUDE_SERVICE_ID_LIST.add("sop-website");
EXCLUDE_SERVICE_ID_LIST.add("website-server");
}
@@ -37,7 +38,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
*
* @param serviceId serviceId
*/
public void removeRoutes(String serviceId) {
public synchronized void removeRoutes(String serviceId) {
serviceListener.onRemoveService(serviceId.toLowerCase());
}
@@ -46,7 +47,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
*
* @param instance 服务实例
*/
public void pullRoutes(InstanceDefinition instance) {
public synchronized void pullRoutes(InstanceDefinition instance) {
// serviceId统一小写
instance.setServiceId(instance.getServiceId().toLowerCase());
serviceListener.onAddInstance(instance);

View File

@@ -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

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.gatewaycommon.route;
/**
* 转发选择
* @author tanghc
*/
public interface ForwardChooser<T> {
/**
* 返回转发信息
* @param t 上下文
* @return 返回转发信息
*/
ForwardInfo getForwardInfo(T t);
}

View File

@@ -0,0 +1,19 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class ForwardInfo {
private String path;
private String version;
private String domain;
public ForwardInfo(String path, String version) {
this.path = path;
this.version = version;
}
}

View File

@@ -68,9 +68,10 @@ public class ApiValidator implements Validator {
@Override
public void validate(ApiParam param) {
checkIP(param);
checkEnable(param);
TargetRoute targetRoute = checkEnable(param);
ApiConfig apiConfig = ApiContext.getApiConfig();
if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
if (apiConfig.isIgnoreValidate()
|| BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) {
if (log.isDebugEnabled()) {
log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
}
@@ -103,7 +104,7 @@ public class ApiValidator implements Validator {
*
* @param param 接口参数
*/
protected void checkEnable(ApiParam param) {
protected TargetRoute checkEnable(ApiParam param) {
String name = param.fetchName();
if (name == null) {
throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
@@ -114,12 +115,13 @@ public class ApiValidator implements Validator {
}
String routeId = param.fetchNameVersion();
// 检查路由是否存在
RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
TargetRoute targetRoute = RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
// 检查路由是否启用
RouteConfig routeConfig = routeConfigManager.get(routeId);
if (!routeConfig.enable()) {
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
}
return targetRoute;
}
/**

View File

@@ -37,7 +37,13 @@ public class ValidateService {
RequestContext currentContext = RequestContext.getCurrentContext();
currentContext.setRequest(RequestUtil.wrapRequest(request));
currentContext.setResponse(response);
doValidate(currentContext, callback);
// 解析参数
ApiParam param = ZuulContext.getApiParam();
if (param == null) {
param = paramBuilder.build(currentContext);
ZuulContext.setApiParam(param);
}
doValidate(currentContext, param, callback);
}
/**
@@ -45,10 +51,7 @@ public class ValidateService {
*
* @param currentContext currentContext
*/
private void doValidate(RequestContext currentContext, ValidateCallback callback) {
// 解析参数
ApiParam param = paramBuilder.build(currentContext);
ZuulContext.setApiParam(param);
private void doValidate(RequestContext currentContext, ApiParam param, ValidateCallback callback) {
Exception error = null;
// 验证操作,这里有负责验证签名参数
try {

View File

@@ -8,17 +8,18 @@ import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.zuul.ValidateService;
import com.gitee.sop.gatewaycommon.zuul.controller.ConfigChannelController;
import com.gitee.sop.gatewaycommon.zuul.controller.ErrorLogController;
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController;
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulIndexController;
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreEnvGrayFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteCache;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
import com.netflix.zuul.context.RequestContext;
@@ -26,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.context.annotation.Bean;
@@ -89,22 +89,28 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
return new Servlet30WrapperFilterExt();
}
@Bean
SopRouteLocator sopRouteLocator() {
return new SopRouteLocator();
}
/**
* 选取路由
* @param zuulRouteRepository
* @param sopRouteLocator
* @param proxyRequestHelper
* @return
*/
@Bean
PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) {
PreDecorationFilter preDecorationFilter(SopRouteLocator sopRouteLocator, ProxyRequestHelper proxyRequestHelper) {
// 自定义路由
RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository);
return new PreDecorationFilter(routeLocator,
return new PreDecorationFilter(sopRouteLocator,
this.server.getServlet().getContextPath(),
this.zuulProperties,
proxyRequestHelper);
}
/**
* 路由管理
* @param zuulRouteRepository 路由仓库
@@ -140,14 +146,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
return new PreLimitFilter();
}
/**
* 决定版本号
*/
@Bean
PreEnvGrayFilter preEnvGrayFilter() {
return new PreEnvGrayFilter();
}
/**
* 错误处理扩展
*/
@@ -169,9 +167,13 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
* 统一错误处理
*/
@Bean
@ConditionalOnMissingBean
ZuulErrorController baseZuulController() {
ZuulErrorController zuulErrorController() {
return ApiContext.getApiConfig().getZuulErrorController();
}
@Bean
ZuulForwardChooser zuulForwardChooser() {
return new ZuulForwardChooser();
}
}

View File

@@ -40,7 +40,7 @@ public class ConfigChannelController {
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
}
@Value("${zuul.secret}")
@Value("${sop.secret}")
private String secret;
@PostMapping("/configChannelMsg")

View File

@@ -1,72 +1,23 @@
package com.gitee.sop.gatewaycommon.zuul.controller;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.result.ApiResult;
import com.gitee.sop.gatewaycommon.result.JsonResult;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Map;
/**
* @author tanghc
*/
@Controller
public class ErrorLogController {
@RestController
public class ErrorLogController extends BaseErrorLogController<HttpServletRequest> {
TaobaoSigner signer = new TaobaoSigner();
@Value("${zuul.secret}")
private String secret;
@GetMapping("listErrors")
public ApiResult listErrors(HttpServletRequest request) {
try {
this.check(request);
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
JsonResult apiResult = new JsonResult();
apiResult.setData(allErrors);
return apiResult;
} catch (Exception e) {
ApiResult apiResult = new ApiResult();
apiResult.setCode("505050");
apiResult.setMsg(e.getMessage());
return apiResult;
}
}
@GetMapping("clearErrors")
public ApiResult clearErrors(HttpServletRequest request) {
try {
this.check(request);
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
serviceErrorManager.clear();
return new ApiResult();
} catch (Exception e) {
ApiResult apiResult = new ApiResult();
apiResult.setCode("505050");
apiResult.setMsg(e.getMessage());
return apiResult;
}
}
private void check(HttpServletRequest request) {
@Override
protected ApiParam getApiParam(HttpServletRequest request) {
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
ApiParam apiParam = ApiParam.build(params);
boolean right = signer.checkSign(apiParam, secret);
if (!right) {
throw new RuntimeException("签名校验失败");
}
return ApiParam.build(params);
}
}

View File

@@ -1,45 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.controller;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import org.springframework.beans.factory.annotation.Value;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 传统web开发入口
* @author tanghc
*/
//@WebServlet(urlPatterns = "/rest/*")
public class RestServlet extends HttpServlet {
private static final String EMPTY_VERSION = "";
@Value("${zuul.servlet-path:/zuul}")
private String path;
@Value("${sop.restful.path:/rest}")
private String restPath;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = request.getRequestURL().toString();
int index = url.indexOf(restPath);
// 取/rest的后面部分
String path = url.substring(index + restPath.length());
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, path);
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, EMPTY_VERSION);
request.setAttribute(SopConstants.SOP_NOT_MERGE, true);
request.getRequestDispatcher(this.path).forward(request, response);
}
}

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.gatewaycommon.zuul.configuration;
package com.gitee.sop.gatewaycommon.zuul.controller;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
@@ -6,30 +6,26 @@ import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 处理网关自身异常
* zuul的异常处理
*
* @author tanghc
*/
@Controller
@Slf4j
@RestController
public class ZuulErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
/**
* 错误最终会到这里来
*/
@RequestMapping(ERROR_PATH)
@ResponseBody
@RequestMapping("/error")
public Object error(HttpServletRequest request, HttpServletResponse response) {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getResponse() == null) {
@@ -51,6 +47,6 @@ public class ZuulErrorController implements ErrorController {
@Override
public String getErrorPath() {
return ERROR_PATH;
return "/error";
}
}

View File

@@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletException;
@@ -75,15 +74,4 @@ public class ZuulIndexController {
request.getRequestDispatcher(this.path).forward(request, response);
}
@RequestMapping("/{method}/{version}/")
public void redirect(
@PathVariable("method") String method
, @PathVariable("version") String version
, HttpServletRequest request
, HttpServletResponse response
) {
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, method);
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, version);
validateService.validate(request, response, callback);
}
}

View File

@@ -14,7 +14,10 @@ import org.springframework.beans.factory.annotation.Autowired;
* 灰度发布判断,改变版本号
*
* @author tanghc
* @deprecated
* @see com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser
*/
@Deprecated
public class PreEnvGrayFilter extends BaseZuulFilter {
@Autowired

View File

@@ -1,124 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
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 org.apache.commons.lang.ArrayUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* 服务实例选择器
*
* @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> servers = lb.getReachableServers();
RequestContext currentContext = RequestContext.getCurrentContext();
// 存放预发服务器
List<Server> preServers = new ArrayList<>(4);
// 存放灰度发布服务器
List<Server> grayServers = new ArrayList<>(4);
// 存放非预发服务器
List<Server> notPreServers = new ArrayList<>(4);
for (Server server : servers) {
// 是否开启了预发模式
if (this.isPreServer(server)) {
preServers.add(server);
} else if (this.isGrayServer(server)) {
grayServers.add(server);
} else {
notPreServers.add(server);
}
}
notPreServers.addAll(grayServers);
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
if (preServers.isEmpty() && grayServers.isEmpty()) {
return super.choose(key);
}
// 如果是从预发布域名访问过来,则认为是预发布请求
if (this.isRequestFromPreDomain(currentContext.getRequest())) {
return doChoose(preServers, key);
}
// 如果是灰度请求
if (this.isRequestGrayServer(currentContext)) {
return doChoose(grayServers, key);
}
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
return doChoose(notPreServers, key);
}
/**
* 通过判断hostname来确定是否是预发布请求
*
* @param t t
* @return 返回true可以进入到预发环境
*/
protected boolean isRequestFromPreDomain(HttpServletRequest t) {
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain");
if (StringUtils.isEmpty(domain)) {
return false;
}
String[] domains = domain.split("\\,");
return ArrayUtils.contains(domains, getHost(t));
}
protected boolean isRequestGrayServer(RequestContext t) {
return t.get(EnvGrayManager.ENV_GRAY) != null;
}
protected String getHost(HttpServletRequest t) {
return t.getServerName();
}
protected Server doChoose(List<Server> servers, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -6,7 +6,9 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.message.Error;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.util.Pair;
import com.netflix.zuul.context.RequestContext;
@@ -14,13 +16,12 @@ import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* @author tanghc
*/
@Slf4j
public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, String> {
public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, String> implements ResultExecutorForZuul {
@Override
protected boolean isMergeResult(RequestContext request) {
@@ -62,7 +63,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
}
@Override
public Map<String, Object> getApiParam(RequestContext requestContext) {
public ApiParam getApiParam(RequestContext requestContext) {
return ZuulContext.getApiParam();
}

View File

@@ -2,7 +2,11 @@ package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.core.Ordered;
@@ -19,11 +23,11 @@ import java.util.stream.Collectors;
*/
public class SopRouteLocator implements RouteLocator, Ordered {
@Autowired
private ZuulRouteRepository zuulRouteRepository;
public SopRouteLocator(ZuulRouteRepository zuulRouteRepository) {
this.zuulRouteRepository = zuulRouteRepository;
}
@Autowired
private ZuulForwardChooser zuulForwardChooser;
@Override
public Collection<String> getIgnoredPaths() {
@@ -52,7 +56,13 @@ public class SopRouteLocator implements RouteLocator, Ordered {
if (zuulTargetRoute == null) {
return null;
}
return zuulTargetRoute.getTargetRouteDefinition();
Route targetRouteDefinition = zuulTargetRoute.getTargetRouteDefinition();
ForwardInfo forwardInfo = zuulForwardChooser.getForwardInfo(RequestContext.getCurrentContext());
String forwardPath = forwardInfo.getPath();
targetRouteDefinition.setPath(forwardPath);
String versionInHead = forwardInfo.getVersion();
RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, versionInHead);
return targetRouteDefinition;
}
@Override

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.route.BaseForwardChooser;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext;
/**
* @author tanghc
*/
public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> {
@Override
public ApiParam getApiParam(RequestContext requestContext) {
return ZuulContext.getApiParam();
}
}

View File

@@ -6,11 +6,11 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>2.5.12-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sop-service-common</artifactId>
<version>2.5.12-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sop-service-common</name>

View File

@@ -34,6 +34,8 @@ public class ServiceApiInfo {
private int permission;
/** 是否需要token */
private int needToken;
/** 是否是兼容模式,即使用了@ApiAbility注解 */
private int compatibleMode;
/** 是否是原始Mapping */
private boolean originalMapping;

View File

@@ -95,6 +95,7 @@ public class ApiMetaBuilder {
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
apiMeta.setNeedToken(BooleanUtils.toInteger(apiMappingInfo.isNeedToken()));
apiMeta.setCompatibleMode(BooleanUtils.toInteger(apiMappingInfo.isCompatibleMode()));
return apiMeta;
} else {
if (!ServiceContext.getCurrentContext().getBoolean(ServiceContext.RESTFUL_KEY, false)) {

View File

@@ -1,9 +0,0 @@
package com.gitee.sop.servercommon.manager;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author tanghc
*/
public interface ServiceRouteInfoHandler {
}

View File

@@ -5,6 +5,7 @@ import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.bean.ServiceContext;
import com.gitee.sop.servercommon.util.OpenUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.StringValueResolver;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
@@ -18,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
@@ -29,7 +29,11 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
ApiAbility apiAbility = OpenUtil.getAnnotationFromMethodOrClass(method, ApiAbility.class);
StringValueResolver valueResolver = null;
if (apiMapping != null) {
valueResolver = stringValueResolver;
String version = apiMapping.version();
if (StringUtils.isBlank(version)) {
version = ServiceConfig.getInstance().getDefaultVersion();
}
valueResolver = new ApiMappingStringValueResolverVersion(version);
}
if (isMvc || apiAbility != null) {
valueResolver = stringValueResolverMVC;
@@ -46,7 +50,8 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
boolean ignoreValidate;
boolean mergeResult;
boolean permission;
boolean needToken = false;
boolean needToken;
boolean compatibleMode = false;
ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
if (apiMapping != null) {
name = apiMapping.value()[0];
@@ -63,6 +68,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
mergeResult = apiAbility.mergeResult();
permission = apiAbility.permission();
needToken = apiAbility.needToken();
compatibleMode = true;
} else {
return super.getCustomMethodCondition(method);
}
@@ -87,6 +93,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple
apiMappingInfo.setMergeResult(mergeResult);
apiMappingInfo.setPermission(permission);
apiMappingInfo.setNeedToken(needToken);
apiMappingInfo.setCompatibleMode(compatibleMode);
logger.info("注册接口name:" + method + " version:" + version);
return new ApiMappingRequestCondition(apiMappingInfo);
}

View File

@@ -13,6 +13,8 @@ public class ApiMappingInfo {
private boolean mergeResult;
private boolean permission;
private boolean needToken;
private boolean compatibleMode;
public ApiMappingInfo(String name, String version) {
this.name = name;

View File

@@ -37,6 +37,11 @@ public class ApiMappingRequestCondition implements RequestCondition<ApiMappingRe
*/
@Override
public ApiMappingRequestCondition getMatchingCondition(HttpServletRequest request) {
String servletPath = request.getServletPath();
String path = apiMappingInfo.getName() + "/" + apiMappingInfo.getVersion() + "/";
if (servletPath.contains(path)) {
return this;
}
String version = this.getVersion(request);
if (version.equals(this.apiMappingInfo.getVersion())) {
return this;

View File

@@ -0,0 +1,25 @@
package com.gitee.sop.servercommon.mapping;
import org.springframework.util.StringValueResolver;
/**
* @author tanghc
*/
public class ApiMappingStringValueResolverVersion implements StringValueResolver {
private static final String SPLIT = "/";
private String version;
public ApiMappingStringValueResolverVersion(String version) {
this.version = version;
}
@Override
public String resolveStringValue(String strVal) {
if (strVal == null) {
return null;
}
return strVal + SPLIT + version + SPLIT;
}
}

View File

@@ -73,4 +73,9 @@ public class RouteDefinition {
* 是否需要token
*/
private int needToken;
/**
* 是否是兼容模式,即使用了@ApiAbility注解
*/
private int compatibleMode;
}