SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用

目录

1、网关概述

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

Spring Cloud Gateway 需要 Spring Boot 和 Spring Webflux 提供的 Netty 运行时。它不适用于传统的 Servlet(SpringMVC) 容器。
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用

关键字:

  • Route(路由):网关的基本构建块。它由 ID、目标 URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。
  • Predicate(断言):匹配来自 HTTP 请求的任何内容,例如:URL、Header、参数等。
  • Filter(过滤器):使用特定工厂构建的实例,通过过滤器可以在发送下游请求之前或之后修改请求和响应。

工作流程:
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用

工作流程大体如下:

  1. 用户向Gateway发送请求
  2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
  3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用
  6. 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应

2、搭建基础网关服务

使用SpringCloudAlibaba+Nacos实现Gateway的整合操作,Nacos搭建参考:

《Docker部署Nacos-2.0.3单机环境》

《Docker部署Nacos-2.0.3集群环境》

《Linux部署Nacos-2.0.3单机环境》

《Linux部署Nacos-2.0.3集群环境》

nacos整合时需要注意:

  • 如果gateway使用namespace,则要和项目中的其他服务的namespace一致。
  • 如果gateway使用group,则要和项目中的其他服务的group一致。

2.1 pom

<!--gateway网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--nacos客户端-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2.2 yaml配置

server:
  port: 8085
spring:
  application:
    name: demo-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.111:8848
        # 如果使用namespace,则要和项目中的其他服务的namespace一致
		namespace: xxx
		# 如果使用group,则要和项目中的其他服务的group一致
        group: xxx
    gateway:
      discovery:
        locator:
          # 让gateway可以发现nacos中的微服务
          enabled: true 
      routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: user-service # 当前路由的标识, 要求唯一(任意)
          uri: lb://user-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
          order: 1 #排序,越小匹配优先级越高
          predicates:
            - Path=/user/** # 当请求路径满足Path指定的规则时,才进行路由转发,/user为服务的servlet.context-path名称

转发规则

当请求满足predicates配置的路径时,会将其请求路径转发到uri路径下,比如:http://127.0.0.1:8085/user/list

上述请求中,请求地址:/user/list满足匹配规则,则会被进行转发,转发后请求为:user-service/user/list,即会被转发请求user-service服务,并且请求路径为/user/list

简化版

如果配置路由的断言匹配规则和服务在注册中心的名称一致时,可以不需要对routes进行配置,直接通过网关地址/微服务/接口访问即可。

2.3 验证测试:

启动Gateway服务service业务服务,通过访问接口Gateway地址,实现请求转发;

为了方便看执行的验证情况,service业务服务的部分代码(伪代码)如下:

# service业务服务,部门配置
server:
  port: 8083
  servlet:
    context-path: /user
    ....
    ....
// service业务服务,部分Controller代码
@RestController
public class UserController {
	@GetMapping("list")
    public void listUser() {
        System.out.println("成功请求user/list接口");
    }
}

注册中心服务列表:
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用

直接访问业务服务:
请求地址: http//localhost:8083/user/listSpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用
通过Gateway转发到业务服务:
请求地址: http//localhost:8085/user/list
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用
如果通过Gateway服务的地址能够,成功访问到业务服务,则表示Gateway服务搭建成功.

3、Http超时配置

可以为所有路由配置统一的 Http 超时(响应和连接),也可以为某个路由单独配置超时时间(会覆盖全局配置)。

3.1 全局超时配置

connect-timeout:连接超时时长,以毫秒为单位,默认值为45秒。
response-timeout:响应超时时长,以秒为单位,需要加上s,表示秒。

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 10000
        response-timeout: 5s

3.2 路由超时

connect-timeout:连接超时时长,以毫秒为单位,默认值为全局配置中的时长。
response-timeout:响应超时时长,以毫秒为单位,默认值为全局配置中的时长。

spring:
  cloud:
    gateway:
       routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          # 在metadata中设置单个路由超时时间
          metadata:
            response-timeout: 200
            connect-timeout: 200

4、跨域配置

注意:如果网关服务进行了跨域配置,那么业务服务就不要再配置跨域,否则会出现冲突导致网关跨域配置失效。

跨域配置有两种方式,通过配置yaml或者使用@Configuration注解

4.1 方式一:yaml配置

spring:
  cloud:
    gateway:
   	  globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"

4.2 方式二:@Configuration注解

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
public class GlobalCorsConfig {
    private static final String MAX_AGE = "18000L";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }

            return chain.filter(ctx);
        };
    }


    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new HiddenHttpMethodFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
                return chain.filter(exchange);
            }
        };
    }
}

5、全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,日志操作,安全性验证等功能。
1、内置过滤器
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用
2、自定义全局过滤器

内置的过滤器已经可以完成大部分的功能,但是还是需要我们自己编写过滤器来实现自定义操作,比如完成统一的权限校验。

代码实现如下:

@Configuration
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    //完成判断逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        //获取header
        HttpHeaders headers = request.getHeaders();
        // 校验token是否为空
        List<String> tokens = headers.get("token");

        if (CollectionUtils.isEmpty(tokens)) {
            System.out.println("鉴权失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            throw new RuntimeException("没有权限");
        }
        //调用chain.filter继续向下游执行
        return chain.filter(exchange);

    }

    //顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 1;
    }
}

6、请求过滤器与响应过滤器

通过请求过滤器响应过滤器实现对数据的处理,打印请求数据和响应数据等操作,代码如下:

请求过滤器(RequestGlobalFilter):

GatewayContext:

public class GatewayContext {
    /**
     * cache json body
     */
    private String cacheBody;
    /**
     * cache formdata
     */
    private MultiValueMap<String, String> formData;
    /**
     * cache reqeust path
     */
    private String path;


    public String getCacheBody() {
        return cacheBody;
    }

    public void setCacheBody(String cacheBody) {
        this.cacheBody = cacheBody;
    }

    public MultiValueMap<String, String> getFormData() {
        return formData;
    }

    public void setFormData(MultiValueMap<String, String> formData) {
        this.formData = formData;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

RequestGlobalFilter:

@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
    private final Logger log = LoggerFactory.getLogger(RequestGlobalFilter.class);

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders =
            HandlerStrategies.withDefaults().messageReaders();

    private ServerHttpRequest request = null;

    private MediaType contentType = null;

    private HttpHeaders headers = null;

    private String path = null;

    private String ip = null;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 设置开始时间戳
        exchange.getAttributes().put("start", System.currentTimeMillis());
        // 重新设置请求的IP
        // 写入客户端ip,否则在下游服务获取的ip为网关ip
        ServerHttpRequest newRequest = exchange.getRequest().mutate().header("CLIENT_IP", ip).build();
        exchange = exchange.mutate().request(newRequest).build();

        // 初始化request并且获取path
        request = exchange.getRequest();
        headers = request.getHeaders();
        contentType = headers.getContentType();
        path = request.getPath().pathWithinApplication().value();

        /*
         * save request path and serviceId into gateway context
         */
        GatewayContext gatewayContext = new GatewayContext();
        gatewayContext.setPath(path);
        exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);

        log.info("=>> Start:HttpMethod:{},Url:{}", request.getMethod(), request.getURI().getRawPath());


        if (request.getMethod() == HttpMethod.GET) {
            // 记录请求的参数信息 针对GET 请求
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            StringBuilder builder = new StringBuilder();
            for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
                builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
            }
            log.info("MethodParam:{}", builder);

        }
        if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT || request.getMethod() == HttpMethod.DELETE) {
            Mono<Void> voidMono = null;
            if (contentType != null) {
                if (StringUtils.contains(contentType.toString(), MediaType.APPLICATION_JSON.toString())) {
                    voidMono = readBody(exchange, chain, gatewayContext);
                }
                if (StringUtils.contains(contentType.toString(), MediaType.APPLICATION_FORM_URLENCODED.toString())) {
                    voidMono = readFormData(exchange, chain, gatewayContext);
                }
                if (StringUtils.contains(contentType.toString(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
                    voidMono = readFormData(exchange, chain, gatewayContext);
                }

                return voidMono;
            }
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -3;
    }

    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {

        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    /*
                     * read the body Flux<DataBuffer>, and release the buffer
                     * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature
                     * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
                     */
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer =
                                exchange.getResponse().bufferFactory().wrap(bytes);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });
                    /**
                     * repackage ServerHttpRequest
                     */
                    ServerHttpRequest mutatedRequest =
                            new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return cachedFlux;
                                }
                            };
                    /**
                     * mutate exchage with new ServerHttpRequest
                     */
                    ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                    /**
                     * read body string with default messageReaders
                     */
                    return ServerRequest.create(mutatedExchange, messageReaders)
                            .bodyToMono(String.class).doOnNext(objectValue -> {
                                //请求参数
                                log.info("MethodParam:{}", objectValue);
                                gatewayContext.setCacheBody(objectValue);
                            }).then(chain.filter(mutatedExchange));
                });
    }

    /**
     * ReadFormData
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        return exchange.getFormData()
                .doOnNext(multiValueMap -> {
                    gatewayContext.setFormData(multiValueMap);
                    //注意对文件的处理
                    log.info("MethodParam:{}", multiValueMap);
                }).then(Mono.defer(() -> {
                    Charset charset = contentType.getCharset();
                    charset = charset == null ? StandardCharsets.UTF_8 : charset;
                    String charsetName = charset.name();
                    MultiValueMap<String, String> formData =
                            gatewayContext.getFormData();

                    // formData is empty just return
                    if (null == formData || formData.isEmpty()) {
                        return chain.filter(exchange);
                    }
                    StringBuilder formDataBodyBuilder = new StringBuilder();
                    String entryKey;
                    List<String> entryValue;
                    try {

                        // repackage form data
                        for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
                            entryKey = entry.getKey();
                            entryValue = entry.getValue();
                            if (entryValue.size() > 1) {
                                for (String value : entryValue) {
                                    formDataBodyBuilder.append(entryKey).append("=")
                                            .append(
                                                    URLEncoder.encode(value, charsetName))
                                            .append("&");
                                }
                            } else {
                                formDataBodyBuilder
                                        .append(entryKey).append("=").append(URLEncoder
                                                .encode(entryValue.get(0), charsetName))
                                        .append("&");
                            }
                        }
                    } catch (UnsupportedEncodingException e) {
                        // ignore URLEncode Exception
                    }
                    /**
                     * substring with the last char '&'
                     */
                    String formDataBodyString = "";
                    if (formDataBodyBuilder.length() > 0) {
                        formDataBodyString = formDataBodyBuilder.substring(0,
                                formDataBodyBuilder.length() - 1);
                    }
                    /**
                     * get data bytes
                     */
                    byte[] bodyBytes = formDataBodyString.getBytes(charset);
                    int contentLength = bodyBytes.length;
                    ServerHttpRequestDecorator decorator =
                            new ServerHttpRequestDecorator(
                                    request) {
                                /**
                                 * change content-length
                                 *
                                 * @return
                                 */
                                @Override
                                public HttpHeaders getHeaders() {
                                    HttpHeaders httpHeaders = new HttpHeaders();
                                    httpHeaders.putAll(super.getHeaders());
                                    if (contentLength > 0) {
                                        httpHeaders.setContentLength(contentLength);
                                    } else {
                                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
                                                "chunked");
                                    }
                                    return httpHeaders;
                                }

                                /**
                                 * read bytes to Flux<Databuffer>
                                 *
                                 * @return
                                 */
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return DataBufferUtils
                                            .read(new ByteArrayResource(bodyBytes),
                                                    new NettyDataBufferFactory(
                                                            ByteBufAllocator.DEFAULT),
                                                    contentLength);
                                }
                            };
                    ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();

                    return chain.filter(mutateExchange);
                }));
    }
}

响应过滤器(ResponseGlobalFilter):

@Component
public class ResponseGlobalFilter implements GlobalFilter, Ordered {
    private final Logger log = LoggerFactory.getLogger(ResponseGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();
        ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                //记录响应日志
                long end = System.currentTimeMillis();
                long start = Long.parseLong(exchange.getAttribute("start").toString());
                long useTime = end - start;
                if (body instanceof Flux) {
                    // 获取响应 ContentType
                    String responseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    // 记录 JSON 格式数据的响应体
                    if (!StringUtils.isEmpty(responseContentType) && responseContentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        // 解决返回体分段传输
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer join = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[join.readableByteCount()];
                            join.read(content);
                            DataBufferUtils.release(join);
                            String responseData = new String(content, StandardCharsets.UTF_8);

                            String responseStr = responseData.replaceAll("\n", "").replaceAll("\t", "");
                            responseStr = responseStr.length() > 500 ? responseStr.substring(500) : responseStr;


                            log.info("=>> END: Time: {}ms", useTime);
                            log.info("RESPONSE INFO = {}", responseStr);

                            return bufferFactory.wrap(responseData.getBytes());
                        }));
                    }
                }

                return super.writeWith(body);
            }
        };

        return chain.filter(exchange.mutate().response(responseDecorator).build());
    }

    @Override
    public int getOrder() {
        return -2;
    }
}

效果:
SpringCloudAlibaba+Nacos整合Gateway网关,实现Http超时、跨域、元数据、全局异常配置使用

7、全局异常处理机制

7.1 编写统一响应类

public class ResponseObject {

    /**
     * 信息
     */
    private String msg;

    /**
     * 响应码
     */
    private int code;

    /**
     * 数据
     */
    private Object data;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    // 返回数据
    public static ResponseObject success(Object object) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(object);
        responseObject.setCode(200);
        responseObject.setMsg("操作成功");
        return responseObject;
    }

    // 返回信息
    public static ResponseObject success(String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(true);
        responseObject.setCode(200);
        responseObject.setMsg(msg);
        return responseObject;
    }
    // 直接返回
    public static ResponseObject success() {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(true);
        responseObject.setCode(200);
        responseObject.setMsg("操作成功");
        return responseObject;
    }

    // 自定义返回数据和信息
    public static ResponseObject success(Object object, String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(object);
        responseObject.setCode(200);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // 自定义返回数据和内容
    public static ResponseObject fail(Object data, String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(data);
        responseObject.setCode(500);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // 自定义返回信息
    public static ResponseObject fail(String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(400);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // 直接返回
    public static ResponseObject fail() {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(400);
        responseObject.setMsg("操作失败");
        return responseObject;
    }

    // 自定义返回信息和编码
    public static ResponseObject fail(String msg, int code) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(code);
        responseObject.setMsg(msg);
        return responseObject;
    }
}

7.2 创建ErrorHandlerConfiguration

@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;


    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }

}

7.3 创建JsonExceptionHandler

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    private final static Logger log = LoggerFactory.getLogger(JsonExceptionHandler.class);

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Throwable error = super.getError(request);
        return this.buildMessage(request, error);
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     *
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return HttpStatus.OK.value();
    }

    /**
     * 构建异常响应信息
     *
     * @param request
     * @param throwable
     * @return
     */
    private Map<String, Object> buildMessage(ServerRequest request, Throwable throwable) {

        Map<String, Object> map = new HashMap<>(8);

        log.error("[网关异常信息]请求路径:{},异常信息:{},异常类型:{}", request.path(), throwable.getMessage(), ExceptionUtils.getStackTrace(throwable));

        //封装错误信息
        map.put("message", throwable.getMessage());
        map.put("data", false);

        return map;
    }
}

7.4 修改ResponseGlobalFilter

ResponseGlobalFilter中,添加以下代码:

return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
....
....
//将服务返回的json转为标准格式
ResponseObject object = JSON.parseObject(responseData, ResponseObject.class);
//解析为object非200抛出异常
if (object.getData() != null && object.getCode() != 200 && !(boolean) object.getData())
  {
	throw new RuntimeException(object.getMsg());
  }
  ....
  ....
}

7.4 测试

在服务中,抛出异常,测试Gateway中的处理

@RestController
public class UserController {

    @GetMapping("list")
    public ResponseObject listUser() {
        // 模拟出现异常,也可以使用全局异常拦截器实现
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            return ResponseObject.fail();
        }
        return null;
    }
}
上一篇:测试进阶必备,这5款http接口自动化测试工具真的很香


下一篇:低门槛彻底理解JavaScript中的深拷贝和浅拷贝