Blob 形式下载文件

386 阅读3分钟

背景

对于文件下载,一般简单的文件直接采用 window.open("url")形式,浏览器新开一个tab页面,能够快速的下载,但对于稍微大一点的文件就不太友好了,页面一片空白什么提示都没有,这时候就需要Blob这种形式来提供提示。

为什么使用 blob

  • 二进制数据处理:blob 是一种二进制数据对象,适合处理文件数据。
  • 避免内存问题:对于大文件,blob 可以避免将整个文件加载到内存中,减少内存占用。

部分代码

@GetMapping("{projectId}/archiveFile/export-full")
public void exportFull(HttpServletResponse response, @PathVariable String projectId) {
    bidProjectArchiveFileExportEOService.exportArchiveProjectFiles(response, projectId);
}

// 注意设置 header 中文乱码
// 设置Zip响应头
public static void setResponseHeaderOfZip(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
    response.reset();
    response.setHeader("fileName", encodeFileName(fileName));
    response.setHeader("fileType", "zip");
    response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("GB2312"), "ISO8859-1"));
    response.setContentType("application/octet-stream; charset=utf-8");
    response.setCharacterEncoding("UTF-8");
}


private static String encodeFileName(String fileName) {
    return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\+", "%20");
}

前端

  1. 使用 Axios 发起 GET 请求时,设置 responseType 为 blob。
  2. 使用 ElLoading 组件显示加载提示,并使用 setInterval 实现计时功能。
  3. 从响应头中获取文件名,并使用 decodeURIComponent 解码文件名。
  4. 使用 ElMessage 组件进行消息提示。 通过以上步骤,你可以在文件下载过程中显示带有计时的加载提示,并在文件下载完成后显示相应的成功或失败消息
import axios from 'axios'
import { ElMessage, ElLoading } from 'element-plus'

/**
 * Blob 形式下载文件
 *
 * @param url 目标地址
 */
export function downloadFileByBlob(url) {

  let loading
  let startTime = new Date().getTime()
  let intervalId

  loading = ElLoading.service({
    lock: true,
    text: '文件正在导出中,请稍候... (0s)',
    spinner: 'el-icon-loading',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  intervalId = setInterval(() => {
    const currentTime = new Date().getTime()
    const elapsedTime = Math.floor((currentTime - startTime) / 1000)
    loading.setText(`文件正在导出中,请稍候... (${elapsedTime}s)`)
  }, 1000)

  axios.defaults.timeout = 50000
  axios({
    url: url,
    method: 'GET',
    responseType: 'blob', // 重要:设置响应类型为 blob
    headers: {
      'Content-Type': 'application/json'
    }
  })
    .then(response => {
      clearInterval(intervalId)
      loading.close()
      // console.log('downloadFileByBlob', response)
      const fileName = decodeURIComponent(response.headers['filename']) || `${new Date().getTime()}.${response.headers['filetype']}`
      // 解码文件名
      const decodedFileName = decodeURIComponent(fileName)
      // 创建一个 URL 对象
      const url = window.URL.createObjectURL(new Blob([response.data]))
      const a = document.createElement('a')
      a.href = url
      a.download = decodedFileName // 设置下载文件的名称
      document.body.appendChild(a)
      a.click()
      a.remove()
      window.URL.revokeObjectURL(url)
      ElMessage.success('文件下载完成')
    })
    .catch(error => {
      clearInterval(intervalId)
      loading.close()
      console.error('下载文件时出错:', error)
      ElMessage.error('文件导出失败')
    })
}

使用 blob 形式的优点

  1. 处理大文件: blob 可以处理大文件,因为它不会将整个文件加载到内存中,而是将其作为二进制数据流处理。
  2. 兼容性: blob 是现代浏览器广泛支持的,适用于大多数现代浏览器。
  3. 灵活性: 可以方便地处理不同类型的文件(如 PDF、ZIP、图片等),而不需要额外的处理。
  4. 安全性: 使用 blob 可以避免直接在 URL 中暴露文件路径,提高安全性 。

使用 blob 形式的缺点

  1. 性能问题: 对于非常大的文件,创建 blob 对象可能会消耗较多的内存和处理时间。
  2. 浏览器兼容性: 虽然现代浏览器普遍支持 blob,但一些旧版本的浏览器可能不支持。
  3. 复杂性:
  4. 需要更多的代码来处理 blob 对象的创建和下载,增加了代码的复杂性。
  5. 文件名处理: 需要正确处理文件名的编码和解码,确保文件名在传输过程中不出现乱码
  6. 移动端的支持问题,兼容性非常差