实现一个Spring Cloud Gateway的GlobalFilter,具体要求是打印请求参数、请求body、请求头、Cookie、URI和响应结果,同时限制每项内容不超过1000字符,排除文件上传下载,包含import引用
时间: 2025-08-29 09:20:58 AIGC 浏览: 9
<think>好的,我现在需要帮助用户解决在Spring Cloud Gateway中创建自定义GlobalFilter的问题。用户的需求是记录请求参数、Body、Header、Cookie、URI和响应结果,同时限制每项内容不超过1000字符,还要排除文件上传和下载的场景。首先,我得理清实现这个功能的关键步骤和可能遇到的问题。
首先,GlobalFilter的基本结构我比较熟悉。用户需要打印请求和响应的详细信息,所以需要分别处理请求和响应两个阶段。对于请求部分,可能需要拦截请求,获取参数、头信息、Cookie、URI,以及请求体。响应部分则需要处理响应体,同时记录状态码等信息。
接下来,关于请求体的获取,这里有个问题:在Spring WebFlux中,请求体的数据流只能被消费一次,如果在过滤器中读取了,后面的处理可能无法再读取。所以需要用缓存的方式,例如使用ServerHttpRequestDecorator来包装请求,这样后续的处理还能获取到请求体。同样,响应体也需要用ServerHttpResponseDecorator来包装。
然后是内容长度的限制。用户要求每项内容不超过1000字符,所以需要在读取参数、头、Cookie、URI和请求/响应体时,截取前1000个字符。例如,使用substring方法或者StringUtils的abbreviate方法来实现截断。
接下来是排除文件上传和下载的场景。文件上传通常是通过POST请求,Content-Type可能是multipart/form-data,而下载可能涉及特定的路径或Content-Type如application/octet-stream。可以通过检查请求的Content-Type头或URI路径来排除这些情况。例如,如果请求的Content-Type包含multipart或者路径中包含/upload/,则跳过处理。
代码结构方面,GlobalFilter需要实现GlobalFilter接口,并重写filter方法。在方法中,先判断是否是文件上传或下载,如果是则直接放行,不记录日志。否则,记录请求信息,然后继续处理链,最后处理响应信息。
对于请求参数的获取,可以通过ServerWebExchange的请求对象获取查询参数。请求头、Cookie和URI可以直接从请求对象中获取。请求体的处理需要读取缓存的内容,并转换为字符串,同时注意字符限制。
响应信息的处理需要在过滤器链执行完成后,通过装饰响应对象来获取响应体和状态码。同样,响应体的内容需要读取并截断到1000字符。
需要注意异步处理的问题,因为WebFlux是基于响应式编程的,所有的操作都应该是非阻塞的。在读取请求体和响应体时,需要使用相应的操作符,如flatMap、map等,确保不会阻塞线程。
此外,需要处理可能出现的异常,比如在读取请求体或响应体时发生错误,应该记录异常信息,同时不影响请求的正常处理。
最后,需要编写示例代码,包含必要的import语句,确保用户能够直接复制使用,并根据实际情况调整包路径和日志配置。
现在,总结一下步骤:
1. 创建GlobalFilter实现类,实现GlobalFilter接口。
2. 在filter方法中,先判断是否为文件上传/下载请求,如果是则跳过。
3. 对于非文件请求,记录请求参数、头、Cookie、URI和请求体,并截断到1000字符。
4. 使用装饰器包装请求和响应,以便读取内容。
5. 处理响应时,记录状态码、头和响应体,同样截断到1000字符。
6. 添加必要的日志输出和异常处理。
在代码实现中,需要注意使用ServerHttpRequestDecorator和ServerHttpResponseDecorator来包装原始请求和响应,确保后续处理能正常进行。同时,使用DataBufferUtils.join来读取请求体和响应体的内容,并将其转换为字符串,但要注意内存使用,避免大文件导致内存溢出,不过用户已经排除了文件上传下载,所以这个问题可能影响较小。
另外,关于内容类型的判断,可能需要检查Content-Type头是否包含multipart或者application/octet-stream,或者路径中包含上传下载的关键字,比如/upload或/download。这部分需要根据用户的具体需求来调整,示例中可能需要使用正则表达式或简单的字符串包含来判断。
最后,测试代码时需要注意不同场景下的表现,确保文件上传下载被正确排除,日志内容正确截断,并且不影响网关的正常路由功能。</think>### 分步实现方案
#### 1. 创建全局过滤器类
```java
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
@Component
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 排除文件上传下载场景(根据Content-Type或路径判断)
if (isFileRequest(exchange.getRequest())) {
return chain.filter(exchange);
}
// 记录请求信息
logRequest(exchange.getRequest());
// 包装请求和响应以读取Body
ServerHttpRequest wrappedRequest = wrapRequest(exchange.getRequest());
ServerHttpResponse wrappedResponse = wrapResponse(exchange.getResponse());
return chain.filter(exchange.mutate().request(wrappedRequest).response(wrappedResponse).build())
.doOnSuccess(aVoid -> logResponse(exchange.getResponse()));
}
private boolean isFileRequest(ServerHttpRequest request) {
String contentType = request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
return (contentType != null && (contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)
|| contentType.contains(MediaType.APPLICATION_OCTET_STREAM_VALUE)))
|| request.getURI().getPath().contains("/upload")
|| request.getURI().getPath().contains("/download");
}
private void logRequest(ServerHttpRequest request) {
String params = truncate(request.getQueryParams().toString(), 1000);
String headers = truncate(request.getHeaders().toString(), 1000);
String cookies = truncate(request.getCookies().toString(), 1000);
String uri = request.getURI().toString();
System.out.printf("Request: URI=%s\nParams=%s\nHeaders=%s\nCookies=%s%n",
uri, params, headers, cookies);
}
private ServerHttpRequest wrapRequest(ServerHttpRequest request) {
return new ServerHttpRequestDecorator(request) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(super.getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
return headers;
}
@Override
public Mono<byte[]> getBody() {
return DataBufferUtils.join(super.getBody())
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
String body = truncate(new String(bytes, StandardCharsets.UTF_8), 1000);
System.out.println("Request Body: " + body);
return bytes;
});
}
};
}
private ServerHttpResponse wrapResponse(ServerHttpResponse response) {
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(org.springframework.core.io.buffer.DataBuffer... buffers) {
return Mono.fromRunnable(() -> {
String body = truncate(DataBufferUtils.join(buffers)
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}).blockOptional().orElse(""), 1000);
System.out.println("Response Body: " + body);
}).then(super.writeWith(buffers));
}
};
}
private void logResponse(ServerHttpResponse response) {
String status = String.valueOf(response.getStatusCode());
String headers = truncate(response.getHeaders().toString(), 1000);
System.out.printf("Response: Status=%s\nHeaders=%s%n", status, headers);
}
private String truncate(String input, int maxLength) {
return input != null ?
input.substring(0, Math.min(input.length(), maxLength)) : "";
}
@Override
public int getOrder() {
return -1; // 高优先级
}
}
```
#### 2. 关键实现点说明
1. **文件上传下载排除**
通过检查`Content-Type`头包含`multipart/form-data`或`application/octet-stream`,以及URI路径包含`/upload`/`/download`实现[^2]
2. **请求/响应Body读取**
使用`ServerHttpRequestDecorator`和`ServerHttpResponseDecorator`包装原始对象,通过`DataBufferUtils`安全读取字节流
3. **内容长度限制**
`truncate()`方法统一处理字符串截断,确保输出不超过1000字符
4. **日志输出位置**
示例使用`System.out`,生产环境建议替换为`Slf4j`日志框架
#### 3. 注意事项
- 文件类型检测逻辑需根据实际业务扩展
- 高并发场景建议异步记录日志
- 生产环境建议增加异常处理逻辑
阅读全文
相关推荐




















