为什么要分片上传?
- 大文件直接上传容易超时、中断、服务器压力大
- 分片失败可以重传单个分片,不用重传整个文件
- 支持并发上传,速度更快
前端负责
- 哈希(给文件生成唯一 ID;
MD5:是哈希里最常用、最简单的一种算法) - 分片(把大文件切成小碎片)
- 分片上传 / 断点续传(查询是否上传,服务器如有,可直接过滤)
- 通知后端合并
后端负责
1. 校验接口(秒传 / 断点续传用)
前端传:文件hash
文件不存在 → 返回已上传的分片序号
2. 上传分片接口
前端传:分片文件 + 文件hash + 分片序号
保存这个小分片到服务器
3. 合并接口
前端传:文件hash + 文件名
把所有分片按顺序合并成完整文件
完整代码
html
<input type="file" id="file">
<button onclick="upload()">上传</button>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
<script>
const CHUNK_SIZE = 2 * 1024 * 1024;
// 计算hash
function getHash(file) {
return new Promise((resolve, reject) => {
const spark = new SparkMD5.ArrayBuffer();
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = e => { spark.append(e.target.result); resolve(spark.end()) };
reader.onerror = reject;
});
}
// 上传单个分片(带失败捕获)
async function uploadChunk(chunk, hash, index) {
const fd = new FormData();
fd.append("chunk", chunk);
fd.append("hash", hash);
fd.append("index", index);
const res = await fetch("http://localhost:3000/upload-chunk", {
method: "POST",
body: fd
});
if (!res.ok) throw new Error(`分片 ${index} 上传失败`);
}
// 主上传(完整版)
async function upload() {
try {
const file = document.getElementById("file").files[0];
if (!file) return alert("请选择文件");
const hash = await getHash(file);
// 切片
const chunks = [];
for (let i = 0; i < file.size; i += CHUNK_SIZE) {
chunks.push(file.slice(i, i + CHUNK_SIZE));
}
// 1. 校验已上传分片(断点续传核心)
const verifyRes = await fetch("http://localhost:3000/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hash })
});
const { uploaded } = await verifyRes.json();
// 2. 只传缺失的分片(失败自动抛错)
for (let i = 0; i < chunks.length; i++) {
if (uploaded.includes(i)) {
console.log("跳过分片:", i);
continue;
}
await uploadChunk(chunks[i], hash, i);
}
// 3. 全部传完 → 合并
const mergeRes = await fetch("http://localhost:3000/merge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hash, filename: file.name })
});
if (!mergeRes.ok) throw new Error("合并失败");
alert("上传成功!");
} catch (err) {
// ✅ 所有错误统一捕获!
console.error(err);
alert("上传失败:" + err.message);
}
}
</script>