一、文件上传原理

一个文件上传的过程如下图所示:

  1. 浏览器发起HTTP POST请求,指定请求头:
    Content-Type: multipart/form-data
  2. 服务端解析请求内容,执行文件保存处理,返回成功消息。

RFC1867 定义了HTML表单文件上传的处理机制。
通常一个文件上传的请求内容格式如下:

POST /upload HTTP/1.1
Host:xxx.org
Content-type: multipart/form-data, boundary="boundaryStr"--boundaryStr
content-disposition: form-data; name="name"Name Of Picture
--boundaryStr
Content-disposition: attachment; name="picfile"; filename="picfile.gif"
Content-type: image/gif
Content-Transfer-Encoding: binary...contents of picfile.gif...

其中boundary指定了内容分割的边界字符串;
Content-dispostion 指定了这是一个附件(文件),包括参数名称、文件名称;
Content-type 指定了文件类型;
Content-Transfer-Encoding 指定内容传输编码;

二、springboot 文件机制

springboot 的文件上传处理是基于Servlet 实现的。
在Servlet 2.5 及早期版本之前,文件上传需要借助 commons-fileupload 组件来实现。
Servlet 3.0规范之后,提供了对文件上传的原生支持,进一步简化了应用程序的实现。
Tomcat 为例,在文件上传之后通过将写入到临时文件,最终将文件实体传参到应用层,如下:

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

临时文件

临时文件的路径定义:

{temp_dir}/upload_xx_xxx.tmp

temp_dir是临时目录,通过 系统属性java.io.tmpdir指定,默认值为:

操作系统 路径
windows C:Users{username}AppDataLocalTemp\
Linux /tmp

定制配置

为了对文件上传实现定制,可以在application.properties中添加如下配置:

//启用文件上传
spring.http.multipart.enabled=true
//文件大于该阈值时,将写入磁盘,支持KB/MB单位
spring.http.multipart.file-size-threshold=0
//自定义临时路径
spring.http.multipart.location=
//最大文件大小(单个)
spring.http.multipart.maxFileSize=10MB
//最大请求大小(总体)
spring.http.multipart.maxRequestSize=10MB

其中 maxFileSize/maxRequestSize 用于声明大小限制,
当上传文件超过上面的配置阈值时,会返回400(BadRequest)的错误;
file-size-threshold是一个阈值,用于控制是否写入磁盘;
location是存储的目录,如果不指定将使用前面所述的默认临时目录。

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

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

三、示例代码

接下来以简单的代码展示文件上传处理

A. 单文件上传

    @PostMapping(value = "/single", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE)@ResponseBodypublic ResponseEntity<String> singleUpload(@RequestParam("file") MultipartFile file) {logger.info("file receive {}", file.getOriginalFilename());// 检查文件内容是否为空if (file.isEmpty()) {return ResponseEntity.badRequest().body("no file input");}// 原始文件名String fileName = file.getOriginalFilename();// 检查后缀名if (!checkImageSuffix(fileName)) {return ResponseEntity.badRequest().body("the file is not image");}// 检查大小if (!checkSize(file.getSize())) {return ResponseEntity.badRequest().body("the file is too large");}String name = save(file);URI getUri = ServletUriComponentsBuilder.fromCurrentContextPath().path("/file/get").queryParam("name", name).build(true).toUri();return ResponseEntity.ok(getUri.toString());}

在上面的代码中,我们通过Controller方法传参获得MultipartFile实体,而后是一系列的检查动作:
包括文件为空、文件后缀、文件大小,这里不做展开。
save 方法实现了简单的本地存储,如下:

    private String save(MultipartFile file) {if (!ROOT.isDirectory()) {ROOT.mkdirs();}try {String path = UUID.randomUUID().toString() + getSuffix(file.getOriginalFilename());File storeFile = new File(ROOT, path);file.transferTo(storeFile);return path;} catch (IllegalStateException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}

B. 多文件上传

与单文件类似,只需要声明MultipartFile数组参数即可:

    @PostMapping(value = "/multi", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)@ResponseBodypublic ResponseEntity<List<String>> multiUpload(@RequestParam("file") MultipartFile[] files) {logger.info("file receive count {}", files.length);List<String> uris = new ArrayList<String>();for (MultipartFile file : files) {

C. 文件上传异常

如前面所述,当文件上传大小超过限制会返回400错误,为了覆盖默认的行为,可以这样:

    @ControllerAdvice(assignableTypes = FileController.class)public class MultipartExceptionHandler {@ExceptionHandler(MultipartException.class)public ResponseEntity<String> handleUploadError(MultipartException e) {return ResponseEntity.badRequest().body("上传失败:" + e.getCause().getMessage());}}

D. Bean 配置

SpringBoot 提供了JavaBean配置的方式,前面提到的几个配置也可以这样实现:

    @Configurationpublic static class FileConfig {@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();factory.setMaxFileSize("10MB");factory.setMaxRequestSize("50MB");return factory.createMultipartConfig();}}

四、文件下载

既然解释了文件上传,自然避免不了文件下载,
文件下载非常简单,只需要包括下面两步:

  1. 读文件流;
  2. 输出到Response;

这样,尝试写一个Controller方法:

    @GetMapping(path = "/get")public ResponseEntity<Object> get(@RequestParam("name") String name) throws IOException {...File file = new File(ROOT, name);if (!file.isFile()) {return ResponseEntity.notFound().build();}if (!file.canRead()) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body("no allow to access");}Path path = Paths.get(file.getAbsolutePath());ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));return ResponseEntity.ok().contentLength(file.length()).body(resource);}

这段代码通过参数(name)来指定访问文件,之后将流写入到Response。

接下来,我们访问一个确实存在的文件,看看得到了什么?

...

!! 没错,这就是文件的内容,浏览器尝试帮你呈现了。
那么,我们所期望的下载呢? 其实,真实的下载过程应该如下图:

区别就在于,我们在返回响应时添加了Content-Disposition头,用来告诉浏览器响应内容是一个附件。
这样根据约定的协议,浏览器会帮我们完成响应的解析及下载工作。
修改上面的代码,如下:

    @GetMapping(path = "/download")public ResponseEntity<Object> download(@RequestParam("name") String name) throws IOException {if (StringUtils.isEmpty(name)) {return ResponseEntity.badRequest().body("name is empty");}if (!checkName(name)) {return ResponseEntity.badRequest().body("name is illegal");}File file = new File(ROOT, name);if (!file.isFile()) {return ResponseEntity.notFound().build();}if (!file.canRead()) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body("no allow to access");}Path path = Paths.get(file.getAbsolutePath());ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));return ResponseEntity.ok().header("Content-Disposition", "attachment;fileName=" + name).contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);}

继续尝试访问文件,此时应该能看到文件被正确下载了。

小结

文件上传开发是Web开发的基础课,从早期的Servlet + common_uploads组件到现在的SpringBoot,文件的处理已经被大大简化。

这次除了展示SpringBoot 文件上传的示例代码之外,也简单介绍了文件上传相关的协议知识点。对开发者来说,了解一点内部原理总是有好处的。

补习系列(11)-springboot 文件上传原理相关推荐

  1. SpringBoot文件上传源码解析

    一.SpringMVC文件上传源码分析前言(这部分我觉得原作者写的很好) 该如何研究SpringMVC的文件上传的源码呢? 研究源码并不是仅仅知道程序是怎样运行的,而应该从宏观的角度.不同的立场去看待 ...

  2. SpringBoot文件上传和下载

    SpringBoot文件上传和下载 一.SpringBoot文件上传 1.SpringMVC文件上传 1.Client处理 选择文件(1)form表单 选择文件 method="post&q ...

  3. 解决Springboot文件上传报错,java.io.FileNotFoundException: D:\System\Temp\tomcat.819...00.tmp (系统找不到指定的文件。)

    Springboot文件上传,csdn上的方法无非是下面这两个: imgFile.transferTo(imageFolder); // 方法一/*** 方法二* FileUtils.copyInpu ...

  4. php怎么上传函数,PHP单文件上传原理及上传函数的封装操作示例

    搜索热词 @H_404_0@本文实例讲述了PHP单文件上传原理及上传函数的封装操作.分享给大家供大家参考,具体如下: @H_404_0@表单: @H_404_0@0.PHP: 无标题文档 请选择您要上 ...

  5. php 打包上传的文件,PHP单文件上传原理及上传函数的打包

    PHP单文件上传原理及上传函数的封装 服务器(临时文件)-->指定目录,当文件进入服务器时它就是临时文件了,这时操作中要用临时文件的名称tmp_name.//在客户端设置上传文件的限制(文件类型 ...

  6. springboot文件上传下载实战 ——文件上传、下载、在线打开、删除

    springboot文件上传下载实战 文件上传 文件上传核心 UserFileController 文件上传测试 文件下载与在线打开 文件下载.在线打开核心 UserFileController 文件 ...

  7. springboot文件上传下载实战 —— 登录功能、展示所有文件

    springboot文件上传下载实战 创建项目 pom.xml 数据库建表与环境准备 建表SQL 配置文件 application.properties 整体架构 前端页面 登录页面 login.ht ...

  8. SpringBoot文件上传异常之提示The temporary upload location xxx is not valid

    SpringBoot文件上传异常之提示The temporary upload location xxx is not valid 参考文章: (1)SpringBoot文件上传异常之提示The te ...

  9. SpringBoot 文件上传 通过Content-Type和文件头判断文件类型

    SpringBoot 文件上传 通过Content-Type和文件头判断文件类型 一.关于MIME MIME的全称是Multipurpose Internet Mail Extensions,即多用途 ...

最新文章

  1. 笔记本上的小键盘计算机怎样用,笔记本小键盘怎么开,详细教您笔记本小键盘怎么开启...
  2. 二分图的最大带权匹配
  3. 工业用微型计算机笔记(14)-指令系统(9)
  4. ios 计算两个时间相差秒数_Ios中时间差的计算,NSData与NSCalendar(日历)对象
  5. python中的axis=0和1代表什么
  6. Eclipse的Git插件Egit: merge合并冲突具体解决方法
  7. 二进制,八进制,十进制,十二进制之间的转换!!!!!!!!!
  8. Python: 除matplotlib外还有哪些数据可视化库?
  9. <input type=“file“> change事件异常处理办法
  10. pmm 监控mysql、mongodb、系统
  11. 如何交付机器学习项目:一份机器学习工程开发流程指南 1
  12. Excel使用技巧之分割字符串
  13. 2022年制造业单项冠军行业研究报告
  14. h5 android上传本地视频文件,关于webview适配H5上传照片或者视频文件的方法
  15. GoGoCode 代码语言转换
  16. 『拓扑排序』「NOI2010」航空管制
  17. rockchip的调试手段
  18. PADS VX2.8 基本规则的设置方法
  19. 为了研究而玩:游戏分析的方法
  20. Linux C报错: /usr/bin/ld: cannot find -ldb

热门文章

  1. linux内核网络协议栈--数据包的数据收发简略流程图(二十八)
  2. 电脑温度测试软件_网购电脑够便宜?坑连着坑真的不好躲
  3. 20164317《网络对抗技术》Exp9 Web安全基础
  4. 环境/---Liunx环境安装
  5. python笔记22-literal_eval函数处理返回json中的单双引号
  6. js进阶正则表达式方括号(方括号作用)(js正则是在双正斜杠之中:/[a-z]/g)...
  7. MySQL Workbench 怎么创建数据库
  8. ubuntu搭配lamp
  9. cefsharp wpf 中文输入问题解决方法
  10. 在jsp页面中实现格式化数字,百分比,货币