puzhibing
2026-04-11 b6f3de8451867d85242b2bd706fcfa4a65df4ff7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.xiaopeng.map.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 破坏的问题。
 * <p>
 * ReplaceRequestFilter(来自 request-wrapper jar,logan-starter-core 传递依赖)
 * 会读取 request 的 InputStream 来包装请求体,导致后续 Spring 解析 multipart 时
 * 抛出 "Stream closed" 异常。
 * <p>
 * 解决方案:注册一个高优先级 Filter,对 multipart 请求提前触发 Servlet 容器的
 * multipart 解析(通过调用 request.getParts()),使文件数据被缓存到临时文件中。
 * 这样即使后续 ReplaceRequestFilter 消费了 InputStream,multipart 数据仍然可用。
 */
@Slf4j
@Configuration
public class MultipartFilterConfig {
 
    private static final String MULTIPART = "multipart/";
    @Bean
    public FilterRegistrationBean<Filter> earlyMultipartParsingFilter() {
        FilterRegistrationBean<Filter> 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);
        }
    }
 
}