前后端文件下载完整实战:从响应头到 Blob

在开发时,一个绕不开的功能就是:

「用户一键下载自己生成的项目代码」

表面上是一个"下载按钮",但背后涉及到:

  • 权限校验
  • 后端流式输出 ZIP
  • HTTP 响应头设计
  • 前端 fetch + Blob 下载

这篇文章我结合真实项目代码,完整拆解一套前后端协作的文件下载方案。

一、整体方案概览

我们先看整体流程(非常关键):

前端:

  • 点击「下载代码」

  • 使用 fetch 发起 GET 请求(携带 Cookie)

  • 后端返回 二进制流(zip)

  • 前端将响应转成 Blob

  • 使用 URL.createObjectURL 触发浏览器下载

后端:

  • 校验应用是否存在

  • 校验当前用户是否是创建者

  • 定位生成好的代码目录

  • 将目录打包为 ZIP

  • 通过 HttpServletResponse 以流的形式返回

二、前端实现:fetch + Blob 的标准下载姿势

1️⃣ 下载状态与按钮控制

ts 复制代码
const downloading = ref(false)

2️⃣ 使用 fetch 发起下载请求

ts 复制代码
const downloadCode = async () => {
  if (!appId.value) {
    message.error('应用ID不存在')
    return
  }

  downloading.value = true
  try {
    const API_BASE_URL = request.defaults.baseURL || ''
    const url = `${API_BASE_URL}/app/download/${appId.value}`

    const response = await fetch(url, {
      method: 'GET',
      credentials: 'include', // ⭐ 非常关键
    })

    if (!response.ok) {
      throw new Error(`下载失败: ${response.status}`)
    }
  • credentials: 'include'

用于携带 Cookie(登录态)

否则后端 getLoginUser 会直接失败

  • 不用 axios?

axios 对二进制下载反而更麻烦

fetch + blob 是浏览器原生、最稳定方案

3️⃣ 从响应头中解析文件名

ts 复制代码
const contentDisposition = response.headers.get('Content-Disposition')
const fileName =
  contentDisposition?.match(/filename="(.+)"/)?.[1]
  || `app-${appId.value}.zip`

为什么要从响应头拿文件名?

  • 后端可以动态控制下载名称

  • 避免前端硬编码

  • 符合 HTTP 标准

4️⃣ Blob 是什么?为什么一定要用它?

Blob(Binary Large Object)本质上是:浏览器中的"二进制文件对象"

  • ZIP / PDF / 图片 / Excel

  • 在 JS 世界里都必须先变成 Blob 才能下载

5️⃣ 触发浏览器下载(核心技巧)

ts 复制代码
const downloadUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = fileName
link.click()
URL.revokeObjectURL(downloadUrl)

这是目前浏览器最标准、兼容性最好的下载方式

6️⃣ 下载按钮示例

html 复制代码
<a-button
  type="primary"
  ghost
  @click="downloadCode"
  :loading="downloading"
  :disabled="!isOwner"
>
  <template #icon>
    <DownloadOutlined />
  </template>
  下载代码
</a-button>

三、后端实现:权限校验 + ZIP 流式下载

1️⃣ 接口定义

java 复制代码
@GetMapping("/download/{id}")
public void downloadAppCode(@PathVariable Long id,
                            HttpServletRequest request,
                            HttpServletResponse response)

2️⃣ 权限与合法性校验(必须)

3️⃣ 获取目录

4️⃣ 核心:如何设置响应头?

在 downloadProjectAsZip 内部,一定要做这几件事:

java 复制代码
response.setContentType("application/octet-stream");
response.setCharacterEncoding("UTF-8");
response.setHeader(
  "Content-Disposition",
  "attachment; filename=\"" + fileName + ".zip\""
);

如果不设置 Content-Disposition:

浏览器可能直接尝试"打开"

前端拿不到文件名

5️⃣ ZIP 流式输出(关键思想)

核心原则:

  • 不要先生成 zip 文件再读, 边压缩,边写入 response 输出流

优点:

  • 节省磁盘

  • 支持大项目

  • 更快响应

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax