news 2026/4/16 12:44:24

银行系统如何选择高效的大文件上传控件?

作者头像

张小明

前端开发工程师

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/16 10:46:43

传统线程 vs 虚拟线程:一场颠覆Java并发编程的性能对决

第一章&#xff1a;Java并发编程的演进与虚拟线程的崛起Java 并发编程自诞生以来经历了多个阶段的演进&#xff0c;从早期的 Thread 与 synchronized 原始模型&#xff0c;到 java.util.concurrent 包的引入&#xff0c;再到 CompletableFuture 和响应式编程的兴起&#xff0c;…

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

如何测试AI生成的代码是否易读?我设计了“可读性评分”

AI生成代码的可读性挑战在软件测试领域&#xff0c;AI生成代码&#xff08;如由GitHub Copilot或ChatGPT生成的代码&#xff09;正迅速普及。然而&#xff0c;这些代码往往缺乏人类工程师的“可读性基因”——变量命名混乱、结构冗长、注释缺失等问题频发。作为测试从业者&…

作者头像 李华
网站建设 2026/4/16 11:05:33

TNF-α/TNFR2信号通路:炎症调控的双重作用与精准研究策略

一、 TNF-α&#xff1a;炎症反应的核心调控因子 肿瘤坏死因子-α是机体固有免疫和适应性免疫应答中的关键枢纽分子&#xff0c;主要由活化的巨噬细胞、T淋巴细胞等免疫细胞产生。作为炎症级联反应的早期启动信号&#xff0c;TNF-α在抵御病原体入侵和组织损伤修复中扮演着不可…

作者头像 李华
网站建设 2026/4/16 0:51:25

好写作AI:论点总被“打脸”?让你的AI伙伴开启“思想实验”模式!

辛辛苦苦想出一个核心论点&#xff0c;却在组会上被导师或同学一句话问倒&#xff0c;瞬间“破防”&#xff1f;这很可能是因为&#xff0c;你的论点只在脑子里跑通了一次“单线程”就匆忙上马了。别慌&#xff0c;现在你可以让你的论文搭档——好写作AI&#xff0c;启动它的“…

作者头像 李华
网站建设 2026/4/16 11:11:37

【波束成形】双功能雷达与通信系统Matlab仿真

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#…

作者头像 李华
网站建设 2026/4/16 11:03:35

【Python高手进阶必备】:深入解析random、secrets、numpy等5大随机数模块

第一章&#xff1a;Python随机数生成概述 Python 提供了强大的内置模块来生成随机数&#xff0c;广泛应用于模拟、游戏开发、密码学和机器学习等领域。其核心工具位于 random 模块中&#xff0c;能够生成伪随机数序列&#xff0c;满足大多数常规需求。 核心模块与功能 random…

作者头像 李华