心宇

FDF24DCED8C6738F8313A6D3B073C3F4

首页 管理

前端调用后端 API 下载 PDF、ZIP、图片、证书等文件时,应该如何正确接收文件数据并让浏览器触发下载?

完整流程可以理解为:

Vue / JavaScript
      │
      │ Axios 请求
      ▼
后端 API
      │
      │ 读取服务器文件
      ▼
HTTP Response
      │
      │ 文件数据
      ▼
Axios
      │
      │ responseType: "blob"
      ▼
Blob
      │
      │ URL.createObjectURL()
      ▼
临时 blob URL
      │
      ▼
<a download>
      │
      ▼
浏览器下载文件

最核心的一句话:

Blob 是浏览器中表示原始文件数据的一种对象。前端通过 Axios 下载文件时,可以使用 responseType: "blob" 将 HTTP 响应作为 Blob 接收,再通过 URL.createObjectURL() 创建临时地址,最终触发浏览器下载。


一、什么是 Blob?

Blob 全称:

Binary Large Object

即:

二进制大型对象

在浏览器 JavaScript 中,Blob 是一种用于表示不可变原始数据的对象。

它可以表示:

PDF
ZIP
图片
音频
视频
Excel
Word
证书
普通文本文件
其他文件数据

例如:

const blob = new Blob(["Hello World"], {
    type: "text/plain"
})

此时:

"Hello World"
      ↓
new Blob()
      ↓
Blob 对象

Blob 不等于“磁盘上的文件”。

它更准确地说是:

浏览器内存中保存的一块文件/原始数据。


二、为什么下载文件经常使用 responseType: "blob"?

使用 Axios 请求普通 API 时:

const res = await axios.get("/api/user")

后端可能返回:

{
    "id": 1,
    "name": "Tom"
}

Axios 会按照普通 API 数据进行处理。

但是,如果接口返回的是:

PDF
ZIP
PNG
JPG
XLSX
CRT

我们的目标不是读取:

res.data.name

而是:

把整个 HTTP Response Body 当作文件数据处理。

所以可以指定:

responseType: "blob"

例如:

const response = await axios.get("/api/download", {
    responseType: "blob"
})

此时:

response.data

就是一个:

Blob

三、一个需要修正的理解

原来的理解是:

JavaScript 默认会把服务器返回的数据解析成字符串,如果不设置 responseType: "blob",二进制文件就一定会损坏。

这个说法过于绝对。

更准确的理解是:

Axios 会根据配置、响应头和默认转换逻辑处理响应。对于文件下载接口,显式设置 responseType: "blob" 可以明确告诉 Axios:我要把响应数据作为浏览器 Blob 接收,而不是按照普通 JSON 等业务数据处理。

另外:

.crt
.key

也不一定都是二进制文件。

例如很多 PEM 格式证书其实是文本:

-----BEGIN CERTIFICATE-----
MIID...
...
-----END CERTIFICATE-----

但即使文件本身是文本格式,如果我们的目标是:

把它作为一个完整文件下载下来

仍然可以使用:

responseType: "blob"

所以不要把 Blob 简单理解成:

Blob = 只能处理二进制文件

更好的理解是:

Blob = 浏览器用于表示原始文件数据的对象

四、前端下载文件的完整过程

假设用户点击:

下载证书

前端调用:

GET /api/certificate/download

后端返回:

certificate.crt

整个过程:

用户点击下载
      ↓
Vue
      ↓
Axios
      ↓
GET /api/certificate/download
      ↓
Gin Controller
      ↓
读取 certificate.crt
      ↓
HTTP Response
      ↓
responseType: "blob"
      ↓
response.data
      ↓
Blob
      ↓
URL.createObjectURL(blob)
      ↓
blob:http://localhost/xxxx
      ↓
创建 <a>
      ↓
a.click()
      ↓
浏览器下载

五、Vue + Axios 下载文件示例

假设后端接口:

GET /api/certificate/download

前端:

import axios from "axios"

async function downloadCertificate() {
    try {
        const response = await axios.get(
            "/api/certificate/download",
            {
                responseType: "blob"
            }
        )

        const blob = response.data

        const url = URL.createObjectURL(blob)

        const link = document.createElement("a")

        link.href = url

        link.download = "certificate.crt"

        document.body.appendChild(link)

        link.click()

        document.body.removeChild(link)

        URL.revokeObjectURL(url)

    } catch (error) {
        console.error("文件下载失败:", error)
    }
}

六、每一步到底在干什么?

第一步:发送请求

const response = await axios.get(
    "/api/certificate/download",
    {
        responseType: "blob"
    }
)

告诉 Axios:

我要请求这个文件,并把 Response Body 作为 Blob 返回。

于是:

response.data

得到的是:

Blob

七、为什么还要 URL.createObjectURL()?

现在:

const blob = response.data

Blob 只是 JavaScript 内存中的一个对象。

浏览器 <a> 标签通常需要:

<a href="某个URL">

所以需要:

const url = URL.createObjectURL(blob)

浏览器会为这个 Blob 创建一个临时 URL。

例如概念上类似:

blob:http://localhost:5173/550e8400-e29b-41d4-a716

于是:

Blob
 ↓
URL.createObjectURL()
 ↓
blob URL

现在浏览器就可以通过这个 URL 访问 Blob 数据。


八、创建隐藏的 a 标签

接下来:

const link = document.createElement("a")

相当于动态创建:

<a></a>

然后:

link.href = url

相当于:

<a href="blob:http://localhost/..."></a>

再指定:

link.download = "certificate.crt"

相当于:

<a
    href="blob:http://localhost/..."
    download="certificate.crt"
>
</a>

download 属性告诉浏览器:

不要正常打开这个 URL,而是把它作为文件下载。


九、为什么调用 link.click()?

代码:

link.click()

相当于程序模拟用户点击:

<a download="certificate.crt">
    下载
</a>

于是浏览器开始下载:

certificate.crt

因此:

link.click()

本质就是:

通过 JavaScript 主动触发浏览器的文件下载行为。


十、为什么最后要 revokeObjectURL?

前面执行:

const url = URL.createObjectURL(blob)

浏览器为 Blob 创建了一个临时 URL。

使用完成后应该:

URL.revokeObjectURL(url)

告诉浏览器:

这个临时 URL 已经不用了,可以释放相关资源。

所以完整生命周期:

Blob
 ↓
createObjectURL()
 ↓
临时 URL
 ↓
浏览器下载
 ↓
revokeObjectURL()
 ↓
释放临时 URL

对于偶尔下载一个小文件,不释放通常不会立即产生明显问题。

但如果页面长期运行并不断创建 Blob URL:

下载
下载
下载
下载
...

一直不释放,就可能造成不必要的内存占用。

因此推荐:

URL.revokeObjectURL(url)

十一、完整 Vue 3 示例

例如 Vue 3:

<script setup>
import axios from "axios"

const downloadCertificate = async () => {
    try {
        const response = await axios.get(
            "/api/certificate/download",
            {
                responseType: "blob"
            }
        )

        // 获取 Blob
        const blob = response.data

        // 创建临时 URL
        const url = URL.createObjectURL(blob)

        // 创建下载标签
        const link = document.createElement("a")

        link.href = url
        link.download = "certificate.crt"

        document.body.appendChild(link)

        // 触发下载
        link.click()

        // 删除临时 DOM
        document.body.removeChild(link)

        // 释放 Blob URL
        URL.revokeObjectURL(url)

    } catch (error) {
        console.error("下载证书失败:", error)
    }
}
</script>

<template>
    <button @click="downloadCertificate">
        下载证书
    </button>
</template>

十二、后端 Gin 如何返回文件?

假设服务器存在:

/data/certificate.crt

Gin Controller 可以:

func DownloadCertificate(c *gin.Context) {

    filePath := "/data/certificate.crt"

    c.Header(
        "Content-Disposition",
        `attachment; filename="certificate.crt"`,
    )

    c.Header(
        "Content-Type",
        "application/octet-stream",
    )

    c.File(filePath)
}

前端:

const response = await axios.get(
    "/api/certificate/download",
    {
        responseType: "blob"
    }
)

于是完整流程就是:

Vue
 │
 │ GET
 ▼
Gin Router
 │
 ▼
Controller
 │
 │ c.File()
 ▼
certificate.crt
 │
 ▼
HTTP Response Body
 │
 ▼
Axios
 │
 │ responseType: blob
 ▼
Blob
 │
 ▼
createObjectURL()
 │
 ▼
<a download>
 │
 ▼
浏览器保存文件

十三、Content-Type 是什么?

后端返回文件时通常会设置:

Content-Type

它告诉浏览器:

Response Body 中的数据是什么类型。

例如:

PDF
application/pdf

ZIP
application/zip

PNG
image/png

JPEG
image/jpeg

JSON
application/json

普通二进制文件
application/octet-stream

例如 PDF:

c.Header(
    "Content-Type",
    "application/pdf",
)

ZIP:

c.Header(
    "Content-Type",
    "application/zip",
)

不知道具体类型或者希望作为通用文件处理时,经常使用:

application/octet-stream

十四、Content-Disposition 是什么?

文件下载接口中还有一个非常重要的 Header:

Content-Disposition

例如:

Content-Disposition: attachment; filename="certificate.crt"

其中:

attachment

表示:

希望浏览器把响应作为附件下载。

而:

filename="certificate.crt"

表示建议下载文件名:

certificate.crt

所以:

Content-Type

解决:

这是什么数据?

而:

Content-Disposition

解决:

浏览器应该如何处理它,以及建议使用什么文件名?


十五、让后端决定文件名

实际项目中,文件名通常不是写死在前端:

link.download = "certificate.crt"

而是后端返回:

Content-Disposition: attachment; filename="certificate.crt"

前端读取:

const disposition =
    response.headers["content-disposition"]

然后解析文件名。

简单示例:

const response = await axios.get(
    "/api/certificate/download",
    {
        responseType: "blob"
    }
)

let filename = "download"

const disposition =
    response.headers["content-disposition"]

if (disposition) {
    const match = disposition.match(/filename="?([^"]+)"?/)

    if (match) {
        filename = match[1]
    }
}

const url = URL.createObjectURL(response.data)

const link = document.createElement("a")

link.href = url
link.download = filename

document.body.appendChild(link)

link.click()

document.body.removeChild(link)

URL.revokeObjectURL(url)

于是:

后端
 │
 │ Content-Disposition
 │ filename="certificate.crt"
 ▼
前端
 │
 │ 读取 filename
 ▼
link.download
 │
 ▼
certificate.crt

实际生产项目如果需要支持中文文件名,还需要正确处理 filename*、UTF-8 编码以及跨域时的响应头暴露问题。


十六、Blob 不只是用于下载

Blob 还有一个很常见的用途:

预览文件。

例如后端返回一张图片:

const response = await axios.get(
    "/api/image",
    {
        responseType: "blob"
    }
)

const imageUrl =
    URL.createObjectURL(response.data)

Vue:

<img :src="imageUrl">

流程:

服务器图片
    ↓
Axios
    ↓
Blob
    ↓
createObjectURL()
    ↓
blob URL
    ↓
<img src="">
    ↓
浏览器显示图片

PDF 也可以采用类似思路进行预览。


十七、Blob 和 File 的区别

JavaScript 中还有一个:

File

实际上可以简单理解为:

File 是带有文件信息的 Blob

关系大致是:

Blob
 ↑
File

Blob 主要描述:

一块原始数据

而 File 在 Blob 基础上还包含:

文件名
修改时间
文件类型

例如用户通过:

<input type="file">

选择文件之后,JavaScript 获得的通常是:

File

而从 Axios 文件下载接口获得:

response.data

在设置:

responseType: "blob"

以后通常得到的是:

Blob

十八、Blob 和 FormData 不要混淆

这两个概念经常同时出现在文件功能中,但用途不同。

下载

通常:

服务器
 ↓
HTTP Response
 ↓
Blob
 ↓
浏览器

上传

通常:

File
 ↓
FormData
 ↓
HTTP Request
 ↓
服务器

例如上传:

const formData = new FormData()

formData.append("file", file)

await axios.post(
    "/api/upload",
    formData
)

所以可以先简单记:

下载文件
    ↓
Blob

上传文件
    ↓
File + FormData

这不是绝对规则,但对于常见 Web 开发场景非常实用。


十九、为什么普通文件链接有时根本不需要 Blob?

Blob 并不是所有下载都必须使用。

例如:

<a href="/api/download/file">
    下载文件
</a>

如果后端已经正确返回:

Content-Disposition: attachment

浏览器本身就可以直接下载。

甚至可以:

window.location.href = "/api/download/file"

因此:

文件下载 ≠ 必须 Blob

Blob 更适合以下情况:

需要 Axios / Fetch 请求文件

需要携带 Token

需要先检查接口结果

需要动态处理文件

需要生成预览

需要前端决定文件名

需要对下载数据进一步处理

二十、什么时候应该使用 Blob?

场景一:下载 PDF

responseType: "blob"

例如:

合同
发票
报告
证书

场景二:下载 ZIP

例如后端动态打包:

certificate.crt
private.key
config.json

生成:

certificate.zip

前端:

responseType: "blob"

然后下载。


场景三:下载 Excel

例如:

用户点击“导出 Excel”
        ↓
后端生成 users.xlsx
        ↓
HTTP Response
        ↓
Blob
        ↓
浏览器下载

场景四:图片/PDF预览

例如:

API
 ↓
Blob
 ↓
createObjectURL()
 ↓
<img>

或者:

<iframe>

进行预览。


二十一、最终完整心智模型

以后遇到“前端下载文件”,可以直接想到:

① 用户点击下载
       ↓
② Axios 请求后端
       ↓
③ 后端读取/生成文件
       ↓
④ HTTP Response 返回文件数据
       ↓
⑤ Axios 使用
   responseType: "blob"
       ↓
⑥ response.data 得到 Blob
       ↓
⑦ URL.createObjectURL(blob)
       ↓
⑧ 创建临时 blob URL
       ↓
⑨ 创建 <a>
       ↓
⑩ 设置 download 文件名
       ↓
⑪ link.click()
       ↓
⑫ 浏览器下载
       ↓
⑬ URL.revokeObjectURL()
       ↓
⑭ 释放临时 URL

对应代码:

const response = await axios.get(
    "/api/download",
    {
        responseType: "blob"
    }
)

const blob = response.data

const url = URL.createObjectURL(blob)

const link = document.createElement("a")

link.href = url
link.download = "example.zip"

document.body.appendChild(link)

link.click()

document.body.removeChild(link)

URL.revokeObjectURL(url)

后端 Gin:

func Download(c *gin.Context) {

    filePath := "/data/example.zip"

    c.Header(
        "Content-Disposition",
        `attachment; filename="example.zip"`,
    )

    c.Header(
        "Content-Type",
        "application/zip",
    )

    c.File(filePath)
}

二十二、总结

Blob 是浏览器 JavaScript 中表示原始文件数据的重要对象。

在 Axios 中:

responseType: "blob"

表示:

把 HTTP Response 的数据作为 Blob 文件数据交给前端。

前端获得 Blob 后:

response.data

再通过:

URL.createObjectURL(blob)

创建临时 URL。

然后:

link.href = url
link.download = "文件名"
link.click()

触发浏览器下载。

最终:

URL.revokeObjectURL(url)

释放临时 URL。

所以整个文件下载过程可以浓缩成:

后端文件
   ↓
HTTP Response
   ↓
Axios
   ↓
responseType: "blob"
   ↓
Blob
   ↓
createObjectURL()
   ↓
临时 URL
   ↓
<a download>
   ↓
浏览器下载

最需要记住的是:

Blob 解决的是“浏览器如何表示和操作文件/原始数据”的问题;responseType: "blob" 解决的是“Axios 应该以什么形式把 HTTP 响应交给前端代码”的问题;URL.createObjectURL() 解决的是“如何让浏览器中的其他 API 通过 URL 使用这块 Blob 数据”的问题。

posted on 2026-09-30 10:27  心宇16969  阅读(14)  评论(0)    收藏  举报