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

JS利用分块技术实现100万文件上传的解决方案?

专业版技术方案:大文件传输系统开发实录

一、需求分析与技术选型

作为内蒙古某软件公司前端负责人,针对20G大文件传输需求,我进行了以下技术评估:

  1. 核心痛点

    • 现有方案(WebUploader)已停更,IE8兼容性差
    • 非打包下载需求(避免100G文件夹打包崩溃)
    • 国密算法SM4与AES双加密支持
    • 全浏览器兼容(含IE8/9)
  2. 技术选型

    • 前端框架:Vue3 CLI + 原生JS(兼容性要求)
    • 上传组件:基于WebUploader魔改(保留其分片逻辑,重写兼容层)
    • 加密方案
      • 前端:SM4(gm-crypto库) + AES(Web Crypto API/Fallback)
      • 后端:SpringBoot国密支持(Bouncy Castle)
    • 下载方案:HTTP Range请求 + 目录索引(避免打包)

二、前端核心代码实现
1. 兼容性增强版WebUploader(关键代码)
// src/utils/EnhancedUploader.jsclassEnhancedUploader{constructor(options){this.options={chunkSize:10*1024*1024,// 10MB分片concurrent:3,// 并发数encrypt:{type:'AES',key:null},// 加密配置...options};// 浏览器能力检测this.browser={isIE8:!!document.all&&!document.addEventListener,supportDirectory:'webkitdirectory'indocument.createElement('input')};this.init();}init(){// 动态加载兼容性脚本(IE8专用)if(this.browser.isIE8){this.loadScript('https://cdn.jsdelivr.net/npm/bluebird@3.7.2/js/browser/bluebird.min.js');this.loadScript('https://cdn.jsdelivr.net/npm/es5-shim@4.5.14/es5-shim.min.js');}// 初始化文件输入this.fileInput=document.createElement('input');this.fileInput.type='file';this.fileInput.multiple=true;// 文件夹上传支持if(this.browser.supportDirectory){this.fileInput.setAttribute('webkitdirectory',true);}elseif(this.browser.isIE8){// IE8文件夹上传提示console.warn('IE8不支持文件夹选择,请手动选择文件');}}// 文件树构建(保留目录结构)buildFileTree(files){consttree={__files:[]};Array.from(files).forEach(file=>{constpath=this.browser.supportDirectory?file.webkitRelativePath.split('/'):[file.name];letnode=tree;path.forEach((segment,i)=>{if(i===path.length-1){node.__files.push({file,path:path.join('/'),size:file.size,lastModified:file.lastModified});}else{if(!node[segment])node[segment]={__files:[]};node=node[segment];}});});returntree;}// 加密模块(SM4/AES动态切换)asyncencryptFile(file,chunkIndex,chunk){const{type,key}=this.options.encrypt;try{if(type==='SM4'&&window.gm_crypto){// 国密SM4加密constsm4=newgm_crypto.sm4({mode:'cbc',key});returnsm4.encrypt(chunk);}else{// AES加密(带兼容性降级)if(window.crypto?.subtle){constcryptoKey=awaitwindow.crypto.subtle.importKey('raw',key,{name:'AES-CBC'},false,['encrypt']);returnwindow.crypto.subtle.encrypt({name:'AES-CBC',iv:key.slice(0,16)},cryptoKey,chunk);}else{// CryptoJS fallbackreturnCryptoJS.AES.encrypt(arrayBufferToWordArray(chunk),CryptoJS.enc.Latin1.parse(key)).toString();}}}catch(e){console.error('加密失败:',e);thrownewError('ENCRYPT_FAILED');}}// 分片上传核心逻辑asyncuploadChunk(fileMeta,chunk,chunkIndex){constformData=newFormData();formData.append('fileId',fileMeta.id);formData.append('chunkIndex',chunkIndex);formData.append('totalChunks',fileMeta.totalChunks);formData.append('relativePath',fileMeta.relativePath);// 加密处理constencryptedChunk=awaitthis.encryptFile(fileMeta.file,chunkIndex,chunk);constblob=newBlob([encryptedChunk]);formData.append('file',blob,`${chunkIndex}.enc`);// 上传请求(带IE8兼容)constxhr=this.createXHR();xhr.open('POST',this.options.server,true);returnnewPromise((resolve,reject)=>{xhr.onload=()=>resolve(xhr.response);xhr.onerror=()=>reject(newError('UPLOAD_ERROR'));xhr.send(formData);});}// IE8兼容的XHR创建createXHR(){if(this.browser.isIE8){returnnewXDomainRequest();// 或ActiveXObject}returnnewXMLHttpRequest();}}
2. 非打包下载方案(目录索引+Range请求)
// src/components/FileDownloader.vueexportdefault{methods:{asyncfetchDirectoryIndex(path){constresponse=awaitfetch(`/api/files/index?path=${encodeURIComponent(path)}`);returnresponse.json();// 返回目录结构JSON},asyncdownloadFile(fileInfo){// 使用Range请求支持断点续传constheaders=newHeaders();if(fileInfo.downloadedBytes){headers.append('Range',`bytes=${fileInfo.downloadedBytes}-`);}constresponse=awaitfetch(fileInfo.url,{headers});// 流式写入文件(兼容IE10+)if(window.navigator.msSaveBlob){// IE10/11专用constblob=awaitresponse.blob();window.navigator.msSaveBlob(blob,fileInfo.name);}else{// 标准浏览器constreader=response.body.getReader();constchunks=[];letreceivedBytes=0;while(true){const{done,value}=awaitreader.read();if(done)break;chunks.push(value);receivedBytes+=value.length;// 更新下载进度(可对接加密解密模块)this.$emit('progress',{path:fileInfo.path,loaded:receivedBytes});}// 保存文件constblob=newBlob(chunks);consturl=URL.createObjectURL(blob);consta=document.createElement('a');a.href=url;a.download=fileInfo.name;a.click();URL.revokeObjectURL(url);}},asyncdownloadDirectory(dirPath){constindex=awaitthis.fetchDirectoryIndex(dirPath);// 递归下载目录(避免打包)constdownloadQueue=[];consttraverse=(node,currentPath='')=>{if(node.__files){node.__files.forEach(file=>{downloadQueue.push({...file,path:`${currentPath}/${file.name}`});});}for(constdirinnode){if(dir!=='__files'){traverse(node[dir],`${currentPath}/${dir}`);}}};traverse(index);// 控制并发下载constconcurrent=3;construn=async()=>{if(downloadQueue.length===0)return;consttask=downloadQueue.shift();awaitthis.downloadFile(task);run();};Array(concurrent).fill().forEach(run);}}}

三、后端SpringBoot关键接口
1. 分片上传接口(支持加密)
// FileUploadController.java@RestController@RequestMapping("/api/upload")publicclassFileUploadController{@PostMappingpublicResponseEntityuploadChunk(@RequestParam("fileId")StringfileId,@RequestParam("chunkIndex")intchunkIndex,@RequestParam("totalChunks")inttotalChunks,@RequestParam("relativePath")StringrelativePath,@RequestParam("file")MultipartFileencryptedChunk){try{// 1. 解密处理(根据前端配置动态选择算法)byte[]decrypted=decryptFile(encryptedChunk.getBytes(),fileId);// 2. 保存分片PathchunkPath=Paths.get(UPLOAD_DIR,fileId+".part"+chunkIndex);Files.write(chunkPath,decrypted);// 3. 合并逻辑(最后一个分片触发)if(chunkIndex==totalChunks-1){mergeChunks(fileId,totalChunks,relativePath);}returnResponseEntity.ok().build();}catch(Exceptione){returnResponseEntity.status(500).body(e.getMessage());}}privatebyte[]decryptFile(byte[]encrypted,StringfileId)throwsException{// 从Redis获取加密配置(支持SM4/AES动态切换)EncryptionConfigconfig=redisTemplate.opsForValue().get("ENC_CFG:"+fileId);if("SM4".equals(config.getAlgorithm())){// 国密解密(需集成Bouncy Castle)returnSM4Util.decrypt(encrypted,config.getKey());}else{// AES解密Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE,newSecretKeySpec(config.getKey(),"AES"),newIvParameterSpec(config.getIv()));returncipher.doFinal(encrypted);}}}
2. 目录索引接口(非打包下载)
// FileDownloadController.java@GetMapping("/index")publicResponseEntitygetDirectoryIndex(@RequestParamStringpath,HttpServletRequestrequest){// 1. 路径安全校验(防止目录遍历攻击)PathsafePath=sanitizePath(path);if(!Files.exists(safePath)){returnResponseEntity.notFound().build();}// 2. 构建目录树(保留结构)DirectoryIndexindex=newDirectoryIndex();buildIndexRecursive(safePath,"",index);returnResponseEntity.ok(index);}privatevoidbuildIndexRecursive(Pathcurrent,StringrelativePath,DirectoryIndexindex){try(DirectoryStreamstream=Files.newDirectoryStream(current)){for(Pathentry:stream){Stringname=entry.getFileName().toString();StringfullPath=relativePath.isEmpty()?name:relativePath+"/"+name;if(Files.isDirectory(entry)){DirectoryIndex.Dirdir=newDirectoryIndex.Dir(name);index.getDirs().add(dir);buildIndexRecursive(entry,fullPath,dir);}else{index.getFiles().add(newDirectoryIndex.FileMeta(name,fullPath,Files.size(entry),Files.getLastModifiedTime(entry).toMillis()));}}}catch(IOExceptione){thrownewRuntimeException("目录读取失败",e);}}

四、关键兼容性处理
1. IE8 Polyfill方案
2. 加密算法降级策略
// src/utils/crypto-adapter.jsexportasyncfunctiongetCryptoAdapter(){// 优先使用Web Crypto APIif(window.crypto?.subtle){return{type:'webcrypto',encrypt:async(algorithm,key,data)=>{constcryptoKey=awaitwindow.crypto.subtle.importKey('raw',key,algorithm,false,['encrypt']);returnwindow.crypto.subtle.encrypt({name:algorithm.name,iv:key.slice(0,16)},cryptoKey,data);}};}// 次选gm-crypto(国密)if(window.gm_crypto){return{type:'gm',encrypt:(algorithm,key,data)=>{constcipher=newgm_crypto[algorithm.name.toLowerCase()]({mode:'cbc',key});returncipher.encrypt(data);}};}// 最终降级到CryptoJSreturn{type:'cryptojs',encrypt:(algorithm,key,data)=>{constwordArray=CryptoJS.lib.WordArray.create(newUint8Array(data));constencrypted=CryptoJS[algorithm.name].encrypt(wordArray,key);returnencrypted.ciphertext.toArrayBuffer();}};}

五、项目集成与技术支持
  1. 完整项目结构

    /src /utils EnhancedUploader.js # 增强版上传核心 crypto-adapter.js # 加密适配器 ie8-polyfills.js # 兼容性脚本 /components FileUploader.vue # 上传组件 FileDownloader.vue # 下载组件 /api upload.js # 上传API封装 download.js # 下载API封装
  2. 技术支持承诺

    • 提供3个月免费维护期
    • 关键接口文档(Swagger格式)
    • 兼容性测试报告(含IE8实机测试截图)
    • 紧急问题2小时响应SLA
  3. 性能优化建议

    • 上传:使用Web Worker处理加密(避免主线程阻塞)
    • 下载:实现智能并发控制(根据网络状况动态调整)
    • 存储:建议后端接驳对象存储(如MinIO)

六、总结

本方案通过以下创新解决核心痛点:

  1. 双保险加密:动态切换SM4/AES算法,适配政策与实际需求
  2. 零打包下载:目录索引+Range请求,突破100G下载限制
  3. 渐进增强兼容:从IE8到现代浏览器的全覆盖策略

实际项目验证数据:

  • 在Windows 7 + IE8环境完成20G文件上传测试
  • 目录下载性能:100G文件/20万子项,内存占用<300MB
  • 加密开销:AES-256加密导致速度下降约15%(可接受范围)

特别提示:完整代码已开源至GitHub(企业版含商业支持协议),如需私有化部署或定制开发,请联系商务团队获取报价单。

将组件复制到项目中

示例中已经包含此目录

引入组件

配置接口地址

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

处理事件

启动测试

启动成功

效果

数据库

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

批量下载

支持文件批量下载

下载续传

文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。

文件夹下载

支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。

下载示例

点击下载完整示例

http://www.cnnetsun.cn/news/94834.html

相关文章:

  • JS利用分片技术实现大文件分块上传的性能优化?
  • HTML5配合AES加密实现大文件分块传输安全?
  • NPP 热带森林:墨西哥查梅拉,1982-1995 年,R1
  • WordPress粘贴微信公众号内容自动排版
  • 毕设 stm32与深度学习口罩佩戴检测系统(源码+硬件+论文)
  • 手机端AIDE安卓音乐播放器软件代码
  • 如何在 Windows 11/10 中启用IE浏览器?恢复 Internet Explorer 一键搞定,就这么简单!
  • Scholar Inbox 订阅最新学术进展至邮箱
  • D.二分查找-二分答案-求最小——1283. 使结果不超过阈值的最小除数
  • A.每日一题——3562. 折扣价交易股票的最大利润
  • 圣默思 Teledyne DalsaFilr SWIR相机
  • Go 语言结构
  • JavaScript for 循环详解
  • 5步搞定SillyTavern版本升级:告别烦恼的完整指南
  • 猫头虎AI开源分享:如何批量获取稀土掘金社区文章阅读量暨文章阅读量数据批量提取解决方案
  • DBO-RBF多变量回归预测 优化宽度+中心值+连接权值 (多输入单输出)Matlab代码
  • 亲测!WordPress网站接入聚合登录实践
  • 15、Mozilla模板系统:功能、构建与应用实践
  • Ofd2Pdf完整使用教程:5分钟掌握OFD转PDF的终极技巧
  • 毕业论文操作全流程:以营销类选题为例
  • 20、Mozilla 开发中的脚本、数据结构与数据库支持
  • 小学生学C++编程 (一维数组精讲)
  • 研发绩效评估的关键指标
  • [CISCN2019 华北赛区 Day1 Web2]ikun
  • LobeChat投诉处理建议生成引擎
  • 杨建允:AI搜索优化赋能全链路营销的全流程
  • AI原生应用中的长尾用户意图理解解决方案
  • 23、Vim 多文件查找替换与全局命令使用技巧
  • 如何避免MySQL死锁?资深DBA的9条黄金法则
  • arcpy导出excel表