前端文件下载踩坑记录

399 阅读3分钟

前端下载文件

记录一下在下载文件,文件流时遇到的坑

浏览器环境下载文件到系统有下面两种方法可以实现

  1. window.open() 由window对象提供的方法。
window.open('localhost:3000/api/download')

我用的少就不展开讲

  1. <a> 标签。
<a herf='localhost:3000/api/download'>下载</a>

mdn上面的描述是 href属性可以通向其他网页,文件,同一文件内的位置,电子邮件地址,或任何url超链接。

所以后端提供的接口是现成的文件或者文件流时,直接将接口地址放到herf属性上,下载文件的接口响应头类型是文件类型时,浏览器会自动的调用系统的下载文件窗口。

所以, 浏览器是怎么识别接口返回内容的类型的?

Snipaste_2022-09-22_20-28-27.png

​ 答案就在这里, 响应头中的 content-type这个属性保存了后端给我们标注的数据的 **MIME ** 类型

拿MS Excel的MIME举例,

  • application/vnd.ms-excel 官方
  • application/msexcel

  • application/x-msexcel

  • application/x-ms-excel

  • application/x-excel

  • application/x-dos_ms_excel

  • application/xls

  • application/x-xls

  • application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (xlsx)

    浏览器识别到这些类型的数据时,会弹出下载的提示框

此时直接点击标签就可以下载了,文件名可以通过a标签的download设置

<a herf='localhost:3000/api/download' download='filename.xlsx'>点击下载</a>

但是 这个方法只适用于GET方法的请求。

因为get请求可以将参数拼接到url上面。

那如果是POST方法,也需要携带参数请求怎么办呢? - ajax

大概步骤如下:

1. 先调用接口,将数据请求回来。
2. 将数据转成`Blob`对象或者`File`对象。
3. 利用web提供的`URL`对象中的`createObjectURL()`方法获取到 Blob/File 对象的本地URl地址。
4. 创建一个a标签并将herf改为刚刚获取到的url地址。

这里解释一下,第一步是请求数据,如果是使用axios请求数据,axios会默认将数据类型转为json, 所以需要手动将responseType设置为blob或者arraybuffer类型。 下图是axios官网示例

Snipaste_2022-09-22_20-59-08.png

第二步是将数据创建一个 Blob或File 对象, Blob对象是一个不可变,存储原始数据的类文件对象,而File对象则继承自Blob对象。 以Blob对象为例, 创建Blob对象需要一个 ArrayBuffer ,ArrayBufferView, Blob, DOMString组成的数组, 和指定的MIME类型。 用大白话来说就是创建一个Blob对象需要 传入一段二进制数据, 和告诉它编译这段二进制数据的方式。

第三步就是利用URL.createObjectURL()获取到Blob的url。

最后创建一个a标签用来下载这个文件。

最后用axios举例代码如下, 也可以用原生ajax,fetch。

const baseUrl = "/api/downloads?";
const data = {
    fileId: '001'
}
const res = await axios({
   url:baseUrl,
   method:"POST",
   data,
   responseType: "blob" //这一步是关键,因为axios默认会将responseType设置为json,到时候看到的就是乱码
})
if(res.status == 200){
   const { headers } = res
   let headerStr = headers['content-disposition'] //这一步是为了获取这个字段中保存的filename
   let headerList = headerStr.split(';')
   let file = headerList.find(item=>/^filename=/.test(item))
   let filename
   if(file){
      filename = file.slice(10, -1)
   }
   //创建blob对象时,确保传入的二进制数据而不是乱码的中文  res.data
   const blob = new Blob([res.data], {type:"application/msexcel;charset=utf-8"})
   const url = URL.createObjectURL(blob)
   let a = document.createElement("a");
   a.style.display = "none";
   a.href = url
   a.download = filename || '项目.xlsx'
   document.body.appendChild(a);
   a.click();
   document.body.removeChild(a);
}