一、场景
在写一个统一文件上传的时候,在采用MultipartFile对象接收参数时会出现无法传递或者一直为空情况。
二、根因分析
出现上述问题主要根因如下:
-
前后端参数名对应不一致,即
@RequestPart(value = "multipartFile",required = false) MultipartFile multipartFile
解决; -
关闭文件上传支持,即
spring.servlet.multipart.enabled=false
; -
配置文件中指定了文件上传时的大小值问题,即配置如下
spring: servlet: multipart: enabled: false max-file-size: 10MB max-request-size: 10MB
-
切换内嵌容器tomcat到undertow的配置问题;
-
指定了临时文件站,但路径不存在即
spring.servlet.multipart.location=/tmp
; -
多次读取HttpServletRequest流;
-
Spring Boot已经有
CommonsMultipartResolver
,需要排除原有的Multipart配置@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
,此种情况在Spring boot3.x版本中不存在,主要是在Spring boot3.x版本中CommonsMultipartResolver
已经不存在。
针对上述原因除了第六种比较隐蔽比较深的外,其余解决容易发现解决。
三、包装request处理多次读流的无法获取MultipartFile分析
(一)原因
处理Multipart/form类型和别的post类型都不太一样。x-www-form-urlencoded
和普通post携带参数都是直接从request的inpustream里读取,Multipart/form类型你需要获取Parts 即request.getParts
。
在缓存body[]时已经将流读取。Multipart获取方式和普通表单并不一样。在重写方法时并没有对getParts单独重写处理即可。
(二)解决方式
-
处理Multipart/form时请缓存Parts.重写getParts方法,可以看一下,可以看下Springmvc是怎么处理Multipart/form和普通的application/x-www-form-urlencoded的或者参考下request的getParts方法;
-
过滤器做兼容性处理即可。完整代码
Request包装类
@Slf4j public class RepeatBodyRequestWrapper extends HttpServletRequestWrapper { private final byte[] bodyByteArray; private final Map<String, String[]> parameterMap; public RepeatBodyRequestWrapper(HttpServletRequest request) { super(request); this.bodyByteArray = getByteBody(request); this.parameterMap = super.getParameterMap(); } @Override public BufferedReader getReader() { return ObjectUtils.isEmpty(this.bodyByteArray) ? null : new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bodyByteArray); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { // doNoting } @Override public int read() { return byteArrayInputStream.read(); } }; } private static byte[] getByteBody(HttpServletRequest request) { byte[] body = new byte[0]; try { body = StreamUtils.copyToByteArray(request.getInputStream()); } catch (IOException e) { log.error("解析流中数据异常", e); } return body; } /** * 重写 getParameterMap() 方法 解决 undertow 中流被读取后,会进行标记,从而导致无法正确获取 body 中的表单数据的问题 * * @return Map<String, String [ ]> parameterMap */ @Override public Map<String, String[]> getParameterMap() { return this.parameterMap; } }
过滤器处理
@Component @WebFilter(urlPatterns = "/*") public class RepeatBodyRequestWrapperFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String contentType = httpServletRequest.getContentType(); if (contentType == null) { // 表单请求 filterChain.doFilter(servletRequest, servletResponse); } else if (contentType.startsWith("multipart/")) { // 文件上传类型 filterChain.doFilter(servletRequest, servletResponse); } else if (contentType.startsWith("application/json")) { // json请求 ServletRequest requestWrapper = new RepeatBodyRequestWrapper((HttpServletRequest) servletRequest); filterChain.doFilter(requestWrapper, servletResponse); } } }
经过上述改造,即可解决该问题。