package com.xiaopeng.payment.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 修复 multipart 文件上传被 ReplaceRequestFilter 破坏的问题。
*
* ReplaceRequestFilter(来自 request-wrapper jar,logan-starter-core 传递依赖)
* 会读取 request 的 InputStream 来包装请求体,导致后续 Spring 解析 multipart 时
* 抛出 "Stream closed" 异常。
*
* 解决方案:注册一个高优先级 Filter,对 multipart 请求提前触发 Servlet 容器的
* multipart 解析(通过调用 request.getParts()),使文件数据被缓存到临时文件中。
* 这样即使后续 ReplaceRequestFilter 消费了 InputStream,multipart 数据仍然可用。
*/
@Slf4j
@Configuration
public class MultipartFilterConfig {
private static final String MULTIPART = "multipart/";
@Bean
public FilterRegistrationBean earlyMultipartParsingFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
registration.setFilter(new EarlyMultipartParsingFilter());
registration.addUrlPatterns("/*");
// 确保在 ReplaceRequestFilter 之前执行
registration.setOrder(Integer.MIN_VALUE + 10);
registration.setName("earlyMultipartParsingFilter");
return registration;
}
/**
* 在 filter 链最前面对 multipart 请求提前解析,
* 让 Tomcat 将文件数据缓存到磁盘,避免后续 filter 消费 InputStream 导致 Stream closed。
*/
static class EarlyMultipartParsingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String contentType = httpRequest.getContentType();
if (contentType != null && contentType.toLowerCase().startsWith(MULTIPART)) {
// 提前触发 multipart 解析,Tomcat 会将 parts 缓存
try {
httpRequest.getParts();
} catch (Exception e) {
log.warn("Early multipart parsing failed, will retry later: {}", e.getMessage());
}
}
}
chain.doFilter(request, response);
}
}
}