前端上传大文件怎么用 Blob slice 实现可恢复分片
前端大文件上传不要把整个 File 一次塞进请求。更稳妥的做法是用 file.slice(start, end) 取出一个 Blob,为每个分片携带稳定的 uploadId 和 index,上传失败只重试当前分片;页面刷新后再向服务端查询已收到的索引,跳过它们继续传。这样“可恢复”依赖的是服务端保存的分片状态,而不是浏览器里一个容易丢失的循环变量。
Blob.slice(start, end)的 end 是排除位置,适合按固定字节范围切片。- 恢复标识至少要稳定包含文件特征,服务端再把它映射到唯一
uploadId。 - 上传分片时不要手动设置
multipart/form-data的 Content-Type,让浏览器生成 boundary。 - 只有服务端确认所有 index 到齐,前端才请求合并;重复请求必须按 uploadId 与 index 幂等处理。
一、先固定恢复标识和接口契约
先把前后端约定写清楚。示例用文件名、大小和最后修改时间组成 resumeKey;生产环境可换成服务端签发的指纹或内容哈希,但原则不变:同一个文件再次选择时,初始化接口应返回同一个上传任务或明确告诉前端哪些分片已经存在。
| 接口 | 前端发送 | 服务端返回 |
|---|---|---|
POST /api/uploads/init | name、size、lastModified、chunkSize、resumeKey | uploadId、chunkSize、received |
POST /api/uploads/chunk | uploadId、index、total、chunk、filename | index、stored |
POST /api/uploads/complete | uploadId、total | merged、url |
received 是服务端已持久化的分片索引数组,例如 [0, 1, 3]。前端不应只保存“上次传到了第几个”,因为第 2 片可能失败,而第 3 片已经成功。
二、用 Blob.slice 切出单个分片
MDN 对 Blob.slice() 的定义是返回原 Blob 某个字节子集的新 Blob。它不会要求前端把整个文件转成一个大数组。下面的函数只负责计算范围并取片,end 不包含在结果中。

function getChunk(file, index, chunkSize) {
// end 是排除位置,最后一片不足 chunkSize 也能正常返回。
const start = index * chunkSize;
const end = Math.min(start + chunkSize, file.size);
return file.slice(start, end);
}
function getTotalChunks(file, chunkSize) {
// 向上取整,保证尾部不足一片时仍有一个合法 index。
return Math.ceil(file.size / chunkSize);
}
例如 chunkSize 设为 8 MiB,前端内存里同时只需要处理当前分片。这里不绑定某个固定大小,实际项目应结合网关限制、网络质量和服务端磁盘写入能力压测后再定。
三、给单片请求加幂等重试
重试对象应是当前 index,而不是从头重传整个文件。服务端用 uploadId + index 作为幂等键:同一分片重复到达时覆盖或直接返回已保存状态,但不能生成第二份分片。
async function uploadChunk(file, upload, index, maxRetry = 3) {
const chunk = getChunk(file, index, upload.chunkSize);
let lastError;
for (let attempt = 0; attempt setTimeout(resolve, 500 * 2 ** attempt));
}
}
throw lastError;
}
这段代码没有把 Content-Type 写死,是因为 FormData 的 multipart boundary 需要由浏览器生成。接口返回 4xx 时应先修正参数或任务状态,不要无条件重试;只有超时、断网等可恢复错误适合进入退避循环。
四、刷新后跳过已完成分片
初始化接口需要根据 resumeKey 返回 uploadId 和 received。前端将数组转成 Set,每次循环只提交缺失的索引。真正的断点续传因此由服务端状态驱动。

async function uploadFile(file) {
const chunkSize = 8 * 1024 * 1024;
const total = getTotalChunks(file, chunkSize);
const resumeKey = [file.name, file.size, file.lastModified].join(":");
const initResponse = await fetch("/api/uploads/init", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: file.name,
size: file.size,
lastModified: file.lastModified,
chunkSize,
resumeKey,
}),
});
if (!initResponse.ok) throw new Error("无法初始化上传任务");
const upload = { ...(await initResponse.json()), total, chunkSize };
const received = new Set(upload.received || []);
for (let index = 0; index
示例用串行上传换取简单的顺序和较低的并发压力。若后续要并发,仍要保留同一个 uploadId、index 幂等约束,并限制并发数;不要把“并发”误当成“可恢复”的实现本身。
五、全部到齐后再请求合并
合并接口必须由服务端再次校验分片数量和索引连续性,不能只相信前端循环已经结束。前端只在所有缺失索引都上传成功后调用它:
async function finishUpload(uploadId, total) {
// 服务端按 uploadId 检查 0 到 total - 1,再按序合并临时分片。
const response = await fetch("/api/uploads/complete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ uploadId, total }),
});
if (!response.ok) throw new Error("分片未齐全,暂不合并");
return response.json();
}
验收时重点看三种场景:中途断网后只补传缺失 index;重复点击继续上传不会产生重复文件;最后一片小于 chunkSize 时也能正常合并。只要这三项成立,Blob.slice、重试、恢复标识和合并链路就形成了闭环。
常见问题
为什么不能只把当前 index 存到 localStorage?
因为分片可能乱序完成,单个数字无法表达缺口。应以服务端的 received 集合为准,必要时再把 uploadId 缓存在本地作为查询入口。
每次重试都要重新调用 slice 吗?
可以重新调用,范围计算是确定的;也可以复用当前 Blob。关键是请求仍携带同一 uploadId 和 index,并让服务端幂等。
能否手动设置 multipart/form-data?
不要这样做。使用 FormData 时让浏览器生成带 boundary 的 Content-Type,否则服务端可能无法正确解析字段和文件。
文件名相同就能恢复吗?
不能。文件名可能相同但内容不同,至少还要结合大小和修改时间;对重要文件可改为服务端指纹或内容哈希,并在初始化接口中重新确认。
Go 命令行 flag 参数缺失时怎么返回可读帮助而不是 panic
- 上一篇
- Go 命令行 flag 参数缺失时怎么返回可读帮助而不是 panic
- 下一篇
- Go io/fs 怎么用统一接口读取磁盘和嵌入文件
-
- 文章 · 前端 | 3小时前 |
- 前端 WebSocket 退避重连 怎样处理
- 278浏览 收藏
-
- 文章 · 前端 | 3小时前 |
- 前端 Vue watch 深度监听 怎样处理
- 192浏览 收藏
-
- 文章 · 前端 | 3小时前 | React 竞态 Fetch AbortController useEffect 前端异步
- 前端 React useEffect 竞态 怎样处理
- 107浏览 收藏
-
- 文章 · 前端 | 3小时前 |
- 前端 Vite alias 测试失效 怎样处理
- 483浏览 收藏
-
- 文章 · 前端 | 3小时前 |
- 前端 Vite 环境变量前缀 怎样处理
- 161浏览 收藏
-
- 文章 · 前端 | 3小时前 | 前端 · 性能 · javascript · ArrayBuffer postMessage 结构化克隆 Web Worker 前端计算
- 前端 Web Worker 移出计算 怎样处理
- 121浏览 收藏
-
- 文章 · 前端 | 3小时前 |
- 前端 DataView 读取二进制字段 怎样避免字节序错误
- 248浏览 收藏
-
- 文章 · 前端 | 3小时前 | 前端 · javascript · Fetch ReadableStream TextDecoder 前端分块读取
- 前端 ReadableStream 分块读取 怎样处理
- 380浏览 收藏
-
- 文章 · 前端 | 1天前 | vite 浏览器兼容 前端构建 Baseline build.target
- Vite 怎样设置 Baseline 浏览器构建目标
- 366浏览 收藏
-
- 文章 · 前端 | 1天前 |
- CSS view-transition-name 怎样连接页面状态切换
- 263浏览 收藏
-
- 文章 · 前端 | 1天前 | 前端 · JSON · javascript · JavaScript JSON.stringify 超大整数 JSON.rawJSON
- JSON.rawJSON 怎么在序列化时保留超大整数文本
- 442浏览 收藏
-
- 文章 · 前端 | 1天前 |
- CSS nesting 怎样组织组件内嵌套规则
- 227浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 592次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 629次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 661次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 631次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 446次使用
-
- Go语言实现文件上传
- 2023-01-23 255浏览
-
- Go语言对前端领域的入侵WebAssembly运行原理
- 2022-12-31 130浏览
-
- golang gin框架实现大文件的流式上传功能
- 2022-12-25 459浏览
-
- Golang实现http文件上传小功能的案例
- 2023-01-07 282浏览
-
- 用go gin server来做文件上传服务
- 2023-02-16 381浏览

