前端大文件分片上传实现与断点续传方案(含完整代码讲解)
在上传大文件(如视频、安装包、模型文件)时,直接上传容易出现以下问题:
- 文件过大 → 浏览器/服务器容易超时
- 上传过程中断 → 重新上传浪费时间
- 网络波动 → 上传失败率高
因此,大文件分片上传 + 断点续传 + 秒传校验 是目前最通用、最稳定的解决方案。
本文将通过一段完整可运行的示例代码,详细讲解如何在前端实现分片上传、断点续传、服务端校验等关键功能。
✨ 实现效果
- ✔ 自动切片(默认 5MB/片,可配置)
- ✔ 查询已上传分片(断点续传)
- ✔ 自动跳过已上传的片段
- ✔ 每片上传成功后重新校验
- ✔ 所有片段上传完成后自动触发合并
- ✔ 错误处理完善
📌 核心代码(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. 前端切片(slice 实现)
file.slice(start, end)
浏览器原生提供 slice,因此实现非常简单。
如果选择 5MB 一个片段,1GB 文件会被切成:
Math.ceil(1024MB / 5MB) = 205 片
2. 查询已上传分片(断点续传关键)
let res = await apiCheckChunks(fileId, id); let uploadedList = Array.isArray(res.data) ? res.data : [];
服务端返回的数据通常是:
[0, 3, 5, 6]
前端据此跳过已上传片段,避免重复上传,大幅提升效率。
3. 上传文件分片(FormData)
每个分片上传都附带:
- 分片 index
- 总分片数
- chunk 二进制数据
- 业务 ID 或用户 ID
这是一个完整的可追踪数据结构,支持合并校验。
4. 每片上传后重新校验(确保上传成功)
const res1 = await apiCheckChunks(fileId, id); uploadedList = res1.data || [];
避免服务器延迟导致状态不同步,确保每片上传成功。
5. 全部上传完毕 → 调用合并
await apiMergeChunks(fileId, fileName, id);
后端将所有片段按顺序合并成最终文件。
🏗 后端接口交互说明(简版)
前端需要的接口:
| 接口 | 功能 |
|---|---|
apiCheckChunks(fileId) | 查询已上传的分片列表 |
apiUploadChunk(formData) | 上传某个片段 |
apiMergeChunks(fileId, fileName) | 合并所有片段 |
通常后端会在服务器临时目录中创建:
/upload/tmp/{fileId}/0 /upload/tmp/{fileId}/1 /upload/tmp/{fileId}/2 ...
然后合并成:
/upload/merged/xxx.mp4
💡 常见问题(FAQ)
1. 为什么 uploadedList.includes 报错?
通常是:
- 服务端返回的数据不是数组
- res.data 为 null
- uploadedList 不是数组直接调用 includes 出错
你的代码已经做了兜底处理:
let uploadedList = Array.isArray(res.data) ? res.data : [];
但仍要保证服务端返回值格式正确。
🎯 总结
本文展示了一个完整可用的前端大文件分片上传工具方法,支持:
- 文件切片
- 分片校验
- 断点续传
- 分片上传
- 自动合并
其优势是:
- 前端逻辑清晰
- 易接入任何 UI(Vue/React/uni-app)
- 可结合后端实现秒传(加 MD5 校验)
- 非常适合大文件上传场景
最后如何使用:
// 父组件提交表单 调用大文件上传 addAndUpload(formData).then(async (res) => { if (res.status === 'success') { this.$message({ message: '操作成功', type: '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: (fileId, id) => checkChunks({ fileId, id }), apiUploadChunk: (formData) => uploadChunk(formData), apiMergeChunks: (fileId, fileName, id) => mergeChunks({ fileId, fileName, id }) }) this.$message.success("大文件上传完成") } catch (err) { this.$message.error("分片上传失败") console.error(err) } } this.getData(); this.resetForm2(formName); } else { this.$message({ message: res.msg, type: 'warning' }); loading.close(); } })