Skip to content

上传下载文件方式总结

—— 前端开发中文件上传与下载方式总结(含核心原理 + 案例 + 面试要点)

一、文件上传方式总结

1️⃣ 表单上传(最基础方式)

html
<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">上传</button>
</form>

✅ 特点:

  • 浏览器原生支持
  • 适合简单的文件上传(无需 JS)
  • 表单 enctype 必须为 multipart/form-data

❌ 缺点:

  • 页面会刷新(非异步)
  • 不易控制上传进度

2️⃣ Ajax 异步上传(前端主流方式)

用fetch方式

javascript
const formData = new FormData();
formData.append('file', fileInput.files[0]);
// 关于formData不得不说一下,formData.append的参数只有三个,当设置value时只支持字符串类型和Blob类型
// The field's value. This can be a string or Blob (including subclasses such as File). If none of these are specified the value is converted to a string.

fetch('/upload', {
  method: 'POST',
  body: formData
}).then(res => res.json());

用XMLHttpRequest方式

js
// 检查是否支持FormData
if(window.FormData) { 
  var formData = new FormData();

  // 建立一个upload表单项,值为上传的文件
  formData.append('upload', document.getElementById('upload').files[0]);
  var xhr = new XMLHttpRequest();
  xhr.open('POST', $(this).attr('action'));
  // 定义上传完成后的回调函数
  xhr.onload = function () {
    if (xhr.status === 200) {
      console.log('上传成功');
    } else {
      console.log('出错了');
    }
  };
  xhr.send(formData);
}

✅ 优点:

  • 无需刷新页面
  • 支持显示进度、断点续传
  • 可附带 token 或额外字段

📌 后端接收:

js
// Node + Express
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ success: true });
});

3️⃣ 使用第三方上传 SDK(云存储)

云厂商上传方式特点
阿里云 OSSali-oss SDK支持分片上传、签名直传
腾讯云 COScos-js-sdk-v5支持临时密钥、断点续传
七牛云qiniu-jsBase64 上传、表单上传

✅ 优势:

  • 减轻后端压力(前端直传到云)
  • 支持大文件分片上传与秒传

📌 直传流程:

  1. 前端向后端请求上传凭证(签名)
  2. 前端带签名上传到云存储
  3. 成功后回调/通知后端

4️⃣ 大文件分片上传(chunk upload)

javascript
// 切片
const chunkSize = 5 * 1024 * 1024; // 5MB
const file = input.files[0];
const chunks = [];

for (let i = 0; i < file.size; i += chunkSize) {
  chunks.push(file.slice(i, i + chunkSize));
}

上传:

javascript
chunks.forEach((chunk, index) => {
  const formData = new FormData();
  formData.append('file', chunk);
  formData.append('index', index);
  fetch('/upload', { method: 'POST', body: formData });
});

合并(后端):

js
// 接收到所有切片后合并
fs.appendFileSync('target.mp4', chunk);

✅ 优点:

  • 支持断点续传
  • 可显示进度、并行上传加速

5️⃣ Base64 上传(小文件场景)

javascript
const reader = new FileReader();
reader.onload = () => {
  const base64 = reader.result;
  fetch('/uploadBase64', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data: base64 })
  });
};
reader.readAsDataURL(file);

✅ 优点:

  • 实现简单
  • 可直接用于存储头像、二维码等

❌ 缺点:

  • 体积变大约 33%
  • 不适合大文件

🧭 上传功能优化点(面试重点)

方向说明
✅ 分片上传适合大文件,支持断点续传
✅ 并行上传多个分片同时上传,节省时间
✅ 秒传通过文件 hash 检测是否已存在
✅ 上传进度利用 XMLHttpRequest.onprogress
✅ 失败重试网络异常可自动重传
✅ 前端校验文件大小 / 类型限制
✅ 签名直传提升安全性

二、文件下载方式总结

1️⃣ 直接下载链接(浏览器行为)

html
<a href="https://example.com/file.pdf" download>下载文件</a>

✅ 简单直接,无需 JS。 ⚠️ 同源限制下,跨域可能受限。

2️⃣ 后端返回文件流(最常见)

javascript
fetch('/api/download').then(res => res.blob()).then(blob => {
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'report.pdf';
  a.click();
  URL.revokeObjectURL(url);
});

也可以用form形式下载文件(只替换上面代码中间dom相关操作代码)

js
let myForm = document.createElement('form')
myForm.style.display = 'none'
myForm.setAttribute('target', '')
myForm.setAttribute('method', 'get')
myForm.setAttribute('action', url)
document.body.append(myForm);
myForm.submit();

📌 后端:

js
res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
res.sendFile(filePath);

✅ 优点:

  • 可控制权限
  • 支持跨域下载

顺便一提,下载想要的规格的图片

js
function downloadImgURL(url, name) { 
  let image = new Image(); 
  image.setAttribute("crossOrigin", "anonymous"); 
  image.src = url; 
  image.onload = () => { 
    let canvas = document.createElement("canvas"); 
    canvas.width = image.width; 
    canvas.height = image.height; 
    let ctx = canvas.getContext("2d"); 
    ctx.drawImage(image, 0, 0, image.width, image.height); 
    canvas.toBlob(blob => { 
      let url = URL.createObjectURL(blob); 
      let a = document.createElement("a"); 
      a.download = name; 
      a.href = url; 
      a.click(); 
      a.remove(); // 用完释放URL对象 
      URL.revokeObjectURL(url); 
      }); 
    }; 
  }

3️⃣ Blob 下载 + 文件名解析(推荐方式)

javascript
fetch('/api/download').then(async (res) => {
  const blob = await res.blob();
  const filename = decodeURI(
    res.headers.get('Content-Disposition').split('filename=')[1]
    // 'Content-Type': 'application/octet-stream',// 这是一个二进制文件
  );
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
});

顺便一提:

在常规的 HTTP 响应中,Content-Disposition 响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。

语法:

js
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

它的第一个参数可以是inline或者attachment。如果第一个参数是inline(默认值)则表示展示到网页中或者做为网页展示。如果第一个参数是attachment表示它应该被下载,大部分浏览器会出现一个保存为的对话框,如果提供了filename会用filename填充对话框的文件名。

Content-Disposition: attachment; filename="filename.jpg"

Content-Disposition可以触发“保存为”的对话框

200 OK
Content-Type: text/html; charset=utf-8
Content-Disposition: attachment; filename="cool.html"
Content-Length: 22

<HTML>Save me!</HTML>

上面的响应,不会做为简单的html处理,大多数浏览器会企图以cool.html保存这个文件

在HTML页面中通过POST请求发送Content-Type:multipart/form-data,下面是个例子

js
POST /test.html HTTP/1.1
Host: example.org
Content-Type: multipart/form-data;boundary="boundary"

--boundary
Content-Disposition: form-data; name="field1"

value1
--boundary
Content-Disposition: form-data; name="field2"; filename="example.txt"

value2
--boundary--

关于如何自定义header,大家还是要知道一下的。

本质上跟上述的Content-Disposition差不多,只是我们这里不使用默认的header,我们自己自定义一个response header,跟后端决定好编码方式返回,前端直接获取这个自定义header,然后使用对应的解码即可,如使用decodeURIComponent。 但是我们都要知道,在跨域的情况下,前端获取到的header只有默认的6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。 所以你想要获取到别的header,需要后端配合,设置

js
Access-Control-Expose-Headers: Content-Disposition, custom-header

这样,前端就能获取到对应暴露的header字段,需要注意的是,Content-Disposition也是需要暴露的。

4️⃣ Base64 文件下载

javascript
const base64 = 'data:application/pdf;base64,...';
const a = document.createElement('a');
a.href = base64;
a.download = 'file.pdf';
a.click();

✅ 适用于小文件(例如导出图片、二维码等)。

5️⃣ 流式下载(后端分块输出)

用于大文件或导出报表时(如 zip、excel):

  • 后端使用流(stream)分块输出
  • 前端实时接收 blob 块
  • 可边下载边保存

⚙️ 下载优化方向

优化点说明
✅ 使用 Blob 对象避免内存暴涨
✅ Content-Disposition后端返回文件名
✅ Content-Type保证文件 MIME 类型正确
✅ 异步加载状态提示“文件生成中...”
✅ 分块流式下载大文件时防止阻塞

三、面试高频问答

Q1: 你在项目中如何实现大文件上传? 👉 分片 + 并行上传 + 断点续传 + 文件 hash 校验。

Q2: 如何显示上传进度? 👉 使用 xhr.upload.onprogressfetch + progress event

Q3: 如何实现秒传? 👉 上传前计算文件 hash,后端判断是否已存在该文件。

Q4: 文件下载如何防止浏览器直接预览? 👉 后端添加响应头:

js
res.setHeader('Content-Disposition', 'attachment; filename="xx.pdf"');

Q5: 上传文件如何防止安全问题? 👉 前端校验 + 后端 MIME 检查 + 文件隔离存储。

四、结构化思维导图总结

文件上传下载总结
├── 上传方式
│   ├── 表单上传(multipart/form-data)
│   ├── Ajax 上传(FormData)
│   ├── 云存储直传(带签名)
│   ├── 分片上传(chunk + merge)
│   ├── Base64 上传
│   └── 上传优化
│       ├── 秒传
│       ├── 并行上传
│       ├── 进度条
│       └── 重试机制
├── 下载方式
│   ├── 直接链接下载
│   ├── Blob 下载(fetch + blob)
│   ├── Base64 下载
│   ├── 流式下载(stream)
│   └── 下载优化
│       ├── 设置文件名
│       ├── 正确 Content-Type
│       ├── 防止预览
│       └── 大文件分块
└── 面试高频点
    ├── 分片上传原理
    ├── 秒传实现
    ├── 上传进度监听
    ├── Blob 下载实现
    └── Content-Disposition 作用

参考连接:

https://juejin.cn/post/6895599073676492807

https://www.jianshu.com/p/7636d5c60a8d