SD卡烧录总失败?balenaEtcher 3 步把树莓派镜像写进 U盘不翻车
2026/9/2 12:36:57
大文件上传是 Web 开发中高频且复杂的需求,核心痛点集中在传输稳定性、效率、服务器压力三大维度。秒传、断点续传、分片上传是解决这些问题的三大核心技术,三者并非孤立存在,而是常组合使用
关键技术细节
(1) 分片拆分规则
(2)分片上传策略
(3)服务器分片处理
举例子:
<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>以上就是今天要讲的内容,本文仅仅简单介绍了大文件上传:秒传、断点续传、分片上传知识点,熟练掌握还是需要去试实践