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

相关推荐
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
hanbarger4 小时前
mybatis框架——缓存,分页
java·spring·mybatis
龙少95437 小时前
【深入理解@EnableCaching】
java·后端·spring
啦啦右一13 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
荆州克莱16 小时前
mysql中局部变量_MySQL中变量的总结
spring boot·spring·spring cloud·css3·技术
zquwei16 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
火烧屁屁啦17 小时前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
岁岁岁平安17 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
北辰浮光17 小时前
[spring]XML配置文件标签
xml·spring
ZSYP-S18 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring