news 2026/4/23 12:48:14

医疗教育联合项目中大文件上传插件如何实现断点续传和加密?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
医疗教育联合项目中大文件上传插件如何实现断点续传和加密?

大文件上传系统开发日记

2023年11月15日 项目启动

客户提出了一个极具挑战性的文件传输系统需求,作为山东的个人开发者,这次接到的项目确实不简单。需求包含20G大文件传输、文件夹结构保持、断点续传、加密传输等多项复杂功能,还要兼容IE8这种"古董"浏览器。今天开始记录开发过程中的关键点和解决方案。

技术选型分析

前端方案

由于需要兼容IE8,我们不得不放弃纯H5方案,转而采用WebUploader作为基础。虽然项目要求原生JS实现,但考虑到开发效率,我们决定以WebUploader为核心进行定制开发。

// 初始化WebUploader实例varuploader=WebUploader.create({auto:false,swf:'Uploader.swf',// Flash文件路径,用于IE兼容server:'/api/upload',pick:'#picker',chunked:true,chunkSize:5*1024*1024,// 分片大小5MBthreads:3,fileNumLimit:1000,fileSizeLimit:20*1024*1024*1024,// 20GBfileSingleSizeLimit:20*1024*1024*1024,duplicate:true,compress:false});

文件夹上传实现

WebUploader本身不支持文件夹上传,我们需要通过递归遍历文件夹结构来实现:

// 文件夹处理函数functionhandleDirectory(files,relativePath=''){for(leti=0;i<files.length;i++){constfile=files[i];if(file.isDirectory){constreader=file.createReader();reader.readEntries(entries=>{handleDirectory(entries,relativePath+file.name+'/');});}else{file.customRelativePath=relativePath;uploader.addFiles(file);}}}// 监听文件夹选择document.getElementById('folderPicker').addEventListener('change',function(e){constentries=e.target.webkitEntries;if(entries&&entries.length>0){handleDirectory(entries);}},false);

2023年11月16日 断点续传实现

断点续传是项目的核心需求之一,需要前后端协同设计。

前端断点续传逻辑

// 文件分片上传前检查是否已上传uploader.on('uploadBeforeSend',function(block,data){data.chunk=block.chunk;data.chunks=block.chunks;data.md5=uploader.md5File(block.file);// 检查分片状态return$.ajax({url:'/api/checkChunk',type:'POST',async:false,data:{md5:data.md5,chunk:data.chunk,chunks:data.chunks,fileName:block.file.name,fileSize:block.file.size,relativePath:block.file.customRelativePath||''},success:function(res){if(res.uploaded){// 该分片已上传,跳过returnfalse;}}});});

后端C#实现 (ASP.NET WebForm)

[WebMethod]publicstaticCheckChunkResultCheckChunk(stringmd5,intchunk,intchunks,stringfileName,longfileSize,stringrelativePath){stringchunkPath=Path.Combine(Server.MapPath("~/App_Data/Upload"),md5);// 检查分片目录是否存在if(!Directory.Exists(chunkPath)){Directory.CreateDirectory(chunkPath);}// 检查分片文件是否存在stringchunkFile=Path.Combine(chunkPath,chunk.ToString());boolexists=File.Exists(chunkFile);returnnewCheckChunkResult{Uploaded=exists,AllUploaded=exists&&Directory.GetFiles(chunkPath).Length==chunks};}

2023年11月17日 加密传输实现

客户要求SM4和AES加密,我们决定在前端实现加密后再传输。

前端加密实现

// 使用CryptoJS实现AES加密functionencryptFileChunk(chunk,key){constwordArray=CryptoJS.lib.WordArray.create(chunk);constencrypted=CryptoJS.AES.encrypt(wordArray,key,{mode:CryptoJS.mode.CFB,padding:CryptoJS.pad.Pkcs7});returnencrypted.toString();}// 文件分片加密处理uploader.on('uploadAccept',function(file,data){if(data.chunk){constkey=sessionStorage.getItem('encryptKey')||'defaultEncryptKey';return{chunkData:encryptFileChunk(data.chunkData,key),isEncrypted:true};}returndata;});

后端解密处理

[WebMethod]publicstaticvoidUploadChunk(stringmd5,intchunk,intchunks,stringfileName,longfileSize,stringrelativePath,stringchunkData,boolisEncrypted){stringchunkPath=Path.Combine(Server.MapPath("~/App_Data/Upload"),md5);stringchunkFile=Path.Combine(chunkPath,chunk.ToString());byte[]data=Convert.FromBase64String(chunkData);if(isEncrypted){// 解密处理data=AESHelper.Decrypt(data,ConfigurationManager.AppSettings["EncryptKey"]);}File.WriteAllBytes(chunkFile,data);// 检查是否所有分片都已上传if(Directory.GetFiles(chunkPath).Length==chunks){MergeFiles(md5,fileName,fileSize,relativePath);}}

2023年11月18日 文件夹结构保持

保持文件夹层级结构是项目的另一个挑战,我们需要在前后端协同处理路径信息。

前端路径处理

// 文件添加到队列时记录相对路径uploader.on('fileQueued',function(file){file.relativePath=file.customRelativePath||'';});

后端C#路径处理

privatestaticvoidMergeFiles(stringmd5,stringfileName,longfileSize,stringrelativePath){stringchunkPath=Path.Combine(Server.MapPath("~/App_Data/Upload"),md5);string[]chunkFiles=Directory.GetFiles(chunkPath).OrderBy(f=>int.Parse(Path.GetFileName(f))).ToArray();// 确保目标目录存在stringdestDirectory=Path.Combine(Server.MapPath("~/Uploads"),Path.GetDirectoryName(relativePath));if(!Directory.Exists(destDirectory)){Directory.CreateDirectory(destDirectory);}stringdestFile=Path.Combine(destDirectory,fileName);using(FileStreamfs=newFileStream(destFile,FileMode.Create,FileAccess.Write)){foreach(stringchunkFileinchunkFiles){byte[]buffer=File.ReadAllBytes(chunkFile);fs.Write(buffer,0,buffer.Length);}}// 上传到阿里云OSSUploadToOSS(destFile,Path.Combine(relativePath,fileName));// 清理临时文件Directory.Delete(chunkPath,true);}

2023年11月19日 IE8兼容方案

为了兼容IE8,我们需要引入Flash后备方案和polyfill。

IE8检测和兼容处理

// 检测IE版本functiongetIEVersion(){varua=window.navigator.userAgent;varmsie=ua.indexOf('MSIE ');if(msie>0){returnparseInt(ua.substring(msie+5,ua.indexOf('.',msie)),10);}returnfalse;}// 根据浏览器选择上传方式if(getIEVersion()&&getIEVersion()<=8){// IE8及以下使用Flash上传uploader.options.server='/api/upload_flash';uploader.options.forceFlash=true;}else{// 现代浏览器使用HTML5上传uploader.options.server='/api/upload_html5';uploader.options.forceFlash=false;}

后端Flash上传处理

[WebMethod]publicstaticvoidUploadFlash(){HttpPostedFilefile=HttpContext.Current.Request.Files[0];stringfileName=HttpContext.Current.Request["name"];stringrelativePath=HttpContext.Current.Request["relativePath"]??"";stringmd5=HttpContext.Current.Request["md5"];// 处理文件保存逻辑stringdestDirectory=Path.Combine(Server.MapPath("~/Uploads"),relativePath);if(!Directory.Exists(destDirectory)){Directory.CreateDirectory(destDirectory);}stringdestFile=Path.Combine(destDirectory,fileName);file.SaveAs(destFile);// 上传到阿里云OSSUploadToOSS(destFile,Path.Combine(relativePath,fileName));}

2023年11月20日 阿里云OSS集成

我们需要将最终文件存储到阿里云OSS,实现加密存储。

C# OSS上传实现

privatestaticvoidUploadToOSS(stringlocalFilePath,stringossPath){stringendpoint=ConfigurationManager.AppSettings["OSSEndpoint"];stringaccessKeyId=ConfigurationManager.AppSettings["OSSAccessKeyId"];stringaccessKeySecret=ConfigurationManager.AppSettings["OSSAccessKeySecret"];stringbucketName=ConfigurationManager.AppSettings["OSSBucketName"];OssClientclient=newOssClient(endpoint,accessKeyId,accessKeySecret);try{// 读取文件内容byte[]fileContent=File.ReadAllBytes(localFilePath);// 加密存储if(ConfigurationManager.AppSettings["EncryptStorage"]=="true"){fileContent=AESHelper.Encrypt(fileContent,ConfigurationManager.AppSettings["StorageEncryptKey"]);}// 上传到OSSMemoryStreamstream=newMemoryStream(fileContent);client.PutObject(bucketName,ossPath,stream);}catch(Exceptionex){// 记录错误日志LogError("OSS上传失败: "+ex.Message);throw;}}

2023年11月21日 文件夹下载功能

客户要求文件夹下载不打包,我们需要实现保持目录结构的下载方式。

前端文件夹下载请求

functiondownloadFolder(folderPath){// 获取文件夹内容列表$.ajax({url:'/api/listFolder',type:'POST',data:{folderPath:folderPath},success:function(files){// 逐个创建下载链接files.forEach(file=>{consta=document.createElement('a');a.href=`/api/download?filePath=${encodeURIComponent(file.path)}`;a.download=file.name;a.style.display='none';document.body.appendChild(a);a.click();document.body.removeChild(a);});}});}

后端文件夹列表和下载

[WebMethod]publicstaticListListFolder(stringfolderPath){stringphysicalPath=Path.Combine(Server.MapPath("~/Uploads"),folderPath);varfiles=newList();if(Directory.Exists(physicalPath)){foreach(stringfileinDirectory.GetFiles(physicalPath,"*",SearchOption.AllDirectories)){files.Add(newFileInfo{path=file.Substring(Server.MapPath("~/Uploads").Length).Replace('\\','/').TrimStart('/'),name=Path.GetFileName(file),size=newFileInfo(file).Length});}}returnfiles;}[WebMethod]publicstaticvoidDownload(stringfilePath){stringphysicalPath=Path.Combine(Server.MapPath("~/Uploads"),filePath);if(File.Exists(physicalPath)){byte[]fileBytes=File.ReadAllBytes(physicalPath);// 解密存储的文件if(ConfigurationManager.AppSettings["EncryptStorage"]=="true"){fileBytes=AESHelper.Decrypt(fileBytes,ConfigurationManager.AppSettings["StorageEncryptKey"]);}HttpContext.Current.Response.ContentType="application/octet-stream";HttpContext.Current.Response.AddHeader("Content-Disposition",$"attachment; filename=\"{Path.GetFileName(filePath)}\"");HttpContext.Current.Response.BinaryWrite(fileBytes);HttpContext.Current.Response.End();}else{HttpContext.Current.Response.StatusCode=404;}}

项目总结

这个项目确实极具挑战性,特别是需要兼容IE8的同时还要实现20G大文件上传和文件夹结构保持。通过本次开发,我总结了以下经验:

  1. 分片上传是大文件传输的关键,需要合理设置分片大小
  2. 断点续传依赖于文件唯一标识(MD5)和分片状态记录
  3. 文件夹结构保持需要在前后端协同处理相对路径
  4. IE8兼容需要使用Flash后备方案
  5. 加密传输应该在前端完成,减少敏感数据暴露

完整的项目代码和文档已经整理完毕,欢迎同行在QQ群(374992201)交流讨论。期待与更多开发者合作,共同承接更多优质项目。

设置框架

安装.NET Framework 4.7.2
https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472
框架选择4.7.2

添加3rd引用

编译项目

NOSQL

NOSQL无需任何配置可直接访问页面进行测试

SQL

使用IIS
大文件上传测试推荐使用IIS以获取更高性能。

使用IIS Express

小文件上传测试可以使用IIS Express

创建数据库

配置数据库连接信息

检查数据库配置

访问页面进行测试


相关参考:
文件保存位置,

效果预览

文件上传

文件刷新续传

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

文件夹上传

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

下载完整示例

下载完整示例

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

开发转行海洋科技:蓝碳经济中的3个入口

——软件测试从业者的专业转型指南引言&#xff1a;蓝碳经济的时代浪潮与测试人的机遇在全球气候治理的紧迫需求下&#xff0c;蓝碳经济正成为海洋生态保护与经济发展的核心引擎。蓝碳&#xff08;Blue Carbon&#xff09;指海洋生态系统通过光合作用固定的碳&#xff0c;主要包…

作者头像 李华
网站建设 2026/4/18 20:17:06

探秘 Fluent 水冷电机磁热仿真:从建模到温度场分析

fluent水冷电机磁热仿真、网格划分、前处理、3d建模、自然冷却温度场 单向耦合 双向耦合 磁热温度场分析在电机领域&#xff0c;随着技术不断精进&#xff0c;水冷电机因其高效散热性能受到广泛关注。Fluent 作为强大的仿真软件&#xff0c;为水冷电机的磁热分析提供了有力工具…

作者头像 李华
网站建设 2026/4/23 12:46:17

基于matlab的带点粒子在混合场运动的仿真模拟

- 标题&#xff1a; 基于matlab的带点粒子在混合场运动的仿真模拟 - 关键词&#xff1a;matlab GUI界面 电子 质子 自定义粒子 运动轨迹 电场 磁场 - 简述&#xff1a;针对不同混合场的情景&#xff0c;该模拟程序能准确画出带电粒子的运动轨迹。 关键词 matlab GUI界面 电子…

作者头像 李华
网站建设 2026/4/22 1:42:01

Ab3d.PowerToys 12.0 Crack

适用于 WPF 和 WinForms 的终极 3D 图形工具包 Ab3d.PowerToys 是一个功能强大的 WPF 和 WinForms 3D 工具包库&#xff0c;可大大简化使用科学、技术、CAD 或其他 3D 图形开发桌面应用程序的过程。 该库已在众多专业应用中得到验证。基于用户的积极反馈&#xff0c;它已发展成…

作者头像 李华
网站建设 2026/4/16 12:20:04

从过程性到面向对象:C++编程思维的飞跃

从过程性到面向对象&#xff1a;C编程思维的飞跃 过程性编程 vs 面向对象编程 在编程世界中&#xff0c;有两种主流的思维方式&#xff1a;过程性编程和面向对象编程。让我们通过一个实际的例子来理解它们的区别。 想象你是垒球队的数据记录员。如果采用过程性编程方法&…

作者头像 李华
网站建设 2026/4/19 19:39:18

与学习相关的技巧(超参数的验证)

超参数的验证 神经网络中&#xff0c;除了权重和偏置等参数&#xff0c;超参数&#xff08;hyper-parameter&#xff09;也经 常出现。这里所说的超参数是指&#xff0c;比如各层的神经元数量、batch 大小、参 数更新时的学习率或权值衰减等。如果这些超参数没有设置合适的值&a…

作者头像 李华