news 2026/3/3 20:12:35

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

作者头像

张小明

前端开发工程师

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/3/3 15:46:53

信创OA系统如何解决wangEditor粘贴Word艺术字格式丢失?

【开发手记&#xff1a;Word图片一键转存功能“闯关记”】 日期&#xff1a;2023年X月X日 星期X 天气&#xff1a;代码如山&#xff0c;我自岿然啃之 作为一名上海软件工程专业的大三学生&#xff0c;最近在给自己的CMS新闻管理系统“动手术”——目标是让后台编辑器&#xf…

作者头像 李华
网站建设 2026/3/3 10:22:08

政务CMS如何用wangEditor实现PDF表单数据到网页的映射?

【集团级富文本编辑器国产化集成项目纪实——从需求拆解到全栈信创落地】 2023年X月X日 周X 上海浦东新区 一、项目背景与核心需求 作为某集团技术负责人&#xff0c;近期承接政府数字化项目时&#xff0c;客户提出关键需求&#xff1a; 功能需求&#xff1a; Word粘贴/导入&a…

作者头像 李华
网站建设 2026/3/4 0:42:15

【开题答辩全过程】以 邯郸市流浪猫狗救助领养系统为例,包含答辩的问题和答案

个人简介 一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等 开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。 感谢大家…

作者头像 李华
网站建设 2026/3/3 13:35:57

空调集中管理控制系统解决方案:全域管控,节能调度智慧运维

空调集中管理控制系统&#xff0c;是针对多台、多区域空调(中央空调、多联机、分体机等)&#xff0c;通过物联网/网络集中平台&#xff0c;实现统一监控、智能控制、节能调度、远程运维的整体解决方案&#xff0c;替代人工逐台操作&#xff0c;把分散空调变成可管、可控、可节能…

作者头像 李华
网站建设 2026/3/3 12:08:24

2026年软件测试新范式:驯化AI为何比优化算法更重要?

在2026年的软件测试领域&#xff0c;一个关键命题正在重塑行业认知&#xff1a;‌“会驯化AI”比“优化算法”更重要‌。这一转变并非否定算法优化的价值&#xff0c;而是揭示了AI技术从实验室走向工程实践的深层逻辑——当测试对象从静态代码转向动态智能体时&#xff0c;‌驾…

作者头像 李华