SpringCloud:搭建基于Gateway的微服务网关(二)
0.代码
https://github.com/fengdaizang/OpenAPI
1.引入相关依赖
pom文件如下:
<?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>OpenAPI</artifactId> <groupId>com.fdzang.microservice</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion><span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>api-gateway<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">><br> <!-- 公共模块引入了web模块,会与gateway产生冲突,故排除 --></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>com.fdzang.microservice<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>api-common<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span>1.0-SNAPSHOT<span style="color: #0000ff;"></</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">exclusions</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">exclusion</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>org.springframework.boot<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>spring-boot-starter-web<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">exclusion</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">exclusions</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>org.projectlombok<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>lombok<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>org.apache.commons<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>commons-collections4<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>commons-io<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>commons-io<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>commons-codec<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>commons-codec<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>com.google.guava<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>guava<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>com.google.code.gson<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>gson<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>com.alibaba<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>fastjson<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span>${fastjson.version}<span style="color: #0000ff;"></</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">><br></span>
<!-- 引入gateway模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>
<!-- 引入eureka模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>
<!-- 引入openfeign模块,这里不要用feign,Springboot2.0已弃用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency><span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>org.springframework.cloud<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>spring-cloud-starter-netflix-hystrix<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span>${spring.cloud.starter.version}<span style="color: #0000ff;"></</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span>org.springframework.boot<span style="color: #0000ff;"></</span><span style="color: #800000;">groupId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span>spring-boot-configuration-processor<span style="color: #0000ff;"></</span><span style="color: #800000;">artifactId</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span>${spring.boot.version}<span style="color: #0000ff;"></</span><span style="color: #800000;">version</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"><</span><span style="color: #800000;">optional</span><span style="color: #0000ff;">></span>true<span style="color: #0000ff;"></</span><span style="color: #800000;">optional</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependency</span><span style="color: #0000ff;">></span> <span style="color: #0000ff;"></</span><span style="color: #800000;">dependencies</span><span style="color: #0000ff;">></span>
</project>
2.配置Gateway
server:
port: 7000
#注册到eureka
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7003/eureka/
#配置gateway拦截规则
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: gateway
uri: http://www.baidu.com
predicates:
- Path=/**
#这里定义了鉴权的服务名,以及白名单
auth:
service-id: api-auth-v1
gateway:
white:
- /login
这里是id生成器的配置,Twitter-Snowflake
IdWorker:
workerId: 122
datacenterId: 1231
3.过滤器
3.1.ID生成拦截
对每个请求生成一个唯一的请求id
package com.fdzang.microservice.gateway.gateway;import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/**
生成一个请求的特定id
@author tanghu
@Date: 2019/11/5 18:42
*/
@Slf4j
@Component
public class SerialNoFilter implements GlobalFilter, Ordered {@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();String requestId</span>=<span style="color: #000000;"> request.getHeaders().getFirst(GatewayConstant.REQUEST_TRACE_ID); </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (StringUtils.isEmpty(requestId)) { Object attribute </span>=<span style="color: #000000;"> exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID); </span><span style="color: #0000ff;">if</span> (attribute == <span style="color: #0000ff;">null</span><span style="color: #000000;">) { requestId </span>=<span style="color: #000000;"> String.valueOf(IdWorker.getWorkerId()); exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId); } }</span><span style="color: #0000ff;">else</span><span style="color: #000000;">{ exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange);
}
@Override
public int getOrder() {
return GatewayConstant.Order.SERIAL_NO_ORDER;
}
}
3.2.鉴权拦截
获取请求头中的鉴权信息,对信息校验,这里暂时没有做(AuthResult authService.auth(AuthRequest request)),这里需求请求其他模块对请求信息进行校验,返回校验结果
package com.fdzang.microservice.gateway.gateway;import com.fdzang.microservice.common.entity.auth.AuthCode;
import com.fdzang.microservice.common.entity.auth.AuthRequest;
import com.fdzang.microservice.common.entity.auth.AuthResult;
import com.fdzang.microservice.gateway.service.AuthService;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
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.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;
import java.util.Map;
import java.util.TreeMap;/**
权限校验
@author tanghu
@Date: 2019/10/22 18:00
*/
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {@Autowired
private AuthService authService;@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
String url = exchange.getRequest().getURI().getPath();ServerHttpRequest request </span>=<span style="color: #000000;"> exchange.getRequest(); </span><span style="color: #008000;">//</span><span style="color: #008000;">跳过白名单</span> <span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span> != WhiteUrl.getWhite() &&<span style="color: #000000;"> WhiteUrl.getWhite().contains(url)){ </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange); } </span><span style="color: #008000;">//</span><span style="color: #008000;">获取权限校验部分 </span><span style="color: #008000;">//</span><span style="color: #008000;">Authorization: gateway:{AccessId}:{Signature}</span> String authHeader =<span style="color: #000000;"> exchange.getRequest().getHeaders().getFirst(GatewayConstant.AUTH_HEADER); </span><span style="color: #0000ff;">if</span><span style="color: #000000;">(StringUtils.isBlank(authHeader)){ log.warn(</span>"request has no authorization header, uuid:{}, request:{}"<span style="color: #000000;">,requestId, url); </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> IllegalArgumentException("bad request"<span style="color: #000000;">); } List</span><String> auths = Splitter.on(":"<span style="color: #000000;">).trimResults().omitEmptyStrings().splitToList(authHeader); </span><span style="color: #0000ff;">if</span>(CollectionUtils.isEmpty(auths) || auths.size() != 3 || !GatewayConstant.AUTH_LABLE.equals(auths.get(0<span style="color: #000000;">))){ log.warn(</span>"bad authorization header, uuid:{}, request:[{}], header:{}"<span style="color: #000000;">, requestId, url, authHeader); </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> IllegalArgumentException("bad request"<span style="color: #000000;">); } </span><span style="color: #008000;">//</span><span style="color: #008000;">校验时间戳是否合法</span> String timestamp =<span style="color: #000000;"> exchange.getRequest().getHeaders().getFirst(GatewayConstant.TIMESTAMP_HEADER); </span><span style="color: #0000ff;">if</span> (StringUtils.isBlank(timestamp) ||<span style="color: #000000;"> isTimestampExpired(timestamp)) { log.warn(</span>"wrong timestamp:{}, uuid:{}, request:{}"<span style="color: #000000;">, timestamp, requestId, url); } String accessId </span>= auths.get(1<span style="color: #000000;">); String sign </span>= auths.get(2<span style="color: #000000;">); String stringToSign </span>=<span style="color: #000000;"> getStringToSign(request, timestamp); AuthRequest authRequest </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> AuthRequest(); authRequest.setAccessId(accessId); authRequest.setSign(sign); authRequest.setStringToSign(stringToSign); authRequest.setHttpMethod(request.getMethodValue()); authRequest.setUri(url); AuthResult authResult </span>=<span style="color: #000000;"> authService.auth(authRequest); </span><span style="color: #0000ff;">if</span> (authResult.getStatus() !=<span style="color: #000000;"> AuthCode.SUCEESS.getAuthCode()) { log.warn(</span>"checkSign failed, uuid:{}, accessId:{}, request:[{}], error:{}"<span style="color: #000000;">, requestId, accessId, url, authResult.getDescription()); </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> RuntimeException(authResult.getDescription()); } log.info(</span>"request auth finished, uuid:{}, orgCode:{}, userName:{}, accessId:{}, request:{}, serviceName:{}"<span style="color: #000000;">, requestId, authResult.getOrgCode(), authResult.getUsername(), accessId, url, authResult.getServiceName()); exchange.getAttributes().put(GatewayConstant.SERVICE_NAME,authResult.getServiceName()); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange);
}
/**
获取原始字符串(签名前)
@param request
@param timestamp
@return
*/
private String getStringToSign(ServerHttpRequest request, String timestamp){
// headers
TreeMap<String, String> headersInSign = new TreeMap<>();
HttpHeaders headers = request.getHeaders();
for (Map.Entry<String,List<String>> header:headers.entrySet()) {
String key = header.getKey();
if (key.startsWith(GatewayConstant.AUTH_HEADER_PREFIX)) {
headersInSign.put(key, header.getValue().get(0));
}
}StringBuilder headerStringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : headersInSign.entrySet()) {
headerStringBuilder.append(entry.getKey()).append("😊.append(entry.getValue()).append("\n");
}
String headerString = null;
if (headerStringBuilder.length() != 0) {
headerString = headerStringBuilder.deleteCharAt(headerStringBuilder.length()-1).toString();
}// Url_String
TreeMap<String, String> paramsInSign = new TreeMap<>();
MultiValueMap<String, String> parameterMap = request.getQueryParams();
if (MapUtils.isNotEmpty(parameterMap)) {
for (Map.Entry<String, List<String>> entry : parameterMap.entrySet()) {
paramsInSign.put(entry.getKey(), entry.getValue().get(0));
}
}// 原始url
String originalUrl = request.getURI().getPath();StringBuilder uriStringBuilder = new StringBuilder(originalUrl);
if (!parameterMap.isEmpty()) {
uriStringBuilder.append("?");
for (Map.Entry<String, String> entry : paramsInSign.entrySet()) {
uriStringBuilder.append(entry.getKey());
if (StringUtils.isNotBlank(entry.getValue())) {
uriStringBuilder.append("=").append(entry.getValue());
}
uriStringBuilder.append("&");
}
uriStringBuilder.deleteCharAt(uriStringBuilder.length()-1);
}String uriString = uriStringBuilder.toString();
String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
//这里可以对请求参数进行MD5校验,暂时不做
String contentMd5 = headers.getFirst(GatewayConstant.CONTENTE_MD5);String[] parts = {
request.getMethodValue(),
StringUtils.isNotBlank(contentMd5) ? contentMd5 : "",
StringUtils.isNotBlank(contentType) ? contentType : "",
timestamp,
headerString,
uriString
};return Joiner.on(GatewayConstant.STRING_TO_SIGN_DELIM).skipNulls().join(parts);
}/**
校验时间戳是否超时
@param timestamp
@return
*/
private boolean isTimestampExpired(String timestamp){
long l = NumberUtils.toLong(timestamp, 0L);
if (l == 0) {
return true;
}return Math.abs(System.currentTimeMillis() - l) > GatewayConstant.EXPIRE_TIME_SECONDS *1000;
}@Override
public int getOrder() {
return GatewayConstant.Order.AUTH_ORDER;
}
}
3.3.服务分发
根据鉴权后的结果能得到服务名,然后重写路由以及请求,对该次请求进行转发
package com.fdzang.microservice.gateway.gateway;import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Splitter;
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.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;
/**
@author tanghu
@Date: 2019/11/6 15:39
*/
@Slf4j
@Component
public class ModifyRequestFilter implements GlobalFilter, Ordered {@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
ServerHttpRequest request = exchange.getRequest();</span><span style="color: #008000;">//</span><span style="color: #008000;">跳过白名单</span> <span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">null</span> != WhiteUrl.getWhite() &&<span style="color: #000000;"> WhiteUrl.getWhite().contains(url)){ </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange); } String serviceName </span>=<span style="color: #000000;"> exchange.getAttribute(GatewayConstant.SERVICE_NAME); </span><span style="color: #008000;">//</span><span style="color: #008000;">修改路由</span> Route route =<span style="color: #000000;"> exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); Route newRoute </span>=<span style="color: #000000;"> Route.async() .asyncPredicate(route.getPredicate()) .filters(route.getFilters()) .id(route.getId()) .order(route.getOrder()) .uri(GatewayConstant.URI.LOAD_BALANCE</span>+<span style="color: #000000;">serviceName).build(); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,newRoute); </span><span style="color: #008000;">//</span><span style="color: #008000;">修改请求路径</span> List<String> strings = Splitter.on("/").omitEmptyStrings().trimResults().limit(3<span style="color: #000000;">).splitToList(url); String newServletPath </span>= "/" + strings.get(2<span style="color: #000000;">); ServerHttpRequest newRequest </span>=<span style="color: #000000;"> request.mutate().path(newServletPath).build(); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange.mutate().request(newRequest).build());
}
@Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_REQUEST_ORDER;
}
}
3.4.统一响应
对响应进行统一封装
package com.fdzang.microservice.gateway.gateway;import com.alibaba.fastjson.JSON;
import com.fdzang.microservice.common.entity.ApiResult;
import com.fdzang.microservice.gateway.entity.GatewayError;
import com.fdzang.microservice.gateway.entity.GatewayResult;
import com.fdzang.microservice.gateway.entity.GatewayResultEnums;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.nio.charset.Charset;
/**
@author tanghu
@Date: 2019/11/7 8:58
*/
@Slf4j
@Component
public class ModifyResponseFilter implements GlobalFilter, Ordered {@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer);String originalbody </span>= <span style="color: #0000ff;">new</span> String(content, Charset.forName("UTF-8"<span style="color: #000000;">)); String finalBody </span>=<span style="color: #000000;"> originalbody; ApiResult apiResult </span>= JSON.parseObject(originalbody,ApiResult.<span style="color: #0000ff;">class</span><span style="color: #000000;">); GatewayResult result </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GatewayResult(); result.setCode(GatewayResultEnums.SUCC.getCode()); result.setMsg(GatewayResultEnums.SUCC.getMsg()); result.setReq_id(requestId); </span><span style="color: #0000ff;">if</span> (apiResult.getCode() == <span style="color: #0000ff;">null</span> && apiResult.getMsg() == <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 尝试解析body为网关的错误信息</span> GatewayError gatewayError = JSON.parseObject(originalbody,GatewayError.<span style="color: #0000ff;">class</span><span style="color: #000000;">); result.setSub_code(gatewayError.getStatus()); result.setSub_msg(gatewayError.getMessage()); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { result.setSub_code(apiResult.getCode()); result.setSub_msg(apiResult.getMsg()); } result.setData(apiResult.getData()); finalBody </span>=<span style="color: #000000;"> JSON.toJSONString(result); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> bufferFactory.wrap(finalBody.getBytes()); })); } </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">super</span><span style="color: #000000;">.writeWith(body); } }; </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_RESPONSE_ORDER;
}
}
4.测试
10:25:54.961 [main] INFO c.f.microservice.mock.util.SignUtil - StringToSign:
GET
1573093554201
/v2/base/zuul/tag/getMostUsedTags?from=2017-11-25 00:00:00&plate_num=部A11110&to=2017-11-30 00:00:00
10:25:54.979 [main] INFO c.f.microservice.mock.util.HttpUtil - sign:Y+usbpHlwOw4F2sq4b0pNjgXGDAXoYgs1syOOPxPFAE=
10:25:59.868 [main] INFO com.fdzang.microservice.mock.Demo - {"code":0,"data":[{"tagPublishedRefCount":3,"tagTitle":"Solo","id":"1533101769023","tagReferenceCount":3},{"tagPublishedRefCount":1,"tagTitle":"tetet","id":"1559285894006","tagReferenceCount":1}],"msg":"succ","req_id":"2627469547766022144","sub_code":0,"sub_msg":"ok"}
Process finished with exit code 0
由返回结果,可知此次请求完成。
5.注意事项
转发的目标服务需要跟网关注册在同一个注册中心下,路由uri配置为 lb://service_name,则会转发到对应的服务下,并且gateway会自动采用负载均衡机制
响应请求的顺序需要小于 NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 该值为-1
其他拦截器的顺序无固定要求,值越小越先执行