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

 本文转载自微信公众号「Java大厂面试官」,作者laker。转载本文请联系Java大厂面试官公众号。

文件上传

概述

Spring支持可插拔的MultipartResolver对象进行文件上传。目前有2个实现;

  • 在Servlet 2.5 及早期版本之前,文件上传需要借助 commons-fileupload 组件来实现。
  • 从Servlet 3.0规范之后,提供了对文件上传的原生支持,进一步简化了应用程序的实现。

commons-fileupload

要使用commons-fileupload的CommonsMultipartResolver处理文件上传,我们需要添加以下依赖项:

 
 
 
 
  1.     commons-fileupload
  2.     commons-fileupload

配置定义CommonsMultipartResolver bean。

 
 
 
 
  1. @Bean(name = "multipartResolver")
  2. public CommonsMultipartResolver multipartResolver() {
  3.     CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
  4.     multipartResolver.setMaxUploadSize(100000);
  5.     return multipartResolver;
  6. }

Servlet 3.0

SpringBoot项目参见MultipartAutoConfiguration.java类,默认会自动配置StandardServletMultipartResolver,我们不需要做任何事情,就能使用了。

 
 
 
 
  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
  3. @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
  4. @ConditionalOnWebApplication(type = Type.SERVLET)
  5. @EnableConfigurationProperties(MultipartProperties.class)
  6. public class MultipartAutoConfiguration {
  7.  private final MultipartProperties multipartProperties;
  8.  public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
  9.   this.multipartProperties = multipartProperties;
  10.  }
  11.  @Bean
  12.  @ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
  13.  public MultipartConfigElement multipartConfigElement() {
  14.   return this.multipartProperties.createMultipartConfig();
  15.  }
  16.  @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
  17.  @ConditionalOnMissingBean(MultipartResolver.class)
  18.  public StandardServletMultipartResolver multipartResolver() {
  19.   StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
  20.   multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
  21.   return multipartResolver;
  22.  }
  23. }

常见文件上传相关需求,我整理总结如下:

单文件上传

前端核心代码

 
 
 
 
  1.     
  2.         
  3.             
  4.         
  5.         
  6.             
  7.         
  8.     

后端核心代码

 
 
 
 
  1. @RequestMapping(value = "/upload-file", method = RequestMethod.POST)
  2. public String submit(@RequestParam("file") MultipartFile file) {
  3.     return "ok";
  4. }

多文件上传

前端核心代码

  
 
 
 
  1.     
  2.         
  3.             
  4.             
  5.         
  6.         
  7.             
  8.             
  9.         
  10.         
  11.             
  12.             
  13.         
  14.         
  15.             
  16.         
  17.     
  18. Select a file to upload
    Select a file to upload
    Select a file to upload

后端核心代码

我们需要注意每个输入字段具有相同的名称,以便可以将其作为MultipartFile数组进行访问:

 
 
 
 
  1. @RequestMapping(value = "/upload-files", method = RequestMethod.POST)
  2.    public String submit(@RequestParam("files") MultipartFile[] files) {
  3.        return "ok";
  4.    }

带其他参数的文件上传

前端核心代码

 
 
 
 
  1.     
  2.         
  3.             
  4.             
  5.         
  6.         
  7.             
  8.             
  9.         
  10.         
  11.             
  12.             
  13.         
  14.         
  15.             
  16.         
  17.     
  18. Name
    Email
    Select a file to upload

后端核心代码

“在控制器中,我们可以使用@RequestParam注解获取所有表单数据,也可以不使用@RequestParam获取

 
 
 
 
  1. @PostMapping("/upload-files-with-data")
  2. public String submit(
  3.             @RequestParam MultipartFile file, @RequestParam String name,
  4.             String email) {
  5.     return "ok";
  6. }

优雅的后端实现

我们还可以将所有表单字段封装在类中,当文件中有很多其他字段时,就很方便。

 
 
 
 
  1. public class FormDataWithFile {
  2.     private String name;
  3.     private String email;
  4.     private MultipartFile file;
  5. }
  6. @PostMapping("/upload-files-with-data")
  7. public String submit(FormDataWithFile formDataWithFile) {
  8.     return "ok";
  9. }

多个(文件+参数)上传

功能需求类似于上传如下请求:

 
 
 
 
  1. [
  2.     {
  3.         "name": "a",
  4.         "emainl": "b",
  5.         "file":
  6.     },
  7.     {
  8.         "name": "a",
  9.         "emainl": "",
  10.         "file":
  11.     }
  12. ]

但是这样写是行不通的,解决方案如下:

方案一:上传文件Base64

把文件转为base64字符串,但是转换后的字符串大小是原图片大小的3倍。(慎用)

 
 
 
 
  1. [
  2.     {
  3.         "name": "a",
  4.         "emainl": "",
  5.         "fileBase64":"xxxxx"
  6.     },
  7.     {
  8.         "name": "b",
  9.         "emainl": "",
  10.         "fileBase64":"xxxxx"
  11.     }
  12. ]

方案二:上传文件url

先把图片上传到服务器,获取文件url,然后再把文件的URL与其他参数上传到后端

 
 
 
 
  1. [
  2.     {
  3.         "name": "a",
  4.         "emainl": "",
  5.         "fileUrl":"xxxxx.png"
  6.     },
  7.     {
  8.         "name": "b",
  9.         "emainl": "",
  10.         "fileUrl":"xxxxx.png"
  11.     }
  12. ]

文件上传原理

通常一个文件上传的请求内容格式如下:

 
 
 
 
  1. POST /upload HTTP/1.1 
  2. Host:xxx.org 
  3. Content-type: multipart/form-data, boundary="boundaryStr"
  4. --boundaryStr
  5. content-disposition: form-data; name="name"
  6. Name Of Picture
  7. --boundaryStr
  8. Content-disposition: attachment; name="picfile"; filename="picfile.gif"
  9. Content-type: image/gif
  10. Content-Transfer-Encoding: binary
  11. ...contents of picfile.gif...

其中 boundary 指定了内容分割的边界字符串;

Content-dispostion 指定了这是一个附件(文件),包括参数名称、文件名称;

Content-type 指定了文件类型;

Content-Transfer-Encoding 指定内容传输编码;

Tomcat 实现了 Servlet3.0 规范,通过ApplicationPart对文件上传流实现封装, 其中,DiskFileItem 描述了上传文件实体,在请求解析时生成该对象, 需要关注的是,DiskFileItem 声明了一个临时文件,用于临时存储上传文件的内容, SpringMVC 对上层的请求实体再次封装,最终构造为MultipartFile传递给应用程序。示例如下:

生成的临时文件如下:

这个是临时文件的目录,可以配置的

临时文件打开,查看其内容如下:

参数:name

参数:file

上传完毕后,临时文件会删除

“可以看到,不是file类型的参数也会写入到临时文件。

通过Fiddler进行抓包:

 
 
 
 
  1. POST http://localhost:8080/upload-files-with-data HTTP/1.1
  2. cache-control: no-cache
  3. Accept: */*
  4. Host: localhost:8080
  5. accept-encoding: gzip, deflate
  6. content-type: multipart/form-data; boundary=--------------------------895818005136536360125479
  7. content-length: 268707
  8. Connection: keep-alive
  9. ----------------------------895818005136536360125479
  10. Content-Disposition: form-data; name="name"
  11. 123
  12. ----------------------------895818005136536360125479
  13. Content-Disposition: form-data; name="file"; filename="test.txt"
  14. Content-Type: text/plain
  15. abc123
  16. ----------------------------895818005136536360125479
  17. Content-Disposition: form-data; name="file"; filename="1114289-20190110120111312-1475461850.png"
  18. Content-Type: image/png
  19. ...contents of png...
  20. ----------------------------895818005136536360125479--

到这里,我们就大概就知道了HTTP上传文件的原理了。HTTP把需要上传的表单的所有数据按照一定的格式存放在请求体中,对于文件也是同样的。

  • Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqj67FUBQUHXZj78G表示要上传附件,
  • 其中boundary表示分隔符,如果表单中有多项,就要使用boundary进行分隔,每个表单项由------FormBoundary开始,以------FormBoundary结尾。例如这样:
 
 
 
 
  1. ------FormBoundary
  2. Content-Disposition: form-data; name="param1"
  3. value1
  4. ------FormBoundary

这个boundary的值是由浏览器生成的,由浏览器来保证与上传内容不重复。

  • 在每个分隔项里,需要我们去重点关注Content-Disposition消息头,其中第一个参数总是固定不变的form-data,name表示表单元素属性名,回车换行符后面的内容就是元素的值。还有Content-Type表示我们上传的文件的MIME类型,我们在服务器端需要根据这个进行文件的区分。
  • 最后一个boundary的结尾会多两个--

HTTP就是按照这种格式,把表单中的数据封装成一个请求一股脑的发给服务器端,服务器端根据这种规则对接收到的请求进行解析,从而完成文件上传功能。

下面是从网上找的一个后台解析示例。可以DEBUG跟踪代码去分析。

 
 
 
 
  1. @WebServlet(urlPatterns = "/lakerfile")
  2. public class FileUploadDemo extends HttpServlet {
  3.     @Override
  4.     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  5.         DiskFileItemFactory fac = new DiskFileItemFactory();
  6.         ServletFileUpload upload = new ServletFileUpload(fac);
  7.         upload.setFileSizeMax(10 * 1024 * 1024);
  8.         upload.setSizeMax(20 * 1024 * 1024);
  9.         if (ServletFileUpload.isMultipartContent(request)) { // 只处理Multipart请求
  10.                 List list = upload.parseRequest(new ServletRequestContext(request));// 解析报文
  11.                 for (FileItem item : list) {
  12.                     if (item.isFormField()) {
  13.                         String fileName = item.getFieldName();
  14.                         String value = item.getString("UTF-8");
  15.                     } else {
  16.                         File file = new File(realPath, name);
  17.                         item.write(file);
  18.                         ...
  19.         }
  20.     }
  21. }

遇到的问题

Spring Boot上传文件大小限制

 
 
 
 
  1. spring:
  2.   servlet:
  3.     multipart:
  4.       # 最大文件大小(单个)
  5.       max-file-size: 10MB
  6.       # 文件大于该阈值时,将写入磁盘,支持B/KB/MB单位
  7.       file-size-threshold: 0B
  8.       # //最大请求大小(总体)
  9.       max-request-size: 100MB

这几个参数由SpringMVC控制,用于注入 Servlet3.0 的文件上传配置,关联类如下:

 
 
 
 
  1. public class MultipartConfigElement {
  2.     private final String location;// = "";
  3.     private final long maxFileSize;// = -1;
  4.     private final long maxRequestSize;// = -1;
  5.     private final int fileSizeThreshold;// = 0;

上传文件过大异常拦截

 
 
 
 
  1. @ExceptionHandler(MaxUploadSizeExceededException.class)
  2. public Response handleMaxSizeException(MaxUploadSizeExceededException e) {
  3.     log.error(e.getMessage(), e);
  4.     return Response.error(500, "File too large!");
  5. }

自定义tomcat工作目录

自定义临时文件生成目录

 
 
 
 
  1. server:
  2.   tomcat:
  3.     basedir: /laker/tmp

使用swagger上传文件不起作用

  • allowMultiple=true:表示是数组格式的参数
  • dataType = "__file":表示数组中参数的类型
 
 
 
 
  1. @ApiOperation(value = "上传", notes = "上传")
  2. @ApiImplicitParams({
  3.             @ApiImplicitParam(paramType = "form", name = "file", value = "文件对象", required = true, dataType = "__file"),
  4.             @ApiImplicitParam(paramType = "form", name = "files", value = "文件数组", allowMultiple = true, dataType = "__file")
  5.     })
  6. public void test(@RequestParam("file") MultipartFile file, @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception {
  7. }

参考:

https://www.cnblogs.com/yougewe/p/12916211.html

https://www.baeldung.com/spring-file-upload

网页名称:从零搭建开发脚手架SpringBoot文件上传的多种方式、原理及遇到的问题
文章位置:http://www.csdahua.cn/qtweb/news43/333543.html

网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网