当前位置: 首页 > 科技观察

从零搭建开发脚手架SpringBoot文件上传的各种方法、原理及遇到的问题

时间:2023-03-17 15:38:31 科技观察

本文转载自微信公众号《Java大厂面试官》,作者laker。转载本文请联系Java大厂面试官公众号。文件上传概述Spring支持用于文件上传的可插入MultipartResolver对象。目前有2个实现;在Servlet2.5之前,文件上传是在commons-fileupload组件的帮助下实现的。Servlet3.0规范之后,原生支持文件上传,进一步简化了应用的实现。commons-fileupload要使用commons-fileupload的CommonsMultipartResolver来处理文件上传,我们需要添加如下依赖:commons-fileuploadcommons-fileupload配置定义CommonsMultipartResolverbean。@Bean(name="multipartResolver")publicCommonsMultipartResolvermultipartResolver(){CommonsMultipartResolvermultipartResolver=newCommonsMultipartResolver();multipartResolver.setMaxUploadSize(100000);returnmultipartResolver;}Servlet3.0SpringBoot项目参见MultipartAutoConfiguration.java类,默认会自动配置StandardServletMultipartResolver,我们不需要做任何东西,你可以使用它。@Configuration(proxyBeanMethods=false)@ConditionalOnClass({Servlet.class,StandardServletMultipartResolver.class,MultipartConfigElement.class})@ConditionalOnProperty(prefix="spring.servlet.multipart",name="enabled",matchIfMissing=true)@ConditionalOnWebApplication(type=Type.SERVLET)@EnableConfigurationProperties(MultipartProperties.class)publicclassMultipartAutoConfiguration{privatefinalMultipartPropertiesmultipartProperties;publicMultipartAutoConfiguration(MultipartPropertiesmultipartProperties){this.multipartProperties=multipartProperties;}@Bean@ConditionalOnMissingBean({MultipartConfigElement.class,CommonsMultipartResolver.class})publicMultipartConfigElementmultipartConfigElement(){returnthis.multipartProperties.createMultipartConfig();}@Bean(name=DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)@ConditionalOnMissingBean(MultipartResolver.class)publicStandardServletMultipartResolvermultipartResolver(){StandardServletMultipartResolvermultipartResolver=newStandardServletMultipartResolver();multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());returnmultipartResolver;}}常见的文件上传相关需求,我总结如下:单文件上传前端核心代码

后端核心代码@RequestMapping(value="/upload-file",method=RequestMethod.POST)publicStringsubmit(@RequestParam("file")MultipartFilefile){return"ok";}后端核心代码我们需要注意每个输入字段具有相同的名称,以便它可以作为MultipartFile数组访问:@RequestMapping(value="/upload-files",method=RequestMethod.POST)publicStringsubmit(@RequestParam("files")MultipartFile[]files){return"ok";}其他参数文件上传前端核心代码
Name
电子邮件
Selectafiletoupload
后端核心代码”在controller中,我们可以使用@RequestParam注解获取所有表单数据,也可以不用@PostMapping获取using@RequestParam("/upload-files-with-data")publicStringsubmit(@RequestParamMultipartFilefile,@RequestParamStringname,Stringemail){return"ok";}优雅的后端实现我们也可以将所有的表单字段封装在一个类中,当有的时候当你有很多其他领域时很方便publicclassFormDataWithFile{privateStringname;privateStringemail;privateMultipartFilefile;}@PostMapping("/upload-files-with-data")publicStringsubmit(FormDataWithFileformDataWithFile){return"ok";}Multiple(file+parameter)上传功能需求类似上传如下请求:[{"name":"a","emainl":"b","file":},{"name":"a","emainl":"","file":}]但这是如果还不行,解决方法如下:解决方法一:上传文件Base64,将文件转成base64字符串,但转换后的字符串大小是原图的3倍。(谨慎使用)[{"name":"a","emainl":"","fileBase64":"xxxxx"},{"name":"b","emainl":"","fileBase64":"xxxxx"}]方案二:上传文件url先上传图片到服务器,获取文件url,然后将文件url和其他参数上传到后台[{"name":"a","emainl":"","fileUrl":"xxxxx.png"},{"name":"b","emainl":"","fileUrl":"xxxxx.png"}]文件上传的原则通常是文件上传请求内容格式如下:POST/uploadHTTP/1.1Host:xxx.orgContent-type:multipart/form-data,boundary="boundaryStr"--boundaryStrcontent-disposition:form-data;name="name"NameOfPicture--boundaryStrContent-disposition:attachment;name="picfile";filename="picfile.gif"Content-type:image/gifContent-Transfer-Encoding:binary...contentsofpicfile.gif...其中边界指定边界string用于内容分割;Content-dispostion指定这是一个附件(文件),包括参数名和文件名;Content-type指定文件类型;内容-Transfer-Encoding指定内容传输编码;Tomcat实现了Servlet3.0规范,通过ApplicationPart封装了文件上传流。其中,DiskFileItem描述了上传文件的实体,在解析请求时生成。需要注意的是,DiskFileItem语句Atemporaryfile用于临时存放上传文件的内容。SpringMVC再次封装了上层请求实体,最后构造了一个MultipartFile传递给应用程序。示例如下:生成的临时文件如下:这是临时文件的目录,可以配置打开临时文件,内容如下:参数:名称参数:文件上传后,临时文件将被删除”可以看到不属于文件类型的参数也会被写入到临时文件中。通过Fiddler抓包:POSThttp://localhost:8080/upload-files-with-dataHTTP/1.1cache-control:no-cacheAccept:*/*Host:localhost:8080accept-encoding:gzip,deflatecontent-type:multipart/形式-data;boundary=------------------------895818005136536360125479content-length:268707Connection:keep-alive------------------------895818005136536360125479Content-Disposition:form-data;name="name"123----------------------------895818005136536360125479Content-Disposition:form-data;name="file";filename="test.txt"Content-Type:text/plainabc123---------------------------895818005136536360125479Content-Disposition:form-data;name="file";filename="1114289-20190110120111312-1475461850.png"Content-Type:image/png...contentsofpng...----------------------------895818005136536360125479--至此,我们大概知道了HTTP文件上传的原理。HTTP将要上传的表单的所有数据按照一定的格式存储在请求体中,文件也是如此。内容类型:多部分/表单数据;boundary=----WebKitFormBoundaryqj67FUBQUHXZj78G表示要上传附件,boundary表示分隔符。如果表单中有多个项目,则必须用边界分隔它们。每个表单项由---以---FormBoundary开始并以------FormBoundary结束。例如:------FormBoundaryContent-Disposition:form-data;name="param1"value1------FormBoundary边界的值由浏览器生成,浏览器保证与上传内容重复。在每个分隔项中,我们需要重点关注Content-Disposition消息头,其中第一个参数始终是固定的form-data,name表示表单元素的属性名,回车换行后的内容是元素的值。另外Content-Type表示我们上传的文件的MIME类型,我们需要在服务端根据这个来区分文件。最后一个边界还会有两个结局——HTTP就是基于这种格式,将表单中的数据封装成一个request发送给server端。服务器端根据这个规则解析接收到的请求,从而完成文件上传功能。下面是在网上找的后台解析的例子。您可以分析DEBUG跟踪代码。@WebServlet(urlPatterns="/lakerfile")publicclassFileUploadDemoextendsHttpServlet{@OverridepublicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{DiskFileItemFactoryfac=newDiskFileItemFactory();ServletFileUploadupload=newServletFileUpload(fac);upload.setFileSizeMax(10*1024*1024);upload.setSizeMax(20*1024*1024);if(ServletFileUpload.isMultipartContent(request)){//只处理Multipart请求Listlist=upload.parseRequest(newServletRequestContext(request));//解析消息for(FileItemitem:list){if(item.isFormField()){StringfileName=item.getFieldName();Stringvalue=item.getString("UTF-8");}else{Filefile=newFile(realPath,name);item.write(文件);...}}}SpringBoot上传文件大小限制遇到的问题spring:servlet:multipart:#最大文件大小(单个)max-file-size:10MB#当文件大于阈值时,会写入磁盘,supportB/KB/MBunitfile-size-threshold:0B#//最大请求大小(整体)max-request-size:100MB这些参数由SpringMVC控制,用于注入Servlet3.0文件上传配置和关联类如下:publicclassMultipartConfigElement{privatfinalStringlocation;//="";privatefinallongmaxFileSize;//=-1;privatefinallongmaxRequestSize;//=-1;privatefinalintfileSizeThreshold;//=0;上传文件过大异常拦截@ExceptionHandler(MaxUploadSizeExceededException.class)publicResponsehandleMaxSizeException{logMaxUploadSizeExceed).error(e.getMessage(),e);returnResponse.error(500,"Filetoolarge!");}自定义tomcat工作目录自定义临时文件生成目录server:tomcat:basedir:/laker/tmp使用swagger上传文件不起作用allowMultiple=true:表示数组格式的参数dataType="__file":表示数组中参数的类型@ApiOperation(value="upload",notes="upload")@ApiImplicitParams({@ApiImplicitParam(paramType="form",name="file",value="文件对象",required=true,dataType="__file"),@ApiImplicitParam(paramType="form",name="files",value="filearray",allowMultiple=true,dataType="__file")})publicvoidtest(@RequestParam("file")MultipartFilefile,@RequestParam(value="files",required=false)MultipartFile[]files)throwsException{}参考:https://www.cnblogs.com/yougewe??/p/12916211.htmlhttps://www.baeldung.com/spring-file-upload