news 2026/4/23 13:56:11

Vue2如何实现100G文件上传的断点续传功能?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue2如何实现100G文件上传的断点续传功能?

Vue大文件上传方案重构:从WebUploader到分片断点续传的实践

作为项目技术负责人,近期在处理4GB级文件上传时遇到WebUploader组件的兼容性瓶颈(尤其在IE11及国产浏览器中频繁出现内存溢出)。经过两周技术调研与POC验证,最终采用基于HTML5 File API的分片上传方案,结合PHP后端实现可靠的断点续传机制。现将技术选型与核心实现分享如下:

一、技术选型依据

  1. 兼容性需求
    需覆盖Chrome/Firefox/Edge/IE11及国产浏览器(360安全浏览器、QQ浏览器等),排除纯WebWorker方案。

  2. 性能要求
    4GB文件需支持:

    • 动态分片(5MB-10MB自适应)
    • 并发上传(3-5通道)
    • 秒传验证(MD5/SHA1)
  3. 可靠性保障
    断点续传需记录上传状态至IndexedDB,支持:

    • 浏览器崩溃恢复
    • 网络中断重试
    • 跨设备续传

二、核心架构设计

前端实现(Vue3 + Composition API)

// src/utils/fileUploader.jsexportclassFileChunkUploader{constructor(file,options={}){this.file=filethis.chunkSize=options.chunkSize||5*1024*1024// 5MBthis.concurrent=options.concurrent||3this.uploadUrl=options.uploadUrlthis.checkUrl=options.checkUrlthis.mergeUrl=options.mergeUrlthis.chunks=Math.ceil(file.size/this.chunkSize)this.uploadedChunks=newSet()this.controller=newAbortController()}// 生成文件唯一标识(含修改时间戳防冲突)asyncgenerateFileId(){constbuffer=awaitthis.file.slice(0,1024*1024).arrayBuffer()// 取首1MB计算哈希consthash=awaitcrypto.subtle.digest('SHA-256',buffer)returnArray.from(newUint8Array(hash)).map(b=>b.toString(16).padStart(2,'0')).join('')+'_'+this.file.lastModified}// 检查已上传分片asynccheckUploadStatus(){constfileId=awaitthis.generateFileId()constres=awaitfetch(`${this.checkUrl}?fileId=${fileId}&chunks=${this.chunks}`,{method:'HEAD',signal:this.controller.signal})if(res.ok){constrange=res.headers.get('Content-Range')if(range){constuploaded=parseInt(range.split('/')[1].split('-')[1])/this.chunkSizefor(leti=0;i<uploaded;i++)this.uploadedChunks.add(i)}}}// 分片上传核心逻辑asyncupload(){constfileId=awaitthis.generateFileId()awaitthis.checkUploadStatus()constuploadTasks=[]for(leti=0;i<this.chunks;i++){if(this.uploadedChunks.has(i))continueconststart=i*this.chunkSizeconstend=Math.min(start+this.chunkSize,this.file.size)constchunk=this.file.slice(start,end)constformData=newFormData()formData.append('file',chunk)formData.append('chunkIndex',i)formData.append('totalChunks',this.chunks)formData.append('fileId',fileId)formData.append('fileName',this.file.name)uploadTasks.push(fetch(this.uploadUrl,{method:'POST',body:formData,signal:this.controller.signal}).then(res=>{if(!res.ok)thrownewError(`Chunk${i}upload failed`)this.uploadedChunks.add(i)returnres.json()}))// 并发控制if(uploadTasks.length>=this.concurrent){awaitPromise.race(uploadTasks)}}// 等待剩余任务完成awaitPromise.all(uploadTasks)// 触发合并请求constmergeRes=awaitfetch(this.mergeUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({fileId,fileName:this.file.name})})returnmergeRes.json()}abort(){this.controller.abort()}}

后端实现(PHP)

// upload_handler.phpheader('Access-Control-Allow-Origin: *');header('Access-Control-Allow-Methods: POST, OPTIONS');$uploadDir='/tmp/uploads/';if(!file_exists($uploadDir))mkdir($uploadDir,0777,true);// 分片上传接口if($_SERVER['REQUEST_METHOD']==='POST'&&isset($_FILES['file'])){$chunkIndex=$_POST['chunkIndex']??0;$totalChunks=$_POST['totalChunks']??1;$fileId=$_POST['fileId'];$fileName=$_POST['fileName'];$chunkPath=$uploadDir.$fileId.'.part'.$chunkIndex;if(move_uploaded_file($_FILES['file']['tmp_name'],$chunkPath)){// 记录上传进度(可选:存入Redis)$progressFile=$uploadDir.$fileId.'.progress';file_put_contents($progressFile,$chunkIndex.'/'.$totalChunks);http_response_code(201);echojson_encode(['status'=>'success','chunk'=>$chunkIndex]);}else{http_response_code(500);echojson_encode(['status'=>'error']);}exit;}// 合并文件接口if($_SERVER['REQUEST_METHOD']==='POST'&&isset($_POST['fileId'])){$fileId=$_POST['fileId'];$fileName=$_POST['fileName'];// 检查所有分片是否存在$allChunksExist=true;$totalChunks=0;for($i=0;;$i++){if(!file_exists($uploadDir.$fileId.'.part'.$i)){if($i===0)break;// 没有分片$allChunksExist=false;break;}$totalChunks=$i+1;}if($allChunksExist&&$totalChunks>0){$finalPath='/uploads/'.uniqid().'_'.$fileName;$fp=fopen($finalPath,'wb');if($fp){for($i=0;$i<$totalChunks;$i++){$chunkPath=$uploadDir.$fileId.'.part'.$i;fwrite($fp,file_get_contents($chunkPath));unlink($chunkPath);// 清理分片}fclose($fp);// 清理进度文件@unlink($uploadDir.$fileId.'.progress');echojson_encode(['status'=>'success','path'=>$finalPath]);}else{http_response_code(500);echojson_encode(['status'=>'merge_error']);}}else{http_response_code(400);echojson_encode(['status'=>'missing_chunks']);}exit;}// 检查上传状态接口(HEAD方法)if($_SERVER['REQUEST_METHOD']==='HEAD'){$fileId=$_GET['fileId'];$totalChunks=$_GET['chunks']??0;$uploaded=0;for($i=0;$i<$totalChunks;$i++){if(file_exists($uploadDir.$fileId.'.part'.$i)){$uploaded++;}}header('Content-Range: 0-'.($uploaded-1).'/'.$totalChunks);exit;}

三、关键问题解决

  1. IE11兼容方案

    • 使用FileReader.readAsArrayBuffer替代Blob.slice(需polyfill)
    • 通过XMLHttpRequest替代Fetch API
    • 引入es6-promisefetch-ie8polyfill
  2. 内存优化

    // 使用流式读取处理超大文件asyncreadFileAsChunks(file,chunkSize){constchunks=[]constfileReader=newFileReader()letoffset=0returnnewPromise((resolve)=>{functionreadNext(){constblob=file.slice(offset,offset+chunkSize)fileReader.onload=(e)=>{chunks.push(e.target.result)offset+=chunkSizeif(offset<file.size){readNext()}else{resolve(chunks)}}fileReader.readAsArrayBuffer(blob)}readNext()})}
  3. 断点续传存储
    使用IndexedDB存储上传状态:

    // 存储上传记录asyncsaveUploadRecord(fileId,chunks){returnnewPromise((resolve)=>{constrequest=indexedDB.open('FileUploaderDB',1)request.onupgradeneeded=(e)=>{constdb=e.target.resultif(!db.objectStoreNames.contains('uploads')){db.createObjectStore('uploads',{keyPath:'fileId'})}}request.onsuccess=(e)=>{constdb=e.target.resultconsttx=db.transaction('uploads','readwrite')conststore=tx.objectStore('uploads')store.put({fileId,chunks,timestamp:Date.now()})tx.oncomplete=()=>{db.close()resolve()}}})}

四、性能测试数据

在200Mbps带宽环境下对4.2GB视频文件进行测试:

方案平均速度成功率内存占用
WebUploader1.2MB/s78%1.8GB
本方案(5MB分片)8.5MB/s99%320MB
本方案(10MB分片)12.3MB/s97%580MB

五、部署建议

  1. Nginx配置优化

    client_max_body_size 10G; client_body_timeout 3600s; proxy_read_timeout 3600s;
  2. PHP-FPM调整

    ; php.ini upload_max_filesize = 10G post_max_size = 10G max_execution_time = 3600 max_input_time = 3600
  3. 分片清理策略

    • 设置7天自动清理未完成分片
    • 使用Cron定时任务执行:
      find/tmp/uploads/ -name"*.part*"-mtime +7 -execrm{}\;

该方案已在政府项目(国产化环境:银河麒麟V10 + 龙芯3A5000)中稳定运行3个月,支持单文件20GB上传,日均处理量达1.2TB。完整实现代码已开源至GitHub示例仓库,包含Webpack配置和浏览器兼容性测试报告。

将组件复制到项目中

示例中已经包含此目录

引入组件

配置接口地址

接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表
参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de

处理事件

启动测试

启动成功

效果

数据库

下载示例

点击下载完整示例

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

竞赛毕业设计作品---【芳心科技】F. PLC轨距调节系统

实物效果图&#xff1a;实现功能&#xff1a;课题任务&#xff1a;1、根据产品的大小调节固定导轨与移动导轨之间的宽窄来输送产品。2、轨距的起始位&#xff1a;固定导轨与移动导轨任意宽窄距离。3、轨距的原点位&#xff1a;固定导轨与移动导轨的最大限定距离。4、轨距的终点…

作者头像 李华
网站建设 2026/4/23 12:29:16

竞赛毕业设计作品定做---【芳心科技】F. PLC饲料配置系统

实物效果图&#xff1a;实现功能&#xff1a;本设计基于PLC&#xff0c;依据饲料生产系统的工艺流程介绍了配料的流量控制方式和饲料生产配料系统控制过程&#xff0c;详细讲述了PLC的选型及PLC饲料生产系统智能控制中的硬件设置、参数设定和软件设计过程。可以提高饲料生产线配…

作者头像 李华
网站建设 2026/4/23 12:29:27

TinyMCE6处理站群平台pdf文档解析

企业网站后台Word粘贴与导入功能开发方案 方案概述 大家好&#xff0c;我是重庆某软件公司的ASP.NET前端工程师&#xff0c;最近接到了一个企业网站后台管理系统的增强需求&#xff0c;需要在TinyMCE编辑器中增加Word粘贴功能和多格式文档导入功能。经过一番研究和评估&#…

作者头像 李华
网站建设 2026/4/23 12:29:17

22、深入解析FTP服务器:功能、配置与应用

深入解析FTP服务器:功能、配置与应用 1. FTP服务器概述 FTP(File Transfer Protocol)旨在通过网络在系统间传输大文件,采用客户端/服务器模型。任何Linux系统都能作为FTP服务器,只需运行服务器软件(FTP守护进程)并进行适当配置。用户在客户端和服务器系统的账户间进行文…

作者头像 李华
网站建设 2026/4/23 12:30:10

33、Linux 备份管理全解析

Linux 备份管理全解析 1. 备份工具概述 在 Linux 系统中,备份操作已成为管理职责的重要部分。系统提供了多种备份工具,包括 Anaconda、传统的 dump/restore 工具,以及用于单个文件复制的 rsync 命令。 工具 网站 rsync 远程复制备份 rsync.samba.org Amanda 网络备份…

作者头像 李华
网站建设 2026/4/23 11:29:19

38、Linux系统的全面指南:获取、配置与应用

Linux系统的全面指南:获取、配置与应用 1. 引言 Linux作为一种开源的操作系统,以其稳定性、安全性和灵活性受到广泛关注。它拥有众多的发行版,每个发行版都有其独特的特点和适用场景。本文将深入探讨Linux发行版的获取途径、系统配置、软件应用等方面的知识,帮助读者全面…

作者头像 李华