上传下载文件方式总结
# 上传下载文件方式总结
—— 前端开发中文件上传与下载方式总结(含核心原理 + 案例 + 面试要点)。
# 一、文件上传方式总结
# 1️⃣ 表单上传(最基础方式)
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">上传</button>
</form>
2
3
4
✅ 特点:
- 浏览器原生支持
- 适合简单的文件上传(无需 JS)
- 表单
enctype必须为multipart/form-data
❌ 缺点:
- 页面会刷新(非异步)
- 不易控制上传进度
# 2️⃣ Ajax 异步上传(前端主流方式)
用fetch方式
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());
2
3
4
5
6
7
8
9
用XMLHttpRequest方式
// 检查是否支持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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
✅ 优点:
- 无需刷新页面
- 支持显示进度、断点续传
- 可附带 token 或额外字段
📌 后端接收:
// Node + Express
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ success: true });
});
2
3
4
# 3️⃣ 使用第三方上传 SDK(云存储)
| 云厂商 | 上传方式 | 特点 |
|---|---|---|
| 阿里云 OSS | ali-oss SDK | 支持分片上传、签名直传 |
| 腾讯云 COS | cos-js-sdk-v5 | 支持临时密钥、断点续传 |
| 七牛云 | qiniu-js | Base64 上传、表单上传 |
✅ 优势:
- 减轻后端压力(前端直传到云)
- 支持大文件分片上传与秒传
📌 直传流程:
- 前端向后端请求上传凭证(签名)
- 前端带签名上传到云存储
- 成功后回调/通知后端
# 4️⃣ 大文件分片上传(chunk upload)
// 切片
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));
}
2
3
4
5
6
7
8
上传:
chunks.forEach((chunk, index) => {
const formData = new FormData();
formData.append('file', chunk);
formData.append('index', index);
fetch('/upload', { method: 'POST', body: formData });
});
2
3
4
5
6
合并(后端):
// 接收到所有切片后合并
fs.appendFileSync('target.mp4', chunk);
2
✅ 优点:
- 支持断点续传
- 可显示进度、并行上传加速
# 5️⃣ Base64 上传(小文件场景)
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);
2
3
4
5
6
7
8
9
10
✅ 优点:
- 实现简单
- 可直接用于存储头像、二维码等
❌ 缺点:
- 体积变大约 33%
- 不适合大文件
# 🧭 上传功能优化点(面试重点)
| 方向 | 说明 |
|---|---|
| ✅ 分片上传 | 适合大文件,支持断点续传 |
| ✅ 并行上传 | 多个分片同时上传,节省时间 |
| ✅ 秒传 | 通过文件 hash 检测是否已存在 |
| ✅ 上传进度 | 利用 XMLHttpRequest.onprogress |
| ✅ 失败重试 | 网络异常可自动重传 |
| ✅ 前端校验 | 文件大小 / 类型限制 |
| ✅ 签名直传 | 提升安全性 |
# 二、文件下载方式总结
# 1️⃣ 直接下载链接(浏览器行为)
<a href="https://example.com/file.pdf" download>下载文件</a>
✅ 简单直接,无需 JS。 ⚠️ 同源限制下,跨域可能受限。
# 2️⃣ 后端返回文件流(最常见)
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);
});
2
3
4
5
6
7
8
也可以用form形式下载文件(只替换上面代码中间dom相关操作代码)
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();
2
3
4
5
6
7
📌 后端:
res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
res.sendFile(filePath);
2
✅ 优点:
- 可控制权限
- 支持跨域下载
顺便一提,下载想要的规格的图片
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);
});
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3️⃣ Blob 下载 + 文件名解析(推荐方式)
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();
});
2
3
4
5
6
7
8
9
10
11
12
顺便一提:
在常规的 HTTP 响应中,Content-Disposition 响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
语法:
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"
2
3
它的第一个参数可以是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>
2
3
4
5
6
上面的响应,不会做为简单的html处理,大多数浏览器会企图以cool.html保存这个文件
在HTML页面中通过POST请求发送Content-Type:multipart/form-data,下面是个例子
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--
2
3
4
5
6
7
8
9
10
11
12
13
14
关于如何自定义header,大家还是要知道一下的。
本质上跟上述的Content-Disposition差不多,只是我们这里不使用默认的header,我们自己自定义一个response header,跟后端决定好编码方式返回,前端直接获取这个自定义header,然后使用对应的解码即可,如使用decodeURIComponent。
但是我们都要知道,在跨域的情况下,前端获取到的header只有默认的6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
所以你想要获取到别的header,需要后端配合,设置
Access-Control-Expose-Headers: Content-Disposition, custom-header
这样,前端就能获取到对应暴露的header字段,需要注意的是,Content-Disposition也是需要暴露的。
# 4️⃣ Base64 文件下载
const base64 = 'data:application/pdf;base64,...';
const a = document.createElement('a');
a.href = base64;
a.download = 'file.pdf';
a.click();
2
3
4
5
✅ 适用于小文件(例如导出图片、二维码等)。
# 5️⃣ 流式下载(后端分块输出)
用于大文件或导出报表时(如 zip、excel):
- 后端使用流(stream)分块输出
- 前端实时接收 blob 块
- 可边下载边保存
# ⚙️ 下载优化方向
| 优化点 | 说明 |
|---|---|
| ✅ 使用 Blob 对象 | 避免内存暴涨 |
| ✅ Content-Disposition | 后端返回文件名 |
| ✅ Content-Type | 保证文件 MIME 类型正确 |
| ✅ 异步加载状态 | 提示“文件生成中...” |
| ✅ 分块流式下载 | 大文件时防止阻塞 |
# 三、面试高频问答
Q1: 你在项目中如何实现大文件上传? 👉 分片 + 并行上传 + 断点续传 + 文件 hash 校验。
Q2: 如何显示上传进度?
👉 使用 xhr.upload.onprogress 或 fetch + progress event。
Q3: 如何实现秒传? 👉 上传前计算文件 hash,后端判断是否已存在该文件。
Q4: 文件下载如何防止浏览器直接预览? 👉 后端添加响应头:
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 作用
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
参考连接:
https://juejin.cn/post/6895599073676492807
https://www.jianshu.com/p/7636d5c60a8d