先上结论,再谈细节:
- 前端 axios 的 config 配置 responseType: 'blob' (关键)
- 服务端读取文件,以 content-type: 'application/octet-stream' 的格式返回前端
- 前端接收到数据后,使用 Blob 来接收数据,并且创建a标签进行下载
前端发起请求
在使用 axios 发起请求前,首先了解一下 responseType 这个属性是如何在 axios 中使用的。以 axios 最常用的 get 和 post 请求为例,这两种请求方式在传递参数的时候也是不同的。使用的版本 axios@0.19.2
- 如何传递 responseType?
文件:lib/core/Axios.js
结论:可以看到对于 get 和 post 请求,axios的处理方式是不同的。在get请求当中,config 是第二个参数,而到了 post 里 config 变成了第三个参数,这是传递 responseType 第一个需要注意的地方。
- 为什么要传递 responseType?
文件:lib/adapters/xhr.js
结论:默认情况下,config.responseType 是 undefined,所以通常情况下,我们得到的 response 中的 data 一定是 request.responseText 这个 string 对象。但是对于文件下载,我们需要使用的是后面的 request.response 这个对象,这就是为什么要传递 responseType 的原因
- responseType 为什么是 'blob'?
来源:mdn 中对于 xhr.response 属性的介绍
结论:可以看到,response 返回的数据格式由 responseType 来决定,因为我们需要在回调里使用 Blob 构造函数来接收数据,所以我们传递的 reponseType 也应该是 'blob',保证数据格式的统一
// 通过以上几点那么针对axios设置reponseType的方式应该类似下面
const url = '/info/download'
// get、delete、head 等请求
axios.get(url, {params: {...someParmas}, responseType: 'blob'})
.then((res) => {
//...
}).catch((err) => {
//
})
// post、put、patch 等请求
axios.post(url, {...someData}, {responseType: 'blob'})
.then((res) => {
//...
}).catch((err) => {
//
})
服务端如何返回数据
// 这里以express举例
const fs = require('fs')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
// 以post提交举例
app.post('/info/download', function(req, res) {
const filePath = './file/测试.jpeg'
const fileName = '测试.jpeg'
res.set({
'content-type': 'application/octet-stream',
'content-disposition': 'attachment;filename=' + encodeURI(fileName)
})
fs.createReadStream(filePath).pipe(res)
})
app.listen(3000)
结论:使用 express 模拟请求下载接口,返回流文件的时候只需要统一设置 content-type: 'application/octet-stream' 即可,不需要因为不同的文件类型设置不同的 content-type。
另外对于 content-disposition 在设置 filename 的时候,需要对 filename 进行转码,防止下载的文件名中有中文时出现乱码。
前端解析数据流
前端解析数据流,主要使用 Blob 对象来进行接收
// 以之前的post请求举例
axios.post(url, {...someData}, {responseType: 'blob'})
.then((res) => {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理,其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
}).catch((err) => {})
结论:使用 Blob 对返回的 stream 进行解析,同时使用 URL.createObjectURL 将 blob 转换为一个 url 再进行下载,下载完毕后再对 url 进行释放
剩下的一种常见情况是,后台返回的流文件是一张图片,那么前端如何展示这张图片,代码大概是下面的样子
axios.post(url, {...someData}, {responseType: 'blob'})
.then((res) => {
const { data } = res
const reader = new FileReader()
reader.readAsDataURL(data)
reader.onload = (ev) => {
const img = new Image()
img.src = ev.target.result
document.body.appendChild(img)
}
}).catch((err) => {})
结论:使用 FileReader 对象来读取 stream 文件,将文件解析成一个 base64 的链接,最后在将 img 指向这个链接即可
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。