前端大文件分片上传与断点续传实战方案
在处理视频、安装包或模型文件等大资源时,直接全量上传往往面临浏览器超时、网络波动导致中断以及重传耗时等问题。采用分片上传 + 断点续传 + 服务端校验是目前最稳健的解决方案。
核心实现逻辑
下面是一个完整的前端工具函数 uploadLargeFile,它封装了切片、校验、上传及合并的核心流程。
export async function uploadLargeFile({
file,
fileId,
id,
chunkSize = 5 * 1024 * 1024,
apiCheckChunks,
apiUploadChunk,
apiMergeChunks
}) {
if (!file) throw new Error("file 不能为空");
if (!fileId) throw new Error("fileId 不能为空");
const fileName = file.name;
const totalChunks = Math.ceil(file.size / chunkSize);
const chunks = [];
// 1. 前端文件切片
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(file.size, start + chunkSize);
chunks.push(file.slice(start, end));
}
// 2. 查询已上传分片(断点续传关键)
let res = await apiCheckChunks(fileId, id);
let uploadedList = Array.isArray(res.data) ? res.data : [];
if (!Array.isArray(uploadedList)) uploadedList = [];
// 3. 逐片上传
for (let i = 0; i < totalChunks; i++) {
if (uploadedList.includes(i)) {
console.log(`分片 ${i} 已上传,跳过`);
continue;
}
const formData = new FormData();
formData.append("fileId", fileId);
formData.append("id", id);
formData.append("chunkIndex", i);
formData.append("totalChunks", totalChunks);
formData.append("chunk", chunks[i]);
await apiUploadChunk(formData);
// 上传成功后重新查询列表,确保状态同步
const res1 = await apiCheckChunks(fileId, id);
uploadedList = res1.data || [];
if (!uploadedList.includes(i)) {
throw new Error(`分片 ${i} 上传失败,请重试`);
}
}
// 4. 所有片段上传完成 → 执行合并
if (uploadedList.length === totalChunks) {
console.log("所有分片上传完成,开始合并文件");
await apiMergeChunks(fileId, fileName, id);
} else {
throw new Error("未上传完所有分片,无法合并");
}
return true;
}
关键步骤解析
1. 文件切片
利用浏览器原生的 file.slice(start, end) 方法即可轻松实现。例如设置 5MB 为一片,1GB 的文件会被切分为约 205 个片段。这种方式对内存友好且无需额外库支持。
2. 断点续传机制
这是提升用户体验的关键。在正式上传前,先调用接口查询服务端已存在的分片索引(如 [0, 3, 5])。前端遍历本地切片时,若索引存在于列表中则直接跳过,避免重复传输。
3. 分片上传与校验
每个分片通过 FormData 发送,携带 fileId、chunkIndex 等元数据。为了应对服务器处理延迟,代码在每片上传后再次查询已上传列表。这种'乐观上传 + 实时校验'的策略能有效防止因网络抖动导致的文件损坏。
4. 文件合并
当所有分片确认上传完毕后,通知后端将临时目录下的碎片按顺序合并为最终文件。后端通常会在 /upload/tmp/{fileId}/ 下管理碎片,合并后移至正式存储路径。
后端接口规范
前端主要依赖以下三个接口进行交互:
| 接口 | 功能说明 |
|---|---|
apiCheckChunks(fileId, id) | 查询当前任务已上传的分片索引列表 |
apiUploadChunk(formData) | 上传单个分片二进制数据 |
apiMergeChunks(fileId, fileName, id) | 触发后端合并操作 |
常见问题排查
Q: uploadedList.includes 报错怎么办?
这通常是因为服务端返回的数据格式不符合预期(如 null 或非数组)。代码中已做兜底处理 Array.isArray(res.data) ? res.data : [],但建议后端严格保证返回结构一致。
集成示例
在实际项目中,该函数可嵌入到表单提交逻辑中。以下是一个基于 Vue 环境的调用示意:
// 父组件提交表单时调用
addAndUpload(formData).then(async (res) => {
if (res.status === 'success') {
this.$message.success('操作成功');
this.file_Id = res.data.fileId;
loading.close();
this.dialogVisible = false;
if (this.upFile !== null) {
const fileId = this.file_Id;
const file = this.upFile;
this.$message.info("大文件,启动分片上传...");
try {
await uploadLargeFile({
file,
fileId,
id: res.data.id,
chunkSize: 20 * 1024 * 1024,
apiCheckChunks: (fid, iid) => checkChunks({ fileId: fid, id: iid }),
apiUploadChunk: (fd) => uploadChunk(fd),
apiMergeChunks: (fid, fname, iid) => mergeChunks({ fileId: fid, fileName: fname, id: iid })
});
this.$message.success("大文件上传完成");
} catch (err) {
this.$message.error("分片上传失败");
console.error(err);
}
}
this.getData();
this.resetForm2(formName);
} else {
this.$message.warning(res.msg);
loading.close();
}
});
该方案逻辑清晰,易于接入 Vue、React 或 uni-app 等不同 UI 框架。结合 MD5 校验还可进一步扩展秒传功能,非常适合企业级大文件上传场景。

