SpringCloud Gateway 打印请求响应日志、跨域全局配置

version:spring-cloud 2021.0.1,spring-boot 2.6.3,spring-cloud-alibaba 2021.0.1.0

SpringCloudGateway中Post请求参数只能读取一次。

这是因为Gateway网关默认使用的是SpringWebflux,解决这个问题需要容重新构造一个request来替换原先的request。

CacheBodyGlobalFilter这个全局过滤器把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。

//

过滤器的Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置高的原因是某些系统内置的过滤器可能也会去读body。

Request Log
java 复制代码
@Configuration
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        //String path = request.getPath().value();
        String path = request.getPath().pathWithinApplication().value();//打印请求路径
        String requestUrl = this.getOriginalRequestUrl(exchange);//打印请求url
        String method = request.getMethodValue();
        //cors
        HttpHeaders headers = request.getHeaders();
        
        log.info("--> method: {} url: {} header: {}", method, requestUrl, headers);
        if ("POST".equals(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        String bodyString = new String(bytes, StandardCharsets.UTF_8);
                        log.info("--> {}", bodyString);
                        //log.info("--> {}", formatStr(bodyString)); //formData
                        exchange.getAttributes().put("POST_BODY", bodyString);
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(request) {
                            @Override
                            public Flux<DataBuffer> getBody() { return cachedFlux; }
                        };
                        
                        return chain.filter(exchange.mutate().request(mutatedRequest).build());
                    });
        } else if ("GET".equals(method)) {
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            log.info("请求参数:" + queryParams);
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }

    private String getOriginalRequestUrl(ServerWebExchange exchange) {
        ServerHttpRequest req = exchange.getRequest();
        LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        URI requestUri = uris.stream().findFirst().orElse(req.getURI());
        MultiValueMap<String, String> queryParams = req.getQueryParams();
        //打印 /api/rest/feign/order/detail
        // return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();

        return requestUri.toString(); // http://localhost:8091/api/rest/feign/order/detail
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
    /**
     * 去掉FormData 空格,换行和制表符
     */
    private static String formatStr(String str){
        if (str != null && str.length() > 0) {
            Pattern p = Pattern.compile("\\s*|\t|\r|\n");
            Matcher m = p.matcher(str);
            return m.replaceAll("");
        }
        return str;
    }}

Response Log

响应报文内容分段传输导致不全的解决方法:

1.大报文对fluxBody流循环拼接处理,把fluxBody.map变为fluxBody.buffer().map,从而可以foreach循环Body体了。

2.排除Excel导出。

3.对JSON统一格式处理,日期统一格式处理

java 复制代码
@Configuration
public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                return chain.filter(exchange);//降级处理返回数据
            }
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            
                            // 合并多个流集合,解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer buff = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[buff.readableByteCount()];
                            buff.read(content);
                            DataBufferUtils.release(buff);//释放掉内存
                            
                            //排除Excel导出,不是application/json不打印。若请求是上传图片则在最上面判断。
                            MediaType contentType = originalResponse.getHeaders().getContentType();
                            if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                                return bufferFactory.wrap(content);
                            }
                            
                            // 构建返回日志
                            String joinData = new String(content);
                            String result = modifyBody(joinData);
                            List<Object> rspArgs = new ArrayList<>();
                            rspArgs.add(originalResponse.getStatusCode().value());
                            rspArgs.add(exchange.getRequest().getURI());
                            rspArgs.add(result);
                            log.info("<-- {} {}\n{}", rspArgs.toArray());

                            getDelegate().getHeaders().setContentLength(result.getBytes().length);
                            return bufferFactory.wrap(result.getBytes());
                        }));
                    } else {
                        log.error("<-- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            return chain.filter(exchange.mutate().response(decoratedResponse).build());

        } catch (Exception e) {
            log.error("gateway log exception.\n" + e);
            return chain.filter(exchange);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    //返回统一的JSON日期数据 2024-02-23 11:00, null转空字符串
    private String modifyBody(String jsonStr){
        JSONObject json = JSON.parseObject(jsonStr, Feature.AllowISO8601DateFormat);
        JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm";
        return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) -> value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);
    }
}


//    public void setJSONObjectWriteNullStringAsEmpty() {
//    JSON.toJSONString(data,SerializerFeature.WriteNullStringAsEmpty);
//    JSONObject jsonObj = JSON.parseObject(data, Feature.AllowISO8601DateFormat);
//    JSON.toJSONString(data,SerializerFeature.DisableCircularReferenceDetect);
//    JSONObject.DEFFAULT_DATE_FORMAT ="yyyy-MM-dd HH:mm";
//    JSONObject.toJSONString(jsonObject,SerializerFeature.WriteDateUseDateFormat);
//
//    String dataJson = JSON.toJSONString(data, (ValueFilter) (object, name, value) -> {
//        log.info("data:{} ", data);
//
//        log.info("object:{}, name:{}, value:{}", object, name, value);
//        if (value == null) {
//            return "";
//        }
//        return value;
//    });
//    JSONObject jsonObject = new JSONObject();
//    把json对象转换成字节数组
//    byte[] bits = data.getBytes(StandardCharsets.UTF_8);
//    DataBuffer buffer = originalResponse.bufferFactory().wrap(bits);
//    originalResponse.writeWith(Mono.just(buffer));
//    }

}
CorsWebFilterConfig 跨域配置

Gateway跨域处理

https://blog.csdn.net/weixin_43730516/article/details/127040628

java 复制代码
@Configuration
public class CorsWebFilterConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfigurationSource corsConfigurationSource = new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(ServerWebExchange serverWebExchange) {
                CorsConfiguration corsConfig = new CorsConfiguration();
                corsConfig.addAllowedHeader("*");
                corsConfig.addAllowedMethod("*");
                corsConfig.addAllowedOriginPattern("*");
                corsConfig.setMaxAge(1800L);
                corsConfig.setAllowCredentials(true);
                return corsConfig;
            }
        };
        return new CorsWebFilter(corsConfigurationSource);
} }

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_19636353/article/details/126759522

相关推荐
pan_junbiao18 分钟前
SpringBoot使用@Slf4j注解实现日志输出
java·spring boot·spring
计算机学姐1 小时前
基于SpringBoot+Vue的高校实习管理系统
java·vue.js·spring boot·后端·spring·intellij-idea·mybatis
Bulut09072 小时前
SpringCloud 2023 LoadBalancer介绍、使用、获取服务列表原理、负载均衡算法
spring cloud·负载均衡算法·loadbalancer·获取服务列表原理·客户端负载均衡
编啊编程啊程3 小时前
一文上手SpringSecurity【九】
java·spring boot·redis·spring cloud·json
WHabcwu3 小时前
Spring Web MVC课后作业
java·前端·后端·spring·html·mvc
百成Java4 小时前
基于Spring Boot的校园管理系统
java·开发语言·spring boot·后端·mysql·spring
弥琉撒到我4 小时前
微服务nacos解析部署使用全流程
java·spring cloud·微服务·nacos
国通快递驿站5 小时前
助力企业信息化,开源免费工作流引擎AntFlow推出重榜功能tidb支持,为工作流引擎水平扩展提供无限可能
java·spring boot·spring·开源·tidb·activiti
weixin_438197385 小时前
Linux之Docker虚拟化部署
spring cloud·docker·容器