尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

大文件分片上传与断点续传技术详解

大文件分片上传与断点续传技术详解 1. 为什么需要分片上传与续传大文件上传一直是前端开发中的痛点问题。我曾在实际项目中遇到过用户上传3GB视频文件时网络波动导致上传失败用户不得不重新开始的糟糕体验。传统单次上传方式存在几个致命缺陷网络稳定性问题大文件上传耗时较长网络波动或中断会导致整个上传失败服务器压力单次处理大文件会占用大量服务器内存和带宽用户体验差失败后需要从头开始无法保留已上传部分超时风险HTTP请求默认有超时限制大文件容易触发分片上传将大文件切割成多个小块如每片5MB分别上传到服务器。这种方案的优势在于容错性强即使某片上传失败只需重传该片段而非整个文件支持暂停续传记录已上传片段后续可从断点继续并行上传可同时上传多个分片提高速度进度可控能精确计算上传百分比2. 核心实现方案设计2.1 整体流程架构一个完整的分片上传系统需要前后端协同工作前端 1. 文件选择 → 2. 分片切割 → 3. 上传分片 → 4. 确认合并 后端 1. 接收分片 → 2. 临时存储 → 3. 校验完整性 → 4. 合并文件2.2 前端关键技术点2.2.1 文件分片处理使用File API的slice方法实现分片切割function createFileChunks(file, chunkSize 5 * 1024 * 1024) { const chunks [] let cur 0 while (cur file.size) { chunks.push({ chunk: file.slice(cur, cur chunkSize), filename: ${file.name}-${cur} }) cur chunkSize } return chunks }注意浏览器兼容性方面IE10支持File.slice()对于更老浏览器需要polyfill2.2.2 分片上传控制实现并发控制避免浏览器请求数过多async function uploadChunks(chunks, maxConcurrent 3) { const pool new Set() let uploadedCount 0 for (let i 0; i chunks.length; i) { const task uploadSingleChunk(chunks[i]) .then(() { pool.delete(task) uploadedCount updateProgress(uploadedCount / chunks.length) }) pool.add(task) if (pool.size maxConcurrent) { await Promise.race(pool) } } await Promise.all(pool) }2.2.3 断点续传实现关键是在本地存储已上传分片信息// 获取文件唯一标识 function getFileKey(file) { return ${file.name}-${file.size}-${file.lastModified} } // 保存上传记录 function saveProgress(fileKey, chunkIndex) { const progress JSON.parse(localStorage.getItem(fileKey) || {}) progress[chunkIndex] true localStorage.setItem(fileKey, JSON.stringify(progress)) } // 检查已上传分片 function checkUploaded(fileKey, totalChunks) { const progress JSON.parse(localStorage.getItem(fileKey) || {}) return Array(totalChunks).fill() .map((_, i) progress[i] || false) }3. 后端接口设计与实现3.1 必要API接口3.1.1 分片上传接口POST /upload/chunk 参数 - file: 分片数据 - chunkIndex: 当前分片序号 - totalChunks: 总分片数 - fileHash: 文件唯一标识3.1.2 合并接口POST /upload/merge 参数 - fileHash: 文件唯一标识 - filename: 原始文件名 - totalChunks: 总分片数3.2 分片存储策略建议采用临时目录最终合并的方式/tmp /{fileHash} /0.chunk /1.chunk ...合并时按序号读取所有分片并写入最终文件const mergeFiles async (fileHash, filename) { const chunkDir path.join(tmp, fileHash) const chunks await fs.readdir(chunkDir) chunks.sort((a, b) parseInt(a) - parseInt(b)) const writeStream fs.createWriteStream(path.join(uploads, filename)) for (const chunk of chunks) { const chunkPath path.join(chunkDir, chunk) writeStream.write(await fs.readFile(chunkPath)) } writeStream.end() await fs.rm(chunkDir, { recursive: true }) }4. 高级优化与实战技巧4.1 文件秒传优化通过计算文件hash实现秒传async function calculateHash(file) { return new Promise(resolve { const reader new FileReader() reader.onload e { const spark new SparkMD5.ArrayBuffer() spark.append(e.target.result) resolve(spark.end()) } reader.readAsArrayBuffer(file) }) } // 上传前先检查 async function checkFileExist(fileHash) { const res await fetch(/api/check?hash${fileHash}) return res.json() }4.2 分片大小选择策略分片大小需要权衡太小请求次数过多增加开销太大失去分片优势重传成本高推荐策略100MB以下文件5MB/片100MB-1GB10MB/片1GB以上20MB/片4.3 错误处理与重试机制实现智能重试策略async function uploadWithRetry(chunk, retries 3) { for (let i 0; i retries; i) { try { return await uploadSingleChunk(chunk) } catch (err) { if (i retries - 1) throw err await new Promise(r setTimeout(r, 1000 * (i 1))) } } }5. 完整实现示例5.1 前端核心代码class BigFileUploader { constructor(options) { this.chunkSize options.chunkSize || 5 * 1024 * 1024 this.maxConcurrent options.maxConcurrent || 3 this.onProgress options.onProgress || (() {}) } async upload(file) { const fileHash await calculateHash(file) const exist await checkFileExist(fileHash) if (exist) return this.onProgress(1) const chunks createFileChunks(file, this.chunkSize) const uploaded checkUploaded(fileHash, chunks.length) const tasks chunks .filter((_, i) !uploaded[i]) .map(chunk () this.uploadChunk(chunk, fileHash)) await parallelLimit(tasks, this.maxConcurrent) await this.merge(file, fileHash) } async uploadChunk(chunk, fileHash) { const formData new FormData() formData.append(chunk, chunk.chunk) formData.append(chunkIndex, chunk.index) formData.append(fileHash, fileHash) await axios.post(/upload/chunk, formData) saveProgress(fileHash, chunk.index) } }5.2 常见问题排查问题1分片上传后合并失败可能原因分片顺序错乱 → 确保后端按数字顺序合并分片丢失 → 增加MD5校验临时文件权限问题 → 检查服务器目录权限问题2进度显示不准确解决方案使用Web Worker计算hash避免UI阻塞分片大小保持一致便于计算考虑使用requestAnimationFrame更新UI问题3大文件hash计算慢优化方案抽样hash只计算文件头尾和中间部分Web Worker并行计算显示计算进度在实际项目中我遇到过用户上传10GB视频文件的需求。通过实现分片上传断点续传将失败率从35%降到了2%以下。关键是要处理好内存管理——大文件切片时要避免同时加载所有分片到内存应该采用流式处理。
返回列表