学习链接
Blob & File
上传文件
前台
- 整个过程,就是在使用FormData 添加 上File(这个Blob),并且key要和后台的名字对应上
- 在点击上传按钮开始上传之前,使用了URL.createObjectURL(File)创建blobUrl,给了img标签作图片预览
- 上传完毕后,将input file的value置为空。若将input file置为空,则此时不能再从input file中获取file了,得等下次再选择图片才能获得file,将它置为空的目的是为了下次选择同样的图片,也能触发input file的change事件
后台
- 后台仅仅就是用MultipartFile声明接收即可,可以使用@RequestParam注解 或 @RequestPart注解
- 调用MultipartFile#transferTo保存文件
- 可以从MultipartFile#getInputStream中获取流,比如上传到OSS。
前端控制台
后端控制台
前端
<template>
<div>
选择文件: <input type="file" ref="fileInputRef" @change="selectFile" multiple> <!-- 使用multiple属性,可选择多个文件 -->
<br/>
<img v-if="imgUrl" :src="imgUrl" alt="" style="width:54px;height:54px;">
<el-button v-if="imgUrl" type="primary" @click="uploadFile">上传</el-button>
<hr/>
</div>
</template>
<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
name: 'File',
data() {
return {
imgUrl:''
}
},
methods: {
selectFile() {
let file = this.$refs['fileInputRef'].files[0]
console.log(file)
// 上传前, 可以预览该图片
let blobUrl = URL.createObjectURL(file)
this.imgUrl = blobUrl
},
uploadFile() {
// 因为可能选择多个文件, 所以这里是个数组
let file = this.$refs['fileInputRef'].files[0]
let formData = new FormData()
formData.append('mfile', file) // 必须和后端的参数名相同。(我们看到了, 其实就是把blob文件给了formData的一个key)
formData.append("type", 'avatar')
// 可以有下面2种方式, 来上传文件
/* axiosInstance
.post('http://127.0.0.1:8083/file/uploadFile',formData, {headers: {'a':'b'}})
.then(res => {
console.log('响应回来: ',res);
}) */
axiosInstance({ // 这种传参方式, 在axios的index.d.ts中可以看到
url:'http://127.0.0.1:8083/file/uploadFile',
method:'post',
data: formData, // 直接将FormData作为data传输
headers: {
'a':'b' // 可携带自定义响应头
}
}).then(res => {
console.log('响应回来: ',res);
})
console.log(this.$refs['fileInputRef'].value); // C:\fakepath\cfa86972-07a1-4527-8b8a-1991715ebbfe.png
// 上传完文件后, 将value置为空, 以避免下次选择同样的图片而不会触发input file的change事件。
// (注意清空value后,将不能再从input file中获取file,而原先的file仍然能够使用)
this.$refs['fileInputRef'].value = ''
}
}
}
</script>
<style>
</style>
后端代码
@PostMapping("uploadFile")
public Object uploadFile(@RequestPart("mfile")MultipartFile multipartFile,@RequestPart("type") String type) throws IOException {
System.out.println(multipartFile.getClass());
System.out.println(type);
// 源文件名
String originalFilename = multipartFile.getOriginalFilename();
// 内容类型
String contentType = multipartFile.getContentType();
// 文件是否为空(无内容)
boolean empty = multipartFile.isEmpty();
// 文件大小
long size = multipartFile.getSize();
// 文件的字节数据
byte[] bytes = multipartFile.getBytes();
// 获取文件的字节输入流
InputStream inputStream = multipartFile.getInputStream();
// 将文件保存到指定路径下
multipartFile.transferTo(new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + originalFilename));
System.out.println(originalFilename);
System.out.println(contentType);
System.out.println(empty);
System.out.println(size);
System.out.println(bytes.length);
HashMap<String, Object> data = new HashMap<>();
data.put("data", "ok");
return data;
}
下载文件
a标签下载
前端代码
<template>
<div>
<a href="http://127.0.0.1:8083/file/downloadFile?filename=头像a.png">avatar3.png</a>
</div>
</template>
<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
name: 'File',
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>
后台代码
@GetMapping("downloadFile")
public void downloadFile(@RequestParam("filename") String filename) throws Exception {
// 告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
// 意思是未知的应用程序文件,浏览器一般不会自动执行或询问执行。浏览器会像对待,
// 设置了HTTP头Content-Disposition值为attachment的文件一样来对待这类文件,即浏览器会触发下载行为
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
// ,该响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者网页的一部分),还是以附件的形式下载并保存到本地。
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;fileName="+ URLEncoder.encode(filename, "UTF-8"));
File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);
ServletOutputStream ros = response.getOutputStream();
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[2 * 1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
ros.write(bytes, 0, len);
}
ros.flush();
ros.close();
}
动态a标签下载
- 后台代码仍然用上面a标签下载的代码即可
前端代码
- 只需要动态创建a标签,添加到body,然后手动调用js触发a标签的click事件,触发下载
- 下载完成之后,将a标签移除
- 整个过程a标签的样式都是display:none
<template>
<div>
<el-button type="success" @click="downloadFile">下载文件</el-button>
</div>
</template>
<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
name: 'File',
data() {
return {
}
},
methods: {
downloadFile() {
let a = document.createElement('a')
a.href = 'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png'
document.body.appendChild(a)
a.style.display = 'none'
a.click()
document.body.removeChild(a)
}
}
}
</script>
<style>
</style>
axios + 动态a标签
- 后台代码仍然用上面a标签下载的代码即可
前端代码
- 收到后端的响应流 变成前端的 blob对象,然后使用浏览器的api,URL将blob对象创建为blobUrl,然后动态创建a标签,触发a标签点击完成下载
- 使用/file/previewFile接口也是一样的效果,其实,只要后端往response里写数据,这里来就能用blob拿到
- 下面的axios不要用封装的,因为要指定responseType:‘blob’,直接去拿数据
<template>
<div>
<el-button type="success" @click="downloadFile">下载文件</el-button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'File',
data() {
return {
}
},
methods: {
downloadFile() {
axios({ // 使用原来的axios实例, 不能用封装的, 因为下面要直接拿响应的blob数据
url:'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png',
method:'get',
headers: {
'a':'b'
},
responseType: 'blob' // 这个可以在axios的index.d.ts中可以找到
}).then(response=>{
return response.data
}).then(blob=>{
console.log(blob);
let ablob = new Blob([blob])
let blobUrl = window.URL.createObjectURL(ablob)
let tmpLink = document.createElement('a')
tmpLink.style.display = 'none'
tmpLink.href = blobUrl
tmpLink.setAttribute('download','头像b.png')
document.body.appendChild(tmpLink)
tmpLink.click()
document.body.removeChild(tmpLink)
window.URL.revokeObjectURL(blobUrl)
})
}
}
}
</script>
<style>
</style>
浏览器直接输入
直接在浏览器的地址栏输入,即可下载,同样用上面的地址即可:http://127.0.0.1:8083/file/downloadFile?filename=头像a.png
预览文件
- 前端直接一个a标签即可,后端改个响应头即可。
- 如果需要在页面中预览,则可使用上面提到的方法,获取到流之后,然后创建为blobUrl或dataUrl放入img标签的src属性即可。
前端代码
<template>
<div>
<a href="http://127.0.0.1:8083/file/previewFile?filename=头像a.png">头像a.png</a>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'File',
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>
后端代码
设置好响应头即可
@GetMapping("previewFile")
public void previewFile(@RequestParam("filename") String filename) throws Exception {
// 可使用ServletContext 通过文件名获取 媒体资源类型
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE);
File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);
ServletOutputStream ros = response.getOutputStream();
// 可参考: StreamUtils
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[4 * 1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
ros.write(bytes, 0, len);
}
ros.flush();
ros.close();
}