news 2026/1/2 9:57:38

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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

处理事件

启动测试

启动成功

效果

数据库

效果预览

文件上传

文件刷新续传

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

文件夹上传

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

批量下载

支持文件批量下载

下载续传

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

文件夹下载

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

下载示例

点击下载完整示例

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

LobeChat能否集成潮汐信息?沿海地区活动安排建议

LobeChat能否集成潮汐信息&#xff1f;沿海地区活动安排建议 在福建连江的清晨&#xff0c;渔民老陈站在码头边皱眉翻着手机——他需要知道今天退潮的时间是否适合下滩挖蛤蜊&#xff0c;还得看风浪大不大、天气好不好。可他得先打开一个App查潮汐&#xff0c;再切到另一个看天…

作者头像 李华
网站建设 2025/12/17 0:49:43

GEE训练教程:利用 Google Earth Engine 分析广州地区植被动态变化(2016-2025)

利用 Google Earth Engine 分析广州地区植被动态变化(2016-2025) 一、项目概述 本文将通过 Google Earth Engine(GEE)平台,结合 Sentinel-2 卫星遥感数据,对广州地区的植被覆盖状况进行长达十年的动态监测。我们将从数据准备、预处理到植被指数计算,完整展示如何利用云…

作者头像 李华
网站建设 2025/12/17 0:49:42

WordPress跨平台兼容OA系统word上传需求

要求&#xff1a;开源&#xff0c;免费&#xff0c;技术支持 博客&#xff1a;WordPress 开发语言&#xff1a;PHP 数据库&#xff1a;MySQL 功能&#xff1a;导入Word,导入Excel,导入PPT(PowerPoint),导入PDF,复制粘贴word,导入微信公众号内容,web截屏 平台&#xff1a;Window…

作者头像 李华
网站建设 2025/12/17 0:48:16

BGP 基础配置示例

在路由器上启用 BGP 并配置基本参数&#xff0c;指定本地自治系统号&#xff08;ASN&#xff09;和 BGP 路由器 ID。BGP 路由器 ID 通常使用环回接口地址或物理接口地址。router bgp 65001bgp router-id 1.1.1.1no bgp default ipv4-unicast邻居关系建立配置 BGP 邻居&#xff…

作者头像 李华
网站建设 2025/12/26 21:49:02

目标检测——锚框

基于锚框的目标检测&#xff1a; 在图像中随机生成很多个锚框&#xff0c;首先预测锚框内是否含有目标然后预测锚框与目标真实的边缘框的偏移生成锚框后&#xff0c;通过IoU(交并比)来计算两个框之间的相似度&#xff0c;0表示无重叠&#xff0c;1表示重合IoUArea(A∩B)/Area(A…

作者头像 李华