1. 文件上传下载
1.1 上传介绍
1.1.1 概述
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
表单属性 | 取值 | 说明 |
method | post | 必须选择post方式提交 |
enctype | multipart/form-data | 采用multipart格式上传文件 |
type | file | 使用input的file控件上传 |
1.1.2 前端介绍
1). 简单html页面表单
1.<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#117700"><</span><span style="color:#117700">form</span> <span style="color:#0000cc">method</span>=<span style="color:#aa1111">"post"</span> <span style="color:#0000cc">action</span>=<span style="color:#aa1111">"/common/upload"</span> <span style="color:#0000cc">enctype</span>=<span style="color:#aa1111">"multipart/form-data"</span><span style="color:#117700">></span> <span style="color:#117700"><</span><span style="color:#117700">input</span> <span style="color:#0000cc">name</span>=<span style="color:#aa1111">"myFile"</span> <span style="color:#0000cc">type</span>=<span style="color:#aa1111">"file"</span> <span style="color:#117700">/></span> <span style="color:#117700"><</span><span style="color:#117700">input</span> <span style="color:#0000cc">type</span>=<span style="color:#aa1111">"submit"</span> <span style="color:#0000cc">value</span>=<span style="color:#aa1111">"提交"</span> <span style="color:#117700">/></span> <span style="color:#117700"></</span><span style="color:#117700">form</span><span style="color:#117700">></span></span></span>
2). ElementUI中提供的upload上传组件
目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。
1.1.3 服务端介绍
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
- commons-fileupload
- commons-io
而Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">/**</span> <span style="color:#aa5500">* 文件上传</span> <span style="color:#aa5500">* @param file</span> <span style="color:#aa5500">* @return</span> <span style="color:#aa5500">*/</span> <span style="color:#555555">@PostMapping</span>(<span style="color:#aa1111">"/upload"</span>) <span style="color:#770088">public</span> <span style="color:#000000">R</span><span style="color:#981a1a"><</span><span style="color:#008855">String</span><span style="color:#981a1a">></span> <span style="color:#0000ff">upload</span>(<span style="color:#000000">MultipartFile</span> <span style="color:#000000">file</span>){ <span style="color:#000000">System</span>.<span style="color:#000000">out</span>.<span style="color:#000000">println</span>(<span style="color:#000000">file</span>); <span style="color:#770088">return</span> <span style="color:#000000">R</span>.<span style="color:#000000">success</span>(<span style="color:#000000">fileName</span>); }</span></span>
1.2 下载介绍
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。通过浏览器进行文件下载,通常有两种表现形式:
1). 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
2). 直接在浏览器中打开
而我们在今天所需要实现的菜品展示,表现形式为在浏览器中直接打开。
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
1.3 上传代码实现
1.3.1 前端代码
文件上传,我们作为服务端工程师,主要关注服务端代码实现。对于前端页面,可以使用ElementUI提供的上传组件。可以直接使用资料中提供的上传页面,位置:资料/文件上传下载页面/upload.html,将其拷贝到项目的目录(resources/backend/page/demo)下,启动项目,访问上传页面。
http://localhost:8080/backend/page/demo/upload.html
在上述的浏览器抓取的网络请求中,上传文件的调用url,在哪里配置的呢,这个时候,我们需要去看一下前端上传组件。
虽然上述是ElementUI封装的代码,但是实际上最终还通过file域上传文件,如果未指定上传文件的参数名,默认为file。
1.3.2 服务端实现
1). application.yml
需要在application.yml中定义文件存储路径
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">reggie</span><span style="color:#555555">:</span> <span style="color:#221199"> path</span><span style="color:#555555">: </span>D<span style="color:#555555">:</span>\img\</span></span>
2). CommonController
编写文件上传的方法, 通过MultipartFile类型的参数即可接收上传的文件, 方法形参的名称需要与页面的file域的name属性一致。
所在包: com.itheima.reggie.controller
上传逻辑:
1). 获取文件的原始文件名, 通过原始文件名获取文件后缀
2). 通过UUID重新声明文件名, 文件名称重复造成文件覆盖
3). 创建文件存放目录
4). 将上传的临时文件转存到指定位置
代码实现:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#770088">import</span> <span style="color:#000000">com</span>.<span style="color:#000000">itheima</span>.<span style="color:#000000">reggie</span>.<span style="color:#000000">common</span>.<span style="color:#000000">R</span>; <span style="color:#770088">import</span> <span style="color:#000000">lombok</span>.<span style="color:#000000">extern</span>.<span style="color:#000000">slf4j</span>.<span style="color:#000000">Slf4j</span>; <span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">beans</span>.<span style="color:#000000">factory</span>.<span style="color:#000000">annotation</span>.<span style="color:#000000">Value</span>; <span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">bind</span>.<span style="color:#000000">annotation</span>.<span style="color:#981a1a">*</span>; <span style="color:#770088">import</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">web</span>.<span style="color:#000000">multipart</span>.<span style="color:#000000">MultipartFile</span>; <span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">io</span>.<span style="color:#000000">File</span>; <span style="color:#770088">import</span> <span style="color:#000000">java</span>.<span style="color:#000000">util</span>.<span style="color:#000000">UUID</span>; <span style="color:#aa5500">/**</span> <span style="color:#aa5500">* 文件上传和下载</span> <span style="color:#aa5500">*/</span> <span style="color:#555555">@RestController</span> <span style="color:#555555">@RequestMapping</span>(<span style="color:#aa1111">"/common"</span>) <span style="color:#555555">@Slf4j</span> <span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">CommonController</span> { <span style="color:#555555">@Value</span>(<span style="color:#aa1111">"${reggie.path}"</span>) <span style="color:#770088">private</span> <span style="color:#008855">String</span> <span style="color:#000000">basePath</span>; <span style="color:#aa5500">/**</span> <span style="color:#aa5500">* 文件上传</span> <span style="color:#aa5500">* @param file</span> <span style="color:#aa5500">* @return</span> <span style="color:#aa5500">*/</span> <span style="color:#555555">@PostMapping</span>(<span style="color:#aa1111">"/upload"</span>) <span style="color:#770088">public</span> <span style="color:#000000">R</span><span style="color:#981a1a"><</span><span style="color:#008855">String</span><span style="color:#981a1a">></span> <span style="color:#000000">upload</span>(<span style="color:#000000">MultipartFile</span> <span style="color:#000000">file</span>){ <span style="color:#aa5500">//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除</span> <span style="color:#000000">log</span>.<span style="color:#000000">info</span>(<span style="color:#000000">file</span>.<span style="color:#000000">toString</span>()); <span style="color:#aa5500">//原始文件名</span> <span style="color:#008855">String</span> <span style="color:#000000">originalFilename</span> <span style="color:#981a1a">=</span> <span style="color:#000000">file</span>.<span style="color:#000000">getOriginalFilename</span>();<span style="color:#aa5500">//abc.jpg</span> <span style="color:#008855">String</span> <span style="color:#000000">suffix</span> <span style="color:#981a1a">=</span> <span style="color:#000000">originalFilename</span>.<span style="color:#000000">substring</span>(<span style="color:#000000">originalFilename</span>.<span style="color:#000000">lastIndexOf</span>(<span style="color:#aa1111">"."</span>)); <span style="color:#aa5500">//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖</span> <span style="color:#008855">String</span> <span style="color:#000000">fileName</span> <span style="color:#981a1a">=</span> <span style="color:#000000">UUID</span>.<span style="color:#000000">randomUUID</span>().<span style="color:#000000">toString</span>() <span style="color:#981a1a">+</span> <span style="color:#000000">suffix</span>;<span style="color:#aa5500">//dfsdfdfd.jpg</span> <span style="color:#aa5500">//创建一个目录对象</span> <span style="color:#000000">File</span> <span style="color:#000000">dir</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">File</span>(<span style="color:#000000">basePath</span>); <span style="color:#aa5500">//判断当前目录是否存在</span> <span style="color:#770088">if</span>(<span style="color:#981a1a">!</span><span style="color:#000000">dir</span>.<span style="color:#000000">exists</span>()){ <span style="color:#aa5500">//目录不存在,需要创建</span> <span style="color:#000000">dir</span>.<span style="color:#000000">mkdirs</span>(); } <span style="color:#770088">try</span> { <span style="color:#aa5500">//将临时文件转存到指定位置</span> <span style="color:#000000">file</span>.<span style="color:#000000">transferTo</span>(<span style="color:#770088">new</span> <span style="color:#000000">File</span>(<span style="color:#000000">basePath</span> <span style="color:#981a1a">+</span> <span style="color:#000000">fileName</span>)); } <span style="color:#770088">catch</span> (<span style="color:#000000">IOException</span> <span style="color:#000000">e</span>) { <span style="color:#000000">e</span>.<span style="color:#000000">printStackTrace</span>(); } <span style="color:#770088">return</span> <span style="color:#000000">R</span>.<span style="color:#000000">success</span>(<span style="color:#000000">fileName</span>); } } </span></span>
1.3.3 测试
代码编写完成之后,我们重新启动项目,访问上传页面 http://localhost:8080/backend/page/demo/upload.html,然后点击上传图片,选择图片上传时,会发现图片并不能正常的上传,而且在浏览器中可以抓取到响应的数据,从图中我们可以判断出需要登录才可以操作。
而这样的话,就要求我们在测试时,每一次都需要先登录,登录完成后在进行图片上传的测试,为了简化我们的测试,我们可以在 LoginCheckFilter 的doFilter方法中,在不需要处理的请求路径的数组中再加入请求路径 /common/** , 如下:
然后,我们在测试文件的上传功能时,就不需要登录就可以操作了。
我们在测试文件上传时,可以通过debug的形式来跟踪上传的整个过程,验证一下临时文件是否存在,以及上传完成之后,临时文件是否会自动删除。
1.4 下载代码实现
1.4.1 前端代码
文件下载,前端页面可以使用标签展示下载的图片。
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#117700"><</span><span style="color:#117700">img</span> <span style="color:#0000cc">v-if</span>=<span style="color:#aa1111">"imageUrl"</span> <span style="color:#0000cc">:src</span>=<span style="color:#aa1111">"imageUrl"</span> <span style="color:#0000cc">class</span>=<span style="color:#aa1111">"avatar"</span><span style="color:#117700">></</span><span style="color:#117700">img</span><span style="color:#117700">></span></span></span>
在文件上传成功后,在 handleAvatarSuccess 方法中获取文件上传成功之后返回的数据(文件名),然后调用 /common/download?name=xxx 进行文件的下载。在这里,我们想让上传的照片能够在页面展示出来,所以我们就需要在服务端将文件以流的形式写回浏览器。
1.4.2 服务端代码
在 CommonController 中定义方法download,并接收页面传递的参数name,然后读取图片文件的数据,然后以流的形式写回浏览器。
具体逻辑如下:
1). 定义输入流,通过输入流读取文件内容
2). 通过response对象,获取到输出流
3). 通过response对象设置响应数据格式(image/jpeg)
4). 通过输入流读取文件数据,然后通过上述的输出流写回浏览器
5). 关闭资源
代码实现:
1.<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">/**</span> <span style="color:#aa5500">* 文件下载</span> <span style="color:#aa5500">* @param name</span> <span style="color:#aa5500">* @param response</span> <span style="color:#aa5500">*/</span> <span style="color:#555555">@GetMapping</span>(<span style="color:#aa1111">"/download"</span>) <span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#0000ff">download</span>(<span style="color:#008855">String</span> <span style="color:#000000">name</span>, <span style="color:#000000">HttpServletResponse</span> <span style="color:#000000">response</span>){ <span style="color:#770088">try</span> { <span style="color:#aa5500">//输入流,通过输入流读取文件内容</span> <span style="color:#000000">FileInputStream</span> <span style="color:#000000">fileInputStream</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">FileInputStream</span>(<span style="color:#770088">new</span> <span style="color:#000000">File</span>(<span style="color:#000000">basePath</span> <span style="color:#981a1a">+</span> <span style="color:#000000">name</span>)); <span style="color:#aa5500">//输出流,通过输出流将文件写回浏览器</span> <span style="color:#000000">ServletOutputStream</span> <span style="color:#000000">outputStream</span> <span style="color:#981a1a">=</span> <span style="color:#000000">response</span>.<span style="color:#000000">getOutputStream</span>(); <span style="color:#000000">response</span>.<span style="color:#000000">setContentType</span>(<span style="color:#aa1111">"image/jpeg"</span>); <span style="color:#008855">int</span> <span style="color:#000000">len</span> <span style="color:#981a1a">=</span> <span style="color:#116644">0</span>; <span style="color:#008855">byte</span>[] <span style="color:#000000">bytes</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#008855">byte</span>[<span style="color:#116644">1024</span>]; <span style="color:#770088">while</span> ((<span style="color:#000000">len</span> <span style="color:#981a1a">=</span> <span style="color:#000000">fileInputStream</span>.<span style="color:#000000">read</span>(<span style="color:#000000">bytes</span>)) <span style="color:#981a1a">!=</span> <span style="color:#981a1a">-</span><span style="color:#116644">1</span>){ <span style="color:#000000">outputStream</span>.<span style="color:#000000">write</span>(<span style="color:#000000">bytes</span>,<span style="color:#116644">0</span>,<span style="color:#000000">len</span>); <span style="color:#000000">outputStream</span>.<span style="color:#000000">flush</span>(); } <span style="color:#aa5500">//关闭资源</span> <span style="color:#000000">outputStream</span>.<span style="color:#000000">close</span>(); <span style="color:#000000">fileInputStream</span>.<span style="color:#000000">close</span>(); } <span style="color:#770088">catch</span> (<span style="color:#000000">Exception</span> <span style="color:#000000">e</span>) { <span style="color:#000000">e</span>.<span style="color:#000000">printStackTrace</span>(); } }</span></span>