likes
comments
collection
share

大文件下载解决方案

作者站长头像
站长
· 阅读数 10

传统文件下载方案

1、window.open & window.location.href

基本使用

window.open(url);
window.location.href = url

特点:

  • 只支持 get 请求,不支持 post
  • 浏览器会根据 header 的 content-type 来判断是下载文件还是预览文件
  • 不支持 header 添加请求表头的形式来鉴权
  • 不支持自定义 filename

2、a 标签

基本使用

// 两种方式
// 方式 1 
<a href="/url.png" download="filename"></a>

// 方式 2
const a = document.createElement('a')
a.href = 'url'
a.download = 'filename'
// 使用target="_blank"时,添加rel="noopener noreferrer" 堵住钓鱼安全漏洞 防止新页面window指向之前的页面
a.rel = "noopener noreferrer";
a.style.display = 'none'
document.body.appendChild(a)
a.click()
a.remove()

特点:

对于浏览器不能识别的文件类型,会自动转换下载模式,否则默认自动打开文件,可以手动添加 download 属性,启用下载模式。

总结:

  • 仅适用于 同源 URL 或 blob 、data 协议起作用
  • 不能携带 header 所以不支持添加请求表头的形式来鉴权
  • 只支持 get 不支持 post
  • 不会预览,直接下载文件
  • 支持修改 filename

3、API 请求

本质是先获取 blob对象,再通过 URL.createObjectURL 获取 dowload url。所以会有两个下载过程。

基本使用

axios({
    url,
    responseType: 'blob',
    headers: {
      Authorization: getToken()
    }
  }).then((res) => {
    if (res && res.status === 200 && res.data) {
      const { data, headers } = res
      // 从文件中获取文件名称
      let fileName
      if (headers['content-disposition']) {
        fileName = headers['content-disposition'].split(';')[1].split('=')[1]
      } else {
        fileName = data.fileName
      }
      const blob = new Blob([data], { type: headers['content-type'] })
      const downUrl = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = downUrl
      a.download = fileName
      a.style.display = 'none'
      document.body.appendChild(a)
      a.click()
      a.remove()
    }
  })

特点:

  • 支持设置 header 携带鉴权信息
  • 支持修改 filename
  • 支持跨域,支持post
  • 下载完成再响应,对于大文件下载不友好,用户无响应等待时间较长

API 请求加强:fetch+streamsaver (适用于大文件下载)

fetch 和 axios 的区别就是 fetch 是响应之后就返回信息,而 axios 是在文件流下载完成之后再返回。

但是 fetch 返回之后并不会立马调起浏览器下载窗口,所以需要用到一个第三方插件 streamsaver,在接口响应之后就能立马调起浏览器下载窗口,显示下载进度。

基本使用

1、需要借助 streamsaver,安装 streamsaver,npm install streamsaver

2、代码如下

import streamSaver from 'streamsaver'

function download(url: string, filename = '') {
  fetch(url, {
    method: 'get',
    headers: {
      Authorization: getToken() as string
    }
  }).then((res) => {
    if (res.status !== 200) {
      ElMessage.error('文件错误,下载失败')
      return
    }
    const readableStream = res.body
    const fileStream = streamSaver.createWriteStream(filename, {
      size: Number(res.headers.get('content-length'))
    })
    if (window.WritableStream && readableStream?.pipeTo) {
      return readableStream?.pipeTo(fileStream).then((res) => {
        console.log('done writeen')
      })
    }
    window.writer = fileStream.getWriter()
    const reader = res.body?.getReader()
    const pump = () =>
      reader
        ?.read()
        .then((res) =>
          res.done
            ? window.writer.close()
            : window.reader.write(res.value).then(pump)
        )
    pump()
  })
  return
}
特点:
  • 当接口有响应就会弹窗显示下载过程,对大文件下载友好
  • 可以设置 header 携带鉴权信息
  • 支持修改 filename
  • 支持跨域,支持post

参考: