news 2026/2/12 19:33:08

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进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/13 1:22:43

真正厉害的销售,都摸透了人性!

你会发现一个很有意思的现象&#xff1a;在销售行业里&#xff0c;资源差一点没关系&#xff0c;经验少一点也能补&#xff0c;但如果你不懂人性&#xff0c;那真的很难往高手那条路走。为啥&#xff1f; 因为销售本质不是“我把产品介绍给你”&#xff0c;而是&#xff1a;我让…

作者头像 李华
网站建设 2026/2/2 7:38:23

如何彻底修改Dify默认80端口

如何彻底修改 Dify 默认 80 端口 在部署 AI 应用开发平台时&#xff0c;端口冲突几乎是每个工程师都会遇到的“第一道坎”。Dify 作为当前热门的开源 LLM 应用构建平台&#xff0c;默认使用 80 和 443 端口提供 Web 服务。但现实往往没那么理想&#xff1a;你可能已经运行了 Ng…

作者头像 李华
网站建设 2026/2/7 1:33:58

【C 语言进阶】一文吃透文件指针与偏移量的核心关系

引言在 C 语言文件操作中&#xff0c;“文件指针” 和 “偏移量” 是两个绕不开的核心概念&#xff0c;也是很多初学者容易混淆的知识点。比如&#xff1a;为什么fseek(pf,4,SEEK_SET)读取到的是e而不是d&#xff1f;SEEK_END基准下偏移量该怎么算&#xff1f;今天这篇文章&…

作者头像 李华
网站建设 2026/2/7 19:30:36

2026中专生学编程,考什么证书最被认可?

凌晨的实训室里&#xff0c;键盘敲击声不绝于耳&#xff0c;一群年轻学生专注地盯着屏幕上跳动的代码&#xff0c;他们手中各类技能证书的复印件&#xff0c;被整齐地放在简历最显眼的位置。前不久&#xff0c;一场技术类岗位招聘会上&#xff0c;某大型通信企业的面试官看到中…

作者头像 李华
网站建设 2026/2/7 7:04:22

LobeChat天气预报实时查询实现方式

LobeChat天气预报实时查询实现方式 在智能对话系统日益普及的今天&#xff0c;用户早已不再满足于“你好”“再见”式的简单互动。他们期待的是一个能听懂需求、主动办事的数字助手——比如随口一句“今天北京热吗&#xff1f;”&#xff0c;就能立刻得到准确的气温与穿衣建议。…

作者头像 李华