当前位置: 首页 > news >正文

教育行业案例:jQuery如何集成百度WebUploader实现学校官网课件的自动分片续传与水印处理?

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

处理事件

启动测试

启动成功

效果

数据库

下载示例

点击下载完整示例

http://www.jsqmd.com/news/509000/

相关文章:

  • Z-Image Turbo模型溯源:HuggingFace模型卡与训练数据声明
  • 如何选择最佳优化器:PyTorch分割模型AdamW与SGD性能对比指南
  • Kohya_SS图像标注功能完整指南:解决AI训练中的关键标注问题
  • Odoo数据仓库设计终极指南:星型模型与ETL流程完整实现方案
  • psst多语言支持:如何为跨平台Spotify客户端添加新的界面语言
  • 如何在Koel个人音乐服务器中管理播客:完整指南与技巧
  • 从零到精通:Instruments Leaks内存检测全流程指南(含Xcode调试配置)
  • 终极指南:如何使用einops简化从Keras到PyTorch的代码迁移过程
  • InstructPix2Pix与软件测试:自动化测试图像生成
  • 百度网盘提取码查询终极指南:3秒获取任何资源访问权限
  • PowerPaint-V1图像修复工具实测:智能识别背景纹理,无痕移除画面中的人和杂物
  • 滑模控制 vs MPC vs LQR:自动驾驶横向控制算法选型指南(实测数据对比)
  • 汽车制造经验:JS如何基于百度WebUploader插件实现设计图纸的加密分片断点续传与校验?
  • FLUX小红书V2软件测试全攻略:确保生成质量稳定
  • 终极指南:如何在微服务架构中集成ClickHouse实现实时数据分析
  • 视频转文字工具
  • GPT-SoVITS完整使用指南:结合FFmpeg处理音频,打造高质量作品
  • 终极指南:如何利用EinOps消除深度学习实验中的随机因素,提升结果可复现性
  • 打卡信奥刷题(2985)用C++实现信奥题 P6070 『MdOI R1』Decrease
  • 能源化工场景:JS如何通过百度WebUploader组件实现生产数据大附件的秒传断点恢复与日志记录?
  • Qwen3-VL:30B模型微调:使用Visio绘制技术架构图
  • Qwen-Image实际作品:基于RTX4090D的Qwen-VL在农业病虫害图像识别中的应用
  • Nanbeige 4.1-3B开源镜像:支持FP16/INT4量化部署的多精度版本
  • Qwen-Image企业部署:基于RTX4090D的Qwen-VL服务化封装与负载均衡实践
  • 如何用Goutte进行网页数据抓取并与机器学习智能分析结合
  • 从研究到生产:Einops如何通过统一API确保深度学习代码一致性的终极指南
  • ClickHouse数据可视化:5种最佳工具集成方案详解
  • 打卡信奥刷题(2986)用C++实现信奥题 P6075 [JSOI2015] 子集选取
  • Qwen-Image镜像保姆级教学:为算法工程师定制的Qwen-VL推理避坑指南
  • 终极Web Font Loader优化指南:如何通过Tree-Shaking只引入需要的字体模块