news 2026/4/3 4:15:57

大文件上传:秒传、断点续传、分片上传

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大文件上传:秒传、断点续传、分片上传

大文件上传:秒传、断点续传、分片上传


文章目录

  • 大文件上传:秒传、断点续传、分片上传
  • 前言
  • 一、大文件上传的挑战
  • 二、秒传
  • 三、分片上传
  • 四、断点续传
  • 总结

前言

大文件上传是 Web 开发中高频且复杂的需求,核心痛点集中在传输稳定性、效率、服务器压力三大维度。秒传、断点续传、分片上传是解决这些问题的三大核心技术,三者并非孤立存在,而是常组合使用


一、大文件上传的挑战

  1. 在深入技术前,需明确大文件上传(通常指≥100MB 的文件,如视频、大型压缩包底层逻辑:
  • 传输层面:网络波动(导致上传中断,需重新上传全部内容,浪费时间;
  • 服务器层面:单次接收大文件占用大量内存 / 磁盘 IO,易引发服务器过载,甚至触发连接超时;
  • 存储层面:重复文件多次上传导致存储冗余,浪费磁盘空间;
  • 限制层面:浏览器、服务器对单次上传文件大小有默认限制,直接上传大文件会报错;
  • 用户体验层面:上传耗时久,无进度反馈,中断后需重新开始,体验极差
  1. 三大技术分别针对不同痛点:
  • 秒传:解决「重复文件存储 / 传输」问题;
  • 分片上传:解决「单次传输压力大、大小限制」问题;
  • 断点续传:解决「传输中断后重新上传」问题。

二、秒传

  1. 核心定义
    秒传是指用户上传文件时,服务器已存在相同文件,无需传输任何文件数据,仅通过「文件唯一标识校验」直接返回上传成功的技术,全程耗时通常≤300ms。
  2. 核心原理
    基于「文件内容唯一性校验」,核心逻辑是:
  • 对文件内容生成唯一标识(哈希值),相同文件的哈希值绝对一致;
  • 上传前先向服务器查询该哈希值是否存在,存在则直接关联文件,不存在则执行正常上传。

三、分片上传

  1. 核心定义
    将大文件按固定大小拆分为多个「小分片」(如 1MB、5MB),通过多请求并行 / 串行上传分片,服务器接收后按顺序合并为完整文件的技术。核心是「分而治之」,降低单次传输的文件体积。
  2. 核心原理
  • 前端:将文件拆分为 N 个分片,为每个分片分配唯一索引(如 0、1、2…N-1),记录总分片数、分片大小;
  • 后端:接收分片并临时存储,待所有分片上传完成后,按索引顺序合并为完整文件,最后校验文件完整性。
  1. 关键技术细节

    (1) 分片拆分规则

  • 分片大小选择:
    推荐范围:1MB~10MB;
    过小:请求次数过多(如 1GB 文件拆分为 1KB 分片,需 100 万次请求),加重服务器压力;
    过大:失去分片意义(如 1GB 文件拆分为 500MB 分片,与直接上传无差异);
  • 动态适配:根据网络速度调整(网络好→分片大,网络差→分片小),可通过navigator.connection.effectiveType获取网络类型。
  • 分片索引与标识:
    每个分片需携带「文件唯一标识(fileHash)+ 分片索引(chunkIndex)+ 总分片数(totalChunks)+ 分片哈希(chunkHash)」;
  • 注意:fileHash 用于关联同一文件的所有分片,chunkIndex 用于合并时排序,chunkHash 用于校验分片完整性。

(2)分片上传策略

  • 串行上传:按分片索引顺序逐一上传,上一个分片成功后再上传下一个;
    优点:逻辑简单,无并发冲突;
    缺点:上传速度慢;
  • 并行上传:同时上传多个分片;
    优点:充分利用带宽,提升上传速度;
    缺点:需控制并发数(推荐 3~5 个),避免服务器连接数耗尽;
  • 优先级上传:先上传前几个分片(如前 3 个),用于文件预览(如视频封面生成),再并行上传剩余分片。

(3)服务器分片处理

  • 临时存储:分片上传时,服务器需将分片存储在临时目录(如/temp/{fileHash}/{chunkIndex});
  • 分片校验:接收分片后,计算分片哈希并与客户端传递的 chunkHash 对比,不一致则拒绝存储,要求重传;
  • 合并触发:客户端所有分片上传完成后,发送「合并请求」,服务器触发合并逻辑;
  • 合并逻辑:按 chunkIndex 顺序读取所有分片,写入目标文件,合并完成后删除临时分片目录;
  • 完整性校验:合并后计算完整文件的哈希值,与客户端传递的 fileHash 对比,一致则上传成功。

四、断点续传

  1. 核心定义
    当上传中断(断网、浏览器关闭、客户端崩溃)后,再次上传同一文件时,无需从头开始,仅上传未完成的分片 / 部分的技术。核心是「记录上传进度」。
  2. 核心原理
  • 上传进度记录:通过「客户端本地存储」或「服务器存储」记录已上传的分片 / 文件位置;
  • 断点恢复:再次上传时,查询已上传进度,仅传输未完成部分;
  • 依赖关系:断点续传通常依赖分片上传(将文件拆分为分片后,进度可精确到「分片级别」)。
  1. 关键技术细节
    (1)进度记录方式
  • 客户端存储:
    存储位置:localStorage(小容量,适合存储分片索引列表)、IndexedDB(大容量,适合存储大文件进度);
    存储内容:fileHash→{已上传分片索引列表、文件元信息};
    优点:无需服务器参与,查询速度快;
    缺点:仅支持本设备续传,清除浏览器数据后进度丢失;
  • 服务器存储:
    存储位置:数据库(MySQL/PostgreSQL)或缓存(Redis);
    存储内容:fileHash→{已上传分片索引列表、文件元信息、上传时间戳};
    优点:支持跨设备续传(如手机上传一半,电脑继续传);
    缺点:增加服务器存储压力,需定时清理过期进度记录。
    (2)断点类型与处理
  • 主动中断(用户手动暂停):
    客户端:记录当前已上传分片列表,暂停所有未完成上传请求;
    恢复:用户点击继续后,查询已上传分片,仅上传缺失部分;
  • 被动中断(断网、浏览器关闭):
    客户端:下次打开页面时,通过 fileHash 查询本地 / 服务器的进度记录;
    恢复:自动触发续传,或提示用户是否继续上传;
  • 过期中断(上传长时间未完成):
    服务器:设置过期时间(如 24 小时),超过时间未完成上传则删除已存储的分片和进度记录;
    客户端:再次上传时,服务器返回「文件已过期」,需重新上传。

举例子:

<template><divclass="upload-container"><h2>Vue3 大文件上传(秒传+分片+断点续传)</h2><!--文件选择--><input type="file"ref="fileInput"@change="handleFileSelect"class="file-input"/><!--操作按钮--><divclass="btn-group"><button@click="handleUpload":disabled="!file || isUploading">{{isUploading?'上传中...':'开始上传'}}</button><button@click="handlePause":disabled="!isUploading">暂停上传</button></div><!--进度展示--><divclass="progress-wrap"><divclass="progress-bar":style="{ width: `${progress}%` }"></div></div><pclass="progress-text">进度:{{progress}}%</p><!--状态提示--><pclass="status">{{statusText}}</p></div></template><script setup>import{ref,reactive}from'vue'importaxios from'axios'importSparkMD5 from'spark-md5'// 核心配置constBASE_URL='http://localhost:3000'// 后端地址constCHUNK_SIZE=5*1024*1024// 分片大小:5MBconstCONCURRENT_NUM=3// 并行上传数:3个// 响应式数据constfileInput=ref(null)constfile=ref(null)// 选中的文件constfileHash=ref('')// 文件唯一哈希constprogress=ref(0)// 上传进度constisUploading=ref(false)// 是否正在上传conststatusText=ref('请选择文件开始上传')// 状态提示constuploadedChunks=ref([])// 已上传分片索引constuploadTasks=ref([])// 上传任务队列(用于暂停)constisPaused=ref(false)// 是否暂停/** * 步骤1:选择文件 */consthandleFileSelect=(e)=>{constselectedFile=e.target.files[0]if(!selectedFile)returnfile.value=selectedFile statusText.value=`已选择文件:${selectedFile.name}(${formatSize(selectedFile.size)})` progress.value=0}/** * 辅助函数:格式化文件大小(字节→MB/KB) */constformatSize=(bytes)=>{if(bytes<1024)return`${bytes}B`if(bytes<1024*1024)return`${(bytes/1024).toFixed(2)}KB`return`${(bytes/(1024*1024)).toFixed(2)}MB`}/** * 步骤2:计算文件哈希(秒传/断点续传的核心标识) */constcalculateFileHash=async(file)=>{returnnewPromise((resolve)=>{statusText.value='正在计算文件哈希...'constspark=newSparkMD5.ArrayBuffer()constreader=newFileReader()constslice=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSliceconsttotalChunks=Math.ceil(file.size/CHUNK_SIZE)let currentChunk=0// 递归读取分片计算哈希(避免大文件卡顿)constloadNextChunk=()=>{conststart=currentChunk*CHUNK_SIZEconstend=Math.min(start+CHUNK_SIZE,file.size)reader.readAsArrayBuffer(slice.call(file,start,end))}reader.onload=(e)=>{spark.append(e.target.result)currentChunk++// 进度提示statusText.value=`计算哈希中:${Math.ceil((currentChunk/totalChunks)*100)}%`if(currentChunk>=totalChunks){consthash=spark.end()fileHash.value=hashresolve(hash)return}loadNextChunk()}loadNextChunk()})}/** * 步骤3:秒传校验 + 断点续传查询 */constcheckUploadStatus=async(hash,fileName)=>{try{constres=awaitaxios.post(`${BASE_URL}/check`,{fileHash:hash,fileName:fileName})if(res.data.success){// 秒传成功:直接完成progress.value=100statusText.value=`秒传成功!文件地址:${res.data.fileUrl}` isUploading.value=falsereturntrue}else{// 秒传失败:获取已上传分片(断点续传)uploadedChunks.value=res.data.uploadedChunks||[]statusText.value=`秒传失败,已上传分片数:${uploadedChunks.value.length}`returnfalse}}catch(err){statusText.value=`校验失败:${err.message}` isUploading.value=falsereturnfalse}}/** * 步骤4:上传单个分片 */constuploadChunk=async(chunk,chunkIndex,totalChunks)=>{constformData=newFormData()formData.append('fileHash',fileHash.value)formData.append('chunkIndex',chunkIndex)formData.append('totalChunks',totalChunks)formData.append('chunk',chunk)try{awaitaxios.post(`${BASE_URL}/upload-chunk`,formData,{onUploadProgress:(e)=>{// 计算整体进度constchunkProgress=(e.loaded/e.total)/totalChunks*100constbaseProgress=(uploadedChunks.value.length/totalChunks)*100progress.value=Math.min(baseProgress+chunkProgress,100)}})// 记录已上传分片uploadedChunks.value.push(chunkIndex)returntrue}catch(err){statusText.value=`分片${chunkIndex}上传失败:${err.message}`returnfalse}}/** * 步骤5:合并分片 */constmergeChunks=async(totalChunks)=>{try{constres=awaitaxios.post(`${BASE_URL}/merge`,{fileHash:fileHash.value,fileName:file.value.name,totalChunks:totalChunks})if(res.data.success){progress.value=100statusText.value=`上传成功!文件地址:${res.data.fileUrl}`}else{statusText.value='分片合并失败'}isUploading.value=false}catch(err){statusText.value=`合并失败:${err.message}` isUploading.value=false}}/** * 核心:开始上传 */consthandleUpload=async()=>{if(!file.value){statusText.value='请先选择文件'return}if(isUploading.value&&isPaused.value){// 暂停后继续上传isPaused.value=falsestatusText.value='恢复上传...'return}isUploading.value=trueisPaused.value=falsestatusText.value='初始化上传...'// 1. 计算文件哈希awaitcalculateFileHash(file.value)// 2. 秒传校验 + 断点查询constisInstantUpload=awaitcheckUploadStatus(fileHash.value,file.value.name)if(isInstantUpload)return// 3. 分片上传consttotalChunks=Math.ceil(file.value.size/CHUNK_SIZE)constunUploadedChunks=[]// 筛选未上传的分片for(let i=0;i<totalChunks;i++){if(!uploadedChunks.value.includes(i)){unUploadedChunks.push(i)}}if(unUploadedChunks.length===0){// 所有分片已上传,直接合并awaitmergeChunks(totalChunks)return}statusText.value=`开始上传分片(共${totalChunks}片,待传${unUploadedChunks.length}片)`// 并行上传(控制并发数)let index=0constuploadNext=async()=>{if(isPaused.value||index>=unUploadedChunks.length)returnconstchunkIndex=unUploadedChunks[index]index++// 切割分片conststart=chunkIndex*CHUNK_SIZEconstend=Math.min(start+CHUNK_SIZE,file.value.size)constchunk=file.value.slice(start,end)// 上传当前分片consttask=uploadChunk(chunk,chunkIndex,totalChunks).then(()=>{uploadTasks.value=uploadTasks.value.filter(t=>t!==task)// 递归上传下一个uploadNext()})uploadTasks.value.push(task)}// 启动并发上传for(let i=0;i<CONCURRENT_NUM;i++){uploadNext()}// 等待所有分片上传完成awaitPromise.all(uploadTasks.value)if(!isPaused.value){// 合并分片awaitmergeChunks(totalChunks)}}/** * 暂停上传 */consthandlePause=()=>{isPaused.value=trueisUploading.value=falsestatusText.value='已暂停上传,可点击「开始上传」恢复'}</script><style scoped>.upload-container{width:600px;margin:50px auto;padding:20px;border:1px solid #eee;border-radius:8px;}.file-input{margin-bottom:20px;}.btn-group{margin-bottom:15px;}button{padding:8px20px;margin-right:10px;background:#409eff;color:#fff;border:none;border-radius:4px;cursor:pointer;}button:disabled{background:#ccc;cursor:not-allowed;}.progress-wrap{height:10px;width:100%;background:#eee;border-radius:5px;margin-bottom:10px;}.progress-bar{height:100%;background:#409eff;border-radius:5px;transition:width0.3s;}.progress-text{margin:0010px0;color:#666;}.status{color:#333;line-height:1.5;}</style>

总结

以上就是今天要讲的内容,本文仅仅简单介绍了大文件上传:秒传、断点续传、分片上传知识点,熟练掌握还是需要去试实践

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 1:22:25

ComfyUI-Manager终极问题解决指南:从新手到高手

ComfyUI-Manager终极问题解决指南&#xff1a;从新手到高手 【免费下载链接】ComfyUI-Manager 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-Manager 你是否在使用ComfyUI-Manager时遇到过各种奇怪的问题&#xff1f;安装失败、节点冲突、依赖错误...这些看似…

作者头像 李华
网站建设 2026/3/11 15:09:59

前端新手必看:彻底搞懂JS Event对象(附实战技巧+避坑指南)

前端新手必看&#xff1a;彻底搞懂JS Event对象&#xff08;附实战技巧避坑指南&#xff09;前端新手必看&#xff1a;彻底搞懂JS Event对象&#xff08;附实战技巧避坑指南&#xff09;引言&#xff1a;为什么你写的事件处理总出问题&#xff1f;JavaScript 事件机制的底层逻辑…

作者头像 李华
网站建设 2026/3/29 8:24:10

CCS20代码优化前后汇编输出对比分析

深入编译器黑箱&#xff1a;CCS20优化如何让C代码“飞”起来&#xff1f;你有没有写过一段看起来很简洁的C函数&#xff0c;结果在中断里一跑&#xff0c;发现它吃掉了大半CPU时间&#xff1f;我遇到过。那是一个二阶IIR滤波器&#xff0c;逻辑清晰、变量命名规范、注释齐全——…

作者头像 李华
网站建设 2026/3/30 7:09:09

WindowsCleaner:彻底解决C盘爆红问题的智能清理专家

WindowsCleaner&#xff1a;彻底解决C盘爆红问题的智能清理专家 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 当你打开电脑&#xff0c;看到C盘容量条变成刺眼的…

作者头像 李华
网站建设 2026/3/11 20:50:38

ComfyUI-Manager版本升级终极指南:从风险评估到完美迁移

ComfyUI-Manager版本升级终极指南&#xff1a;从风险评估到完美迁移 【免费下载链接】ComfyUI-Manager 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-Manager 还在为ComfyUI-Manager升级后节点失效、配置丢失而烦恼吗&#xff1f;作为ComfyUI生态中负责节点管…

作者头像 李华
网站建设 2026/4/1 19:35:44

转行无头绪?传统算法 vs 大模型 咋选?

聊到AI工程师&#xff0c;很多人可能会觉得都是写代码、搞模型的“技术大佬”&#xff0c;但其实这里面分两大“门派”&#xff1a;传统算法工程师和AI大模型应用开发工程师。 简单说&#xff0c;一个主打“让模型变聪明”&#xff0c;一个专攻“让聪明的模型有用”&#xff0c…

作者头像 李华