本文目录导读:

将一个大文件分成多个小块,客户端记录已经成功上传的分块,下次续传时只上传缺失的分块,最后服务端将所有分块合并成完整文件。
下面从核心原理、实现步骤、代码示例以及关键注意事项四个方面来详细说明。
核心原理
- 分片:客户端将大文件按固定大小(如 1MB、5MB)切割成多个小块。
- 记录进度:客户端记录已成功上传的分片索引或分片的哈希值。
- 查询已上传部分:续传前,客户端向服务端询问“这个文件我已经传了哪些分片?”。
- 增量上传:客户端只上传缺失或上传失败的分片。
- 合并:所有分片上传完成后,服务端按顺序合并成原文件。
实现步骤(前后端协作)
前端(浏览器/客户端)负责:
- 文件分片
- 使用 HTML5 API:
file.slice(start, end)切割文件。 - 每个分片分配一个唯一的索引号(
chunkIndex)。
- 使用 HTML5 API:
- 生成文件唯一标识
- 对文件整体计算一个 MD5 或 SHA-1 哈希(或者用
File.name + File.size + lastModified组合)。
- 对文件整体计算一个 MD5 或 SHA-1 哈希(或者用
- 上传前查询
- 请求服务端接口:
GET /upload/status?fileHash=xxx。 - 服务端返回已收到的分片索引列表。
- 请求服务端接口:
- 并发/顺序上传缺失分片
- 使用
FormData发送分片数据,同时携带fileHash和chunkIndex。
- 使用
- 处理异常
如果某个分片上传失败,记录索引,稍后重试。
- 通知合并
- 所有分片上传完毕后,请求服务端:
POST /upload/merge?fileHash=xxx。
- 所有分片上传完毕后,请求服务端:
后端(服务端)负责:
- 接收分片
- 创建临时目录:
/tmp/uploads/{fileHash}/。 - 将收到的分片写入临时文件,
part、part。
- 创建临时目录:
- 记录已接收的分片
- 可以用数据库、Redis 或直接通过文件系统检测(
os.listdir列出目录下的 .part 文件)。
- 可以用数据库、Redis 或直接通过文件系统检测(
- 返回已接收分片列表
当客户端查询时,返回目录中已有的分片索引。
- 合并文件
merge接口:按索引顺序读取所有.part文件,二进制追加写入目标文件。- 合并完成后,清理临时目录。
代码示例(简化版 Node.js + 前端)
前端代码(JavaScript)
// 1. 文件分片
const file = document.getElementById('fileInput').files[0];
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
const fileHash = await calculateFileHash(file); // 用 SparkMD5 等库计算
// 2. 查询已上传分片
const response = await fetch(`/upload/status?fileHash=${fileHash}`);
const uploadedChunks = await response.json(); // 返回已上传的分片索引数组,如 [0,1,2,5]
// 3. 上传缺失分片
for (let i = 0; i < totalChunks; i++) {
if (uploadedChunks.includes(i)) continue; // 已上传的跳过
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('fileHash', fileHash);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
// 带重试逻辑的上传
await uploadWithRetry('/upload/chunk', formData, 3);
}
// 4. 通知合并
await fetch(`/upload/merge?fileHash=${fileHash}&fileName=${file.name}`, { method: 'POST' });
后端代码(Node.js + Express 简化版)
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const app = express();
const uploadDir = '/tmp/uploads/';
// 分片上传接口
app.post('/upload/chunk', multer().single('file'), (req, res) => {
const { fileHash, chunkIndex } = req.body;
const chunkDir = path.join(uploadDir, fileHash);
if (!fs.existsSync(chunkDir)) fs.mkdirSync(chunkDir, { recursive: true });
const chunkPath = path.join(chunkDir, `${chunkIndex}.part`);
fs.writeFileSync(chunkPath, req.file.buffer);
res.send({ success: true });
});
// 查询已上传分片
app.get('/upload/status', (req, res) => {
const { fileHash } = req.query;
const chunkDir = path.join(uploadDir, fileHash);
if (!fs.existsSync(chunkDir)) return res.json([]);
const chunks = fs.readdirSync(chunkDir).map(f => parseInt(f.replace('.part', '')));
res.json(chunks);
});
// 合并文件
app.post('/upload/merge', (req, res) => {
const { fileHash, fileName } = req.query;
const chunkDir = path.join(uploadDir, fileHash);
const totalChunks = fs.readdirSync(chunkDir).length;
const finalPath = path.join(uploadDir, fileName);
const writeStream = fs.createWriteStream(finalPath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(chunkDir, `${i}.part`);
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
}
writeStream.end();
// 清理临时分片
fs.rmSync(chunkDir, { recursive: true });
res.send({ success: true, path: finalPath });
});
关键注意事项与优化
| 关注点 | 说明与建议 |
|---|---|
| 文件哈希(完整性校验) | 计算文件整体哈希开销大,可改为计算每个分片的 MD5,服务端收到后校验,防止数据损坏。 |
| 并发控制 | 前端可以并发上传 3-5 个分片(提升速度),但需要处理分片乱序到达的问题(服务端靠索引顺序合并,本身不依赖到达顺序)。 |
| 断网恢复 | 使用 localStorage 或 IndexedDB 记录已上传的分片索引,浏览器刷新或断网后,重新计算哈希并查询服务端状态。 |
| 服务端存储策略 | - 小规模:直接用文件系统存储 .part 文件。- 大规模:用 Redis 记录分片状态,用对象存储(如 S3、阿里云OSS)直接上传分片(它们原生支持分片上传 API)。 |
| 过期清理 | 未在 24 小时内完成的临时分片应清理(可用定时任务检查文件修改时间)。 |
| 秒传 | 在查询上传状态时,如果服务端发现文件哈希已存在且完整,直接返回“上传成功”,无需重复上传。 |
总结流程
用户选择文件
↓
前端计算 fileHash + 分片
↓
前端询问服务端:fileHash 已有哪些分片?
↓
服务端返回已收分片列表
↓
前端跳过已有分片,只上传缺失分片
↓
所有分片上传完毕 → 请求合并
↓
服务端按顺序合并分片 → 返回最终文件
断点续传的本质就是 “记录进度 + 部分重传”,实际项目中,可以直接使用成熟方案(如阿里云 OSS 分片上传、Resumable.js、Uppy.js),也可以自己实现以满足定制化需求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播