9

先上结论,再谈细节:

  1. 前端 axios 的 config 配置 responseType: 'blob' (关键)
  2. 服务端读取文件,以 content-type: 'application/octet-stream' 的格式返回前端
  3. 前端接收到数据后,使用 Blob 来接收数据,并且创建a标签进行下载

前端发起请求

在使用 axios 发起请求前,首先了解一下 responseType 这个属性是如何在 axios 中使用的。以 axios 最常用的 getpost 请求为例,这两种请求方式在传递参数的时候也是不同的。使用的版本 axios@0.19.2

  • 如何传递 responseType?

1587294479(1).jpg

文件:lib/core/Axios.js
结论:可以看到对于 get 和 post 请求,axios的处理方式是不同的。在get请求当中,config 是第二个参数,而到了 post 里 config 变成了第三个参数,这是传递 responseType 第一个需要注意的地方。

  • 为什么要传递 responseType?

xhr.jpg

文件:lib/adapters/xhr.js
结论:默认情况下,config.responseType 是 undefined,所以通常情况下,我们得到的 response 中的 data 一定是 request.responseText 这个 string 对象。但是对于文件下载,我们需要使用的是后面的 request.response 这个对象,这就是为什么要传递 responseType 的原因

  • responseType 为什么是 'blob'?

responseType.jpg

来源: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 指向这个链接即可


372563106
49 声望1 粉丝