news 2026/6/17 7:53:27

vue3结合WebUploader实现100G文件分片上传加密存储

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vue3结合WebUploader实现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/6/12 13:58:25

GLM-4.6V-Flash-WEB模型对台风登陆路径的卫星图像理解

GLM-4.6V-Flash-WEB模型对台风登陆路径的卫星图像理解 在沿海城市防灾减灾体系中&#xff0c;台风路径预测一直是气象工作的“硬骨头”。传统方式依赖数值模拟与专家经验结合&#xff0c;从接收到卫星云图到发布预警报告&#xff0c;往往需要数小时的人工研判。而当一场强台风正…

作者头像 李华
网站建设 2026/6/6 15:17:10

2026年人力外派公司怎么选?一份基于五大类型对比的决策指南

在技术驱动业务发展的今天&#xff0c;灵活、高效地获取专业人才已成为企业保持竞争力的关键。人力外派公司作为连接企业与专业技术人才的桥梁&#xff0c;其市场正朝着精细化、场景化方向快速演进。面对众多服务商&#xff0c;企业决策者亟需一套清晰的选型框架。本指南基于行…

作者头像 李华
网站建设 2026/6/10 20:12:59

GLM-4.6V-Flash-WEB模型在考古现场图像记录中的辅助功能

GLM-4.6V-Flash-WEB模型在考古现场图像记录中的辅助功能 在偏远的考古工地&#xff0c;烈日下&#xff0c;一位研究员正蹲在探方边缘&#xff0c;对着刚出土的一堆陶片拍照。他一边拍摄&#xff0c;一边用笔在本子上快速记下土色、质地、分布位置——这是传统考古现场最常见的一…

作者头像 李华
网站建设 2026/6/15 20:53:36

GLM-4.6V-Flash-WEB模型在机场安检图像辅助判读中的设想

GLM-4.6V-Flash-WEB模型在机场安检图像辅助判读中的设想在大型国际机场的高峰时段&#xff0c;一条安检通道每小时要处理上百件行李&#xff0c;X光机屏幕前的安检员必须在几秒内判断每一幅透视图像中是否存在违禁品。长时间高强度作业下&#xff0c;视觉疲劳和注意力分散难以避…

作者头像 李华
网站建设 2026/5/30 5:11:54

酒店客房电视,如何从“背景音”变为“体验加分项”?

在宾客体验被无限细分的今天&#xff0c;酒店经营者们正努力打磨每一个触点。从床品的舒适度到洗护用品的香气&#xff0c;细节无处不在。然而&#xff0c;有一个存在感极强却常被忽略的环节——客房内的电视体验。你是否也常听到类似的反馈&#xff1f;“电视节目好无聊&#…

作者头像 李华
网站建设 2026/6/13 12:25:53

GLM-4.6V-Flash-WEB模型能否识别验证码图片?攻防视角分析

GLM-4.6V-Flash-WEB模型能否识别验证码图片&#xff1f;攻防视角分析 在当前AI能力飞速演进的背景下&#xff0c;一个现实而紧迫的问题浮出水面&#xff1a;那些曾经被视为“人类专属”的交互门槛——比如验证码&#xff0c;是否还能真正阻挡自动化程序&#xff1f;随着多模态大…

作者头像 李华