直接提交大文件会因浏览器及后端请求体大小限制(如413错误、超时)而失败;必须用File.slice()分片(建议2–5MB/片),附带chunkIndex等元信息上传;后端需用busboy等流式接收、Redis维护已传分片状态实现断点续传,并严格管控临时文件清理与合并幂等性。

为什么直接用 <input type="file"> 提交大文件会失败
浏览器对单次表单提交的请求体大小有限制,后端(如 Nginx、Apache、Node.js 默认配置)也常限制 client_max_body_size 或 body-parser 的 limit。上传几百 MB 以上文件时,大概率触发超时、413 Payload Too Large 或连接中断。分片不是“可选优化”,而是绕过这些硬性限制的必要手段。
前端如何用 File.slice() 切分文件并逐片上传
核心是把 File 对象当作 Blob 处理,用原生 slice() 方法切块,配合 FormData 和 fetch 逐片发请求。注意:不同浏览器对 slice() 参数顺序支持一致(start, end, contentType),但 IE10+ 需用 webkitSlice 兜底(已基本可忽略)。
- 每片大小建议设为 2–5 MB:
const chunkSize = 4 * 1024 * 1024,太小增加 HTTP 开销,太大仍可能触发网关限制 - 必须在每片中附带元信息:
chunkIndex、totalChunks、fileId(服务端靠这个合并) - 不要依赖
file.lastModified做唯一标识——重命名后值不变;改用crypto.randomUUID()或前端计算文件 hash(如spark-md5)生成fileId - 示例片段:
const blob = file.slice(start, start + chunkSize);
const formData = new FormData();
formData.append('chunk', blob, `${file.name}.part${i}`);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
formData.append('fileId', fileId);后端如何接收并合并分片(以 Node.js + Express 为例)
关键不在“怎么存”,而在“怎么防重复、断点续传、清理垃圾”。Express 默认的 multer 中间件不支持流式接收分片(它会把整个 chunk 加载进内存),必须用 busboy 或 formidable 接收原始流,并按 fileId/chunkIndex 存为临时文件。
- 每个分片保存路径建议为:
/tmp/uploads/${fileId}/${chunkIndex},避免同名覆盖 - 收到最后一片(
chunkIndex === totalChunks - 1)后,触发合并逻辑:按序读取所有分片fs.createReadStream→fs.createWriteStream管道写入目标文件 - 必须加锁或原子重命名防止并发写冲突;上传中途失败时,需定时扫描并清理超过 24 小时的残留
/tmp/uploads/${fileId}/目录 - 返回给前端的响应里带上
ETag或X-Chunk-Received: true,便于前端做幂等校验
如何实现断点续传和进度恢复
断点续传的前提是服务端能告诉前端“我已经收到了哪些片”。不能靠前端自己记——页面刷新就丢了。必须由服务端维护一个轻量状态,比如 Redis 中存:upload:${fileId} → { received: [0,1,3,4], total: 12 }。
立即学习“前端免费学习笔记(深入)”;
- 上传前先发一次
HEAD /api/upload/status?fileId=xxx,服务端查 Redis 返回已接收的索引数组 - 前端跳过这些索引,只上传缺失的片;若全部已存在,直接触发合并
- 避免用数据库存每片状态——QPS 上去会成瓶颈;Redis 的
SET+SMEMBERS足够快 - 注意:合并操作必须是幂等的。同一
fileId多次触发合并,不应报错或重复写盘
实际最难的部分不是切片或上传,而是服务端状态一致性与清理策略。临时目录没人清、Redis key 过期没配、合并时磁盘满未捕获——这些问题在线上爆发时,比前端逻辑难排查得多。



















