当前位置: 首页 > news >正文

解决Required request part ‘file‘ is not present

在这里插入图片描述

背景

起因:调用上传图片接口出现错误Required request part 'file' is not present

CURL:

curl --location 'localhost:8080/file/upload' \
--form 'file=@"/C:/Users/Leaf/Pictures/头像/a.jpg"'

检查了接口传参Content-Type​类型正常,为multipart/form-data

报错异常:

org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not presentat org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValueInternal(RequestParamMethodArgumentResolver.java:213) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:193) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:114) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.30.jar:5.3.30]at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.30.jar:5.3.30]at javax.servlet.http.HttpServlet.service(HttpServlet.java:555) ~[tomcat-embed-core-9.0.82.jar:4.0.FR]at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.30.jar:5.3.30]at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) ~[tomcat-embed-core-9.0.82.jar:4.0.FR]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at com.leaf.filter.RequestFilter.doFilter(RequestFilter.java:30) ~[classes/:na]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar:5.3.30]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar:5.3.30]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.30.jar:5.3.30]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar:5.3.30]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1794) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.82.jar:9.0.82]at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

分析原因

  1. 代码中请求过滤器处理请求之前,调用过getParameterMap()方法,该方法是通过读取请求的输入流来获取数据的。关键点在于:Servlet 的输入流只能被读取一次
  2. 代码中配置了CommonsMultipartResolver​解析器来解析文件请求,这个解析器也是通过读取请求的输入流来获取数据,因此读取流的时候失败,因为输入流已经被消费了;
  3. 而如果不配置 MultipartResolver ,Spring Boot 会使用默认的 StandardServletMultipartResolver​,它是基于 Servlet 3.0+ 的 Part​ API 实现的,而不是直接读取输入流。它的核心是 request.getParts()​ 方法;
  4. Servlet 容器在第一次调用 getParts()​ 时会解析整个请求体,将解析结果缓存在请求对象中,后续调用 getParts()​ 时直接返回缓存的结果,而不是重新解析。

CommonsMultipartResolver​和StandardServletMultipartResolver​主要区别:

  1. 解析时机

    • StandardServletMultipartResolver:依赖 Servlet 容器的 getParts()​ 方法,只解析一次
    • CommonsMultipartResolver:直接读取输入流,每次都需要重新解析
  2. 数据存储

    • StandardServletMultipartResolver:数据由 Servlet 容器缓存
    • CommonsMultipartResolver:需要自己管理数据
  3. 性能影响

    • StandardServletMultipartResolver:第一次解析后,后续调用都是使用缓存
    • CommonsMultipartResolver:每次都需要重新解析

建议

  1. 如果使用 Servlet 3.0+ 环境,优先使用 StandardServletMultipartResolver
  2. 如果需要更细粒度的控制或自定义解析逻辑,才考虑使用 CommonsMultipartResolver
  3. 如果使用 CommonsMultipartResolver,要避免在解析前调用 request.getParameterMap()

源码

getParameterMap()​部分源码:

@Overridepublic Map<String,String[]> getParameterMap() {if (parameterMap.isLocked()) {return parameterMap;}Enumeration<String> enumeration = getParameterNames();while (enumeration.hasMoreElements()) {String name = enumeration.nextElement();String[] values = getParameterValues(name);parameterMap.put(name, values);}parameterMap.setLocked(true);return parameterMap;}@Overridepublic Enumeration<String> getParameterNames() {if (!parametersParsed) {parseParameters();}return coyoteRequest.getParameters().getParameterNames();}protected void parseParameters() {parametersParsed = true;Parameters parameters = coyoteRequest.getParameters();boolean success = false;try {// Set this every time in case limit has been changed via JMXint maxParameterCount = getConnector().getMaxParameterCount();if (parts != null && maxParameterCount > 0) {maxParameterCount -= parts.size();}parameters.setLimit(maxParameterCount);// getCharacterEncoding() may have been overridden to search for// hidden form field containing request encodingCharset charset = getCharset();boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();parameters.setCharset(charset);if (useBodyEncodingForURI) {parameters.setQueryStringCharset(charset);}// Note: If !useBodyEncodingForURI, the query string encoding is// that set towards the start of CoyoteAdapter.service()parameters.handleQueryParameters();if (usingInputStream || usingReader) {success = true;return;}String contentType = getContentType();if (contentType == null) {contentType = "";}int semicolon = contentType.indexOf(';');if (semicolon >= 0) {contentType = contentType.substring(0, semicolon).trim();} else {contentType = contentType.trim();}if ("multipart/form-data".equals(contentType)) {parseParts(false);success = true;return;}// ...success = true;} finally {if (!success) {parameters.setParseFailedReason(FailReason.UNKNOWN);}}}// parts.add(part); 后续用默认的StandardServletMultipartResolver解析,会调用getPart()能够从缓存拿到数据
private void parseParts(boolean explicit) {// Return immediately if the parts have already been parsedif (parts != null || partsParseException != null) {return;}Context context = getContext();MultipartConfigElement mce = getWrapper().getMultipartConfigElement();if (mce == null) {if (context.getAllowCasualMultipartParsing()) {mce = new MultipartConfigElement(null, connector.getMaxPostSize(), connector.getMaxPostSize(),connector.getMaxPostSize());} else {if (explicit) {partsParseException = new IllegalStateException(sm.getString("coyoteRequest.noMultipartConfig"));return;} else {parts = Collections.emptyList();return;}}}int maxParameterCount = getConnector().getMaxParameterCount();Parameters parameters = coyoteRequest.getParameters();parameters.setLimit(maxParameterCount);boolean success = false;try {File location;String locationStr = mce.getLocation();if (locationStr == null || locationStr.length() == 0) {location = ((File) context.getServletContext().getAttribute(ServletContext.TEMPDIR));} else {// If relative, it is relative to TEMPDIRlocation = new File(locationStr);if (!location.isAbsolute()) {location = new File((File) context.getServletContext().getAttribute(ServletContext.TEMPDIR),locationStr).getAbsoluteFile();}}if (!location.exists() && context.getCreateUploadTargets()) {log.warn(sm.getString("coyoteRequest.uploadCreate", location.getAbsolutePath(),getMappingData().wrapper.getName()));if (!location.mkdirs()) {log.warn(sm.getString("coyoteRequest.uploadCreateFail", location.getAbsolutePath()));}}if (!location.isDirectory()) {parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);partsParseException = new IOException(sm.getString("coyoteRequest.uploadLocationInvalid", location));return;}// Create a new file upload handlerDiskFileItemFactory factory = new DiskFileItemFactory();try {factory.setRepository(location.getCanonicalFile());} catch (IOException ioe) {parameters.setParseFailedReason(FailReason.IO_ERROR);partsParseException = ioe;return;}factory.setSizeThreshold(mce.getFileSizeThreshold());ServletFileUpload upload = new ServletFileUpload();upload.setFileItemFactory(factory);upload.setFileSizeMax(mce.getMaxFileSize());upload.setSizeMax(mce.getMaxRequestSize());if (maxParameterCount > -1) {// There is a limit. The limit for parts needs to be reduced by// the number of parameters we have already parsed.// Must be under the limit else parsing parameters would have// triggered an exception.upload.setFileCountMax(maxParameterCount - parameters.size());}parts = new ArrayList<>();try {List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));int maxPostSize = getConnector().getMaxPostSize();int postSize = 0;Charset charset = getCharset();for (FileItem item : items) {ApplicationPart part = new ApplicationPart(item, location);parts.add(part);if (part.getSubmittedFileName() == null) {String name = part.getName();if (maxPostSize >= 0) {// Have to calculate equivalent size. Not completely// accurate but close enough.postSize += name.getBytes(charset).length;// Equals signpostSize++;// Value lengthpostSize += part.getSize();// Value separatorpostSize++;if (postSize > maxPostSize) {parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);throw new IllegalStateException(sm.getString("coyoteRequest.maxPostSizeExceeded"));}}String value = null;try {value = part.getString(charset.name());} catch (UnsupportedEncodingException uee) {// Not possible}parameters.addParameter(name, value);}}success = true;} catch (InvalidContentTypeException e) {parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE);partsParseException = new ServletException(e);} catch (SizeException e) {parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);checkSwallowInput();partsParseException = new IllegalStateException(e);} catch (IOException e) {parameters.setParseFailedReason(FailReason.IO_ERROR);partsParseException = e;} catch (IllegalStateException e) {// addParameters() will set parseFailedReasoncheckSwallowInput();partsParseException = e;}} finally {// This might look odd but is correct. setParseFailedReason() only// sets the failure reason if none is currently set. This code could// be more efficient but it is written this way to be robust with// respect to changes in the remainder of the method.if (partsParseException != null || !success) {parameters.setParseFailedReason(FailReason.UNKNOWN);}}}

CommonsMultipartResolver​部分源码:

@Overridepublic MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {Assert.notNull(request, "Request must not be null");if (this.resolveLazily) {return new DefaultMultipartHttpServletRequest(request) {@Overrideprotected void initializeMultipart() {MultipartParsingResult parsingResult = parseRequest(request);setMultipartFiles(parsingResult.getMultipartFiles());setMultipartParameters(parsingResult.getMultipartParameters());setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());}};}else {MultipartParsingResult parsingResult = parseRequest(request);return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());}}protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {String encoding = determineEncoding(request);FileUpload fileUpload = prepareFileUpload(encoding);try {List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);return parseFileItems(fileItems, encoding);}catch (FileUploadBase.SizeLimitExceededException ex) {throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);}catch (FileUploadBase.FileSizeLimitExceededException ex) {throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);}catch (FileUploadException ex) {throw new MultipartException("Failed to parse multipart servlet request", ex);}}@Overridepublic List<FileItem> parseRequest(HttpServletRequest request)throws FileUploadException {return parseRequest(new ServletRequestContext(request));}public List<FileItem> parseRequest(RequestContext ctx)throws FileUploadException {List<FileItem> items = new ArrayList<FileItem>();boolean successful = false;try {FileItemIterator iter = getItemIterator(ctx);FileItemFactory fac = getFileItemFactory();final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];if (fac == null) {throw new NullPointerException("No FileItemFactory has been set.");}while (iter.hasNext()) {if (items.size() == fileCountMax) {// The next item will exceed the limit.throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());}final FileItemStream item = iter.next();// Don't use getName() here to prevent an InvalidFileNameException.final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);items.add(fileItem);try {Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new IOFileUploadException(format("Processing of %s request failed. %s",MULTIPART_FORM_DATA, e.getMessage()), e);}final FileItemHeaders fih = item.getHeaders();fileItem.setHeaders(fih);}successful = true;return items;} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new FileUploadException(e.getMessage(), e);} finally {if (!successful) {for (FileItem fileItem : items) {try {fileItem.delete();} catch (Exception ignored) {// ignored TODO perhaps add to tracker delete failure list somehow?}}}}}public FileItemIterator getItemIterator(RequestContext ctx)throws FileUploadException, IOException {try {return new FileItemIteratorImpl(ctx);} catch (FileUploadIOException e) {// unwrap encapsulated SizeExceptionthrow (FileUploadException) e.getCause();}}// ctx.getInputStream() 调用的是ServletRequestContext的getInputStream()方法来读取流
FileItemIteratorImpl(RequestContext ctx)throws FileUploadException, IOException {if (ctx == null) {throw new NullPointerException("ctx parameter");}String contentType = ctx.getContentType();if ((null == contentType)|| (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {throw new InvalidContentTypeException(format("the request doesn't contain a %s or %s stream, content type header is %s",MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));}@SuppressWarnings("deprecation") // still has to be backward compatiblefinal int contentLengthInt = ctx.getContentLength();final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())// Inline conditional is OK here CHECKSTYLE:OFF? ((UploadContext) ctx).contentLength(): contentLengthInt;// CHECKSTYLE:ONInputStream input; // N.B. this is eventually closed in MultipartStream processingif (sizeMax >= 0) {if (requestSize != -1 && requestSize > sizeMax) {throw new SizeLimitExceededException(format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",Long.valueOf(requestSize), Long.valueOf(sizeMax)),requestSize, sizeMax);}// N.B. this is eventually closed in MultipartStream processinginput = new LimitedInputStream(ctx.getInputStream(), sizeMax) {@Overrideprotected void raiseError(long pSizeMax, long pCount)throws IOException {FileUploadException ex = new SizeLimitExceededException(format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",Long.valueOf(pCount), Long.valueOf(pSizeMax)),pCount, pSizeMax);throw new FileUploadIOException(ex);}};} else {input = ctx.getInputStream();}String charEncoding = headerEncoding;if (charEncoding == null) {charEncoding = ctx.getCharacterEncoding();}boundary = getBoundary(contentType);if (boundary == null) {IOUtils.closeQuietly(input); // avoid possible resource leakthrow new FileUploadException("the request was rejected because no multipart boundary was found");}notifier = new MultipartStream.ProgressNotifier(listener, requestSize);try {multi = new MultipartStream(input, boundary, notifier);} catch (IllegalArgumentException iae) {IOUtils.closeQuietly(input); // avoid possible resource leakthrow new InvalidContentTypeException(format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);}multi.setHeaderEncoding(charEncoding);skipPreamble = true;findNextItem();}@Overridepublic InputStream getInputStream() throws IOException {return request.getInputStream();}

StandardServletMultipartResolver​部分源码:

@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);}public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)throws MultipartException {super(request);if (!lazyParsing) {parseRequest(request);}}// request.getParts() 调用的是getParameterMap()中的parts数组,这时候可以拿到缓存数据
private void parseRequest(HttpServletRequest request) {try {Collection<Part> parts = request.getParts();this.multipartParameterNames = new LinkedHashSet<>(parts.size());MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());for (Part part : parts) {String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);ContentDisposition disposition = ContentDisposition.parse(headerValue);String filename = disposition.getFilename();if (filename != null) {if (filename.startsWith("=?") && filename.endsWith("?=")) {filename = MimeDelegate.decode(filename);}files.add(part.getName(), new StandardMultipartFile(part, filename));}else {this.multipartParameterNames.add(part.getName());}}setMultipartFiles(files);}catch (Throwable ex) {handleParseFailure(ex);}}

解决方案

  1. 使用默认的StandardServletMultipartResolver​;

  2. 使用CommonsMultipartResolver​,需要额外引入依赖commons-fileupload​,且手动提前解析multipart请求:

    // 手动解析 multipart 请求
    HttpServletRequest processedRequest = httpRequest;
    if (isMultipartRequest(httpRequest)) {try {processedRequest = multipartResolver.resolveMultipart(httpRequest);} catch (Exception e) {log.warn("Failed to parse multipart request", e);}
    }// 罪魁祸首,getParameterMap()中解析了multipart请求,消费了请求流,当请求到达Controller时,再次尝试解析而找不到文件报错
    Map<String, String[]> parameterMap = httpRequest.getParameterMap();chain.doFilter(processedRequest, response);
    

相关文章:

  • 《操作系统真相还原》——初探内存
  • 虚拟斯德哥尔摩症候群:用户为何为缺陷AI辩护?
  • 涂胶协作机器人解决方案 | Kinova Link 6 Cobot在涂胶工业的方案应用与价值
  • ArcGIS Pro 3.4 二次开发 - 共享
  • 近几年字节飞书测开部分面试题整理
  • hadoop集群启动没有datanode解决
  • 自动化生产线,IT部署一站式解决方案-Infortrend KS私有云安全,一机多用
  • CortexON:开源的多代理AI系统无缝自动化和简化日常任务
  • 拉深工艺模块——回转体拉深件毛坯尺寸的确定(二)
  • 网络攻防技术十三:网络防火墙
  • 电工基础【6】顺序、时间控制
  • [Java 基础]枚举
  • ubutu修改网关
  • 分类预测 | Matlab实现CNN-BiLSTM-Attention高光谱数据分类预测
  • VSCode 工作区配置文件通用模板创建脚本
  • JavaScript中的正则表达式:文本处理的瑞士军刀
  • IDEA集成JRebel插件,实现实时热部署
  • Python-正则表达式(re 模块)
  • docker生命周期
  • idea根据类的全限定名搜索
  • wordpress底部信息后台修改/连云港网站seo
  • 网站建设和钱/徐州seo外包公司
  • 邢台做网站推广的地方/阿里云建站
  • 苏州软件开发公司哪家好/seo优化收费
  • 网站开发图片多打开速度慢/怎样把产品放到网上销售
  • 国内做网站哪家好/高级搜索百度