补习系列(11)-springboot 文件上传原理
一、文件上传原理
一个文件上传的过程如下图所示:
- 浏览器发起HTTP POST请求,指定请求头:
Content-Type: multipart/form-data - 服务端解析请求内容,执行文件保存处理,返回成功消息。
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();}}
四、文件下载
既然解释了文件上传,自然避免不了文件下载,
文件下载非常简单,只需要包括下面两步:
- 读文件流;
- 输出到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 文件上传原理相关推荐
- SpringBoot文件上传源码解析
一.SpringMVC文件上传源码分析前言(这部分我觉得原作者写的很好) 该如何研究SpringMVC的文件上传的源码呢? 研究源码并不是仅仅知道程序是怎样运行的,而应该从宏观的角度.不同的立场去看待 ...
- SpringBoot文件上传和下载
SpringBoot文件上传和下载 一.SpringBoot文件上传 1.SpringMVC文件上传 1.Client处理 选择文件(1)form表单 选择文件 method="post&q ...
- 解决Springboot文件上传报错,java.io.FileNotFoundException: D:\System\Temp\tomcat.819...00.tmp (系统找不到指定的文件。)
Springboot文件上传,csdn上的方法无非是下面这两个: imgFile.transferTo(imageFolder); // 方法一/*** 方法二* FileUtils.copyInpu ...
- php怎么上传函数,PHP单文件上传原理及上传函数的封装操作示例
搜索热词 @H_404_0@本文实例讲述了PHP单文件上传原理及上传函数的封装操作.分享给大家供大家参考,具体如下: @H_404_0@表单: @H_404_0@0.PHP: 无标题文档 请选择您要上 ...
- php 打包上传的文件,PHP单文件上传原理及上传函数的打包
PHP单文件上传原理及上传函数的封装 服务器(临时文件)-->指定目录,当文件进入服务器时它就是临时文件了,这时操作中要用临时文件的名称tmp_name.//在客户端设置上传文件的限制(文件类型 ...
- springboot文件上传下载实战 ——文件上传、下载、在线打开、删除
springboot文件上传下载实战 文件上传 文件上传核心 UserFileController 文件上传测试 文件下载与在线打开 文件下载.在线打开核心 UserFileController 文件 ...
- springboot文件上传下载实战 —— 登录功能、展示所有文件
springboot文件上传下载实战 创建项目 pom.xml 数据库建表与环境准备 建表SQL 配置文件 application.properties 整体架构 前端页面 登录页面 login.ht ...
- SpringBoot文件上传异常之提示The temporary upload location xxx is not valid
SpringBoot文件上传异常之提示The temporary upload location xxx is not valid 参考文章: (1)SpringBoot文件上传异常之提示The te ...
- SpringBoot 文件上传 通过Content-Type和文件头判断文件类型
SpringBoot 文件上传 通过Content-Type和文件头判断文件类型 一.关于MIME MIME的全称是Multipurpose Internet Mail Extensions,即多用途 ...
最新文章
- 笔记本上的小键盘计算机怎样用,笔记本小键盘怎么开,详细教您笔记本小键盘怎么开启...
- 二分图的最大带权匹配
- 工业用微型计算机笔记(14)-指令系统(9)
- ios 计算两个时间相差秒数_Ios中时间差的计算,NSData与NSCalendar(日历)对象
- python中的axis=0和1代表什么
- Eclipse的Git插件Egit: merge合并冲突具体解决方法
- 二进制,八进制,十进制,十二进制之间的转换!!!!!!!!!
- Python: 除matplotlib外还有哪些数据可视化库?
- <input type=“file“> change事件异常处理办法
- pmm 监控mysql、mongodb、系统
- 如何交付机器学习项目:一份机器学习工程开发流程指南 1
- Excel使用技巧之分割字符串
- 2022年制造业单项冠军行业研究报告
- h5 android上传本地视频文件,关于webview适配H5上传照片或者视频文件的方法
- GoGoCode 代码语言转换
- 『拓扑排序』「NOI2010」航空管制
- rockchip的调试手段
- PADS VX2.8 基本规则的设置方法
- 为了研究而玩:游戏分析的方法
- Linux C报错: /usr/bin/ld: cannot find -ldb
热门文章
- linux内核网络协议栈--数据包的数据收发简略流程图(二十八)
- 电脑温度测试软件_网购电脑够便宜?坑连着坑真的不好躲
- 20164317《网络对抗技术》Exp9 Web安全基础
- 环境/---Liunx环境安装
- python笔记22-literal_eval函数处理返回json中的单双引号
- js进阶正则表达式方括号(方括号作用)(js正则是在双正斜杠之中:/[a-z]/g)...
- MySQL Workbench 怎么创建数据库
- ubuntu搭配lamp
- cefsharp wpf 中文输入问题解决方法
- 在jsp页面中实现格式化数字,百分比,货币