saveAs: file-saver/download/fetch/blob 文件下载原理

下载文件的一个不错的包,解析一下下载文件的原理
更新于: 2025-08-27 19:09:16

安装

yarn add file-saver

公司项目的请求

  • 这个请求,是由后端生成的
  • 生成过程,有2个点
    • 添加 attachement 头
curl 'https://kellis-ng-alo7-com.oss-cn-beijing.aliyuncs.com/images/c23f54de640de8dfe5f2247e7d38f733.png?Expires=1756194185&OSSAccessKeyId=LTAI5tSfywws519stBvtNg6R&Signature=1kw7Vsjb12nprXNynnj0f0mhkiI%3D&response-content-disposition=attachment%3Bfilename%3Dc23f54de640de8dfe5f2247e7d38f733.png' \
  -X 'HEAD' \
  -H 'Accept: */*' \
  -H 'Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ru;q=0.6' \
  -H 'Connection: keep-alive' \
  -H 'Origin: https://kellis-ng.alo7.com' \
  -H 'Referer: https://kellis-ng.alo7.com/' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: cross-site' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36' \
  -H 'company-source: apply7' \
  -H 'sec-ch-ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"'

生成预签名

import oss2

# 初始化 bucket
auth = oss2.Auth('your-access-key-id', 'your-access-key-secret')
bucket = oss2.Bucket(auth, 'https://oss-cn-beijing.aliyuncs.com', 'your-bucket-name')

# 生成预签名 URL,强制下载,并指定文件名
url = bucket.sign_url(
    method='GET',
    key='object-in-oss.pdf',  # OSS 中的实际文件名
    expires=3600,  # 过期时间(秒)
    params={
        'response-content-disposition': 'attachment; filename="custom-filename.pdf"'
    }
)

print(url)

file-saver 实现

从原理上说明,为什么这种场景  file-saver 走不通。

  • 不同域名 - 发送 head 请求 → 这里,由于签名会关心方法,所以HEAD 走不通
  • 然后动态创建 a 标签
    • 添加 download 属性 - 并添加文件名
    • 添加 href 链接
    • 添加 norefer 等特定标签

最终的方案

  • 有可能有一个标记的过程(下载次数)
window.open(responseData.url, '_blank');

项目中的代码

  • 这个不要使用 window.fetch(responseData.url,因为这个下载文件到内存流
  • 而 window.open 是直接 网络IO → 本地的 FileIO 简化过程,也没有内存问题
import { message } from 'antd';
// import { saveAs } from 'file-saver';

// import { resouseDownloadHelper } from 'common/help/resouseDownloadHelper';

export function download(url: string, fileName: string, subject: string, uuid: string) {
  alo7Api
    .fetch(alo7Api.prefix.host + `/api/v1/resources_${subject}/${uuid}/download`, {
      method: 'PUT',
    })
    .then(responseData => {
      // saveAs(responseData.url, fileName);
      window.open(responseData.url, '_blank');
      // window.fetch(responseData.url, { cache: 'no-cache' }).then(res => {
      //   const { url } = res;
      //   resouseDownloadHelper(url, fileName);
      // });
    })
    .catch(error => message.error('下载失败:' + error.message));
}

stream 问题

有时候,后端返回是 application/octet-stream 就得走 fetch + blob 方式了

HTTP/1.1 200 OK
Server: AliyunOSS
Date: Wed, 27 Aug 2025 08:40:40 GMT
Content-Type: application/octet-stream
Content-Length: 813953
Connection: close
x-oss-request-id: 68AEC48801B31832319E64AF
Accept-Ranges: bytes
ETag: "710EE1CEE9D3B301A0DE9252486E0A13"
Last-Modified: Tue, 03 Dec 2024 02:44:34 GMT
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 7405199409781227046
x-oss-storage-class: Standard
Content-MD5: cQ7hzunTswGg3pJSSG4KEw==
x-oss-server-time: 37