前后端文件下载完整实战:从响应头到 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 输出流

优点:

  • 节省磁盘

  • 支持大项目

  • 更快响应

相关推荐
time_rg15 小时前
深入理解react——1. jsx与虚拟dom
前端·react.js
Keke15 小时前
🍔 fabric如何实现辅助选区捏
前端·javascript
hang_bro15 小时前
echarts 饼图显示设置
前端·echarts
2501_9418868615 小时前
基于法兰克福金融系统实践的高可靠消息队列设计与多语言实现经验总结分享
服务器·前端·数据库
用户120391129472615 小时前
React 性能优化之道:useMemo、useCallback 与闭包陷阱的深度剖析
前端·javascript·react.js
niconicoC15 小时前
Three.js 高性能天气效果实现:下雨与下雪的 GPU 粒子系统
前端
阿珊和她的猫15 小时前
从 AMD 到 ES6 模块化:前端模块化的演进之路
前端·ecmascript·es6
codexu15 小时前
Tauri iOS 开发中 "pnpm: command not found" 错误解决方案
前端
一月是个猫15 小时前
Electron入门指南:从零开始构建跨平台桌面应用
前端·electron
时715 小时前
前端项目测试覆盖率检测
前端·jest