一个 4.7 GB 视频把浏览器拖进 OOM
(金石瓜分计划强势上线,速戳上图了解详情)
你给一家在线教育平台做「课程视频批量上传」功能。
需求听起来很朴素:讲师后台一次性拖 20 个 4K 视频,浏览器要稳、要快、要能断网续传。
你第一版直接 <input type="file"> + FormData,结果上线当天就炸:
讲师 A 上传 4.7 GB 的 .mov,Chrome 直接 「内存溢出」 崩溃;讲师 B 网断了 3 分钟,重新上传发现进度条归零,心态跟着归零; 运营同学疯狂 @ 前端:“你们是不是没做分片?”
解决方案:三层防线,把 4 GB 切成 2 MB 的“薯片”
1. 表面用法:分片 + 并发,浏览器再也不卡
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// upload.jsconst CHUNK_SIZE = 2 * 1024 * 1024; // 🔍 2 MB 一片,内存友好export async function* sliceFile(file) {let cur = 0;while (cur < file.size) {yield file.slice(cur, cur + CHUNK_SIZE);cur += CHUNK_SIZE;}}
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// uploader.jsimport pLimit from 'p-limit';const limit = pLimit(5); // 🔍 最多 5 并发,防止占满带宽export async function upload(file) {const hash = await calcHash(file); // 🔍 秒传、断点续传都靠它const tasks = [];for await (const chunk of sliceFile(file)) {tasks.push(limit(() => uploadChunk({ hash, chunk })));}await Promise.all(tasks);await mergeChunks(hash, file.name); // 🔍 通知后端合并}
逐行拆解:
sliceFile用file.slice生成 Blob 片段,「不占额外内存」;p-limit控制并发,避免 100 个请求同时打爆浏览器;calcHash用 WebWorker 算 MD5,页面不卡顿(后面细讲)。
2. 底层机制:断点续传到底续在哪?
hash → 已上传分片索引数组 | |||
hash → 已接收分片索引数组 |
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linesequenceDiagramparticipant F as 前端participant B as 后端F->>B: POST /prepare {hash, totalChunks}B-->>F: 200 OK {uploaded:[0,3,7]}loop 上传剩余分片F->>B: POST /upload {hash, index, chunkData}B-->>F: 200 OKendF->>B: POST /merge {hash}B-->>F: 200 OKNote over B: 按顺序写磁盘
前端先 POST /prepare带 hash + 总分片数;后端返回已上传索引 [0, 3, 7];前端跳过这 3 片,只传剩余; 全部完成后 POST /merge,后端按顺序写磁盘。
3. 设计哲学:把“上传”做成可插拔的协议
ounter(lineounter(lineounter(lineounter(lineounter(lineinterface Uploader {prepare(file: File): Promise<PrepareResp>;upload(chunk: Blob, index: number): Promise<void>;merge(): Promise<string>; // 🔍 返回文件 URL}
我们实现了三套:
BrowserUploader:纯前端分片;TusUploader:遵循 tus.io 协议,天然断点续传;AliOssUploader:直传 OSS,用 OSS 的断点 SDK。
应用扩展:拿来即用的配置片段
1. WebWorker 算 Hash(防卡顿)
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// hash.worker.jsimportScripts('spark-md5.min.js');self.onmessage = ({ data: file }) => {const spark = new SparkMD5.ArrayBuffer();const reader = new FileReaderSync();for (let i = 0; i < file.size; i += CHUNK_SIZE) {spark.append(reader.readAsArrayBuffer(file.slice(i, i + CHUNK_SIZE)));}self.postMessage(spark.end());};
2. 环境适配
File.prototype.slice(用 webkitSlice 兜底) | |
fs.createReadStream 分片,Hash 用 crypto.createHash('md5') | |
举一反三:3 个变体场景
「秒传」
上传前先算 hash → 调后端/exists?hash=xxx→ 已存在直接返回 URL,0 流量完成。「加密上传」
在uploadChunk里加一层AES-GCM加密,后端存加密块,下载时由前端解密。「P2P 协同上传」
用 WebRTC 把同局域网学员的浏览器变成 CDN,分片互传后再统一上报,节省 70% 出口带宽。
小结
大文件上传的核心不是“传”,而是“断”。
把 4 GB 切成 2 MB 的薯片,再配上一张能续命的“进度表”,浏览器就能稳稳地吃下任何体积的视频。
AI编程资讯AI Coding专区指南:
https://aicoding.juejin.cn/aicoding