2026/4/17 6:47:45
网站建设
项目流程
公司网站建设及优化计划书,新开传奇网站手游,佛山做网站建设,做司考题的网站大文件上传系统开发日记
2023年11月15日 项目启动
客户提出了一个极具挑战性的文件传输系统需求#xff0c;作为山东的个人开发者#xff0c;这次接到的项目确实不简单。需求包含20G大文件传输、文件夹结构保持、断点续传、加密传输等多项复杂功能#xff0c;还要兼容IE8这…大文件上传系统开发日记2023年11月15日 项目启动客户提出了一个极具挑战性的文件传输系统需求作为山东的个人开发者这次接到的项目确实不简单。需求包含20G大文件传输、文件夹结构保持、断点续传、加密传输等多项复杂功能还要兼容IE8这种古董浏览器。今天开始记录开发过程中的关键点和解决方案。技术选型分析前端方案由于需要兼容IE8我们不得不放弃纯H5方案转而采用WebUploader作为基础。虽然项目要求原生JS实现但考虑到开发效率我们决定以WebUploader为核心进行定制开发。// 初始化WebUploader实例varuploaderWebUploader.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(leti0;ifiles.length;i){constfilefiles[i];if(file.isDirectory){constreaderfile.createReader();reader.readEntries(entries{handleDirectory(entries,relativePathfile.name/);});}else{file.customRelativePathrelativePath;uploader.addFiles(file);}}}// 监听文件夹选择document.getElementById(folderPicker).addEventListener(change,function(e){constentriese.target.webkitEntries;if(entriesentries.length0){handleDirectory(entries);}},false);2023年11月16日 断点续传实现断点续传是项目的核心需求之一需要前后端协同设计。前端断点续传逻辑// 文件分片上传前检查是否已上传uploader.on(uploadBeforeSend,function(block,data){data.chunkblock.chunk;data.chunksblock.chunks;data.md5uploader.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){stringchunkPathPath.Combine(Server.MapPath(~/App_Data/Upload),md5);// 检查分片目录是否存在if(!Directory.Exists(chunkPath)){Directory.CreateDirectory(chunkPath);}// 检查分片文件是否存在stringchunkFilePath.Combine(chunkPath,chunk.ToString());boolexistsFile.Exists(chunkFile);returnnewCheckChunkResult{Uploadedexists,AllUploadedexistsDirectory.GetFiles(chunkPath).Lengthchunks};}2023年11月17日 加密传输实现客户要求SM4和AES加密我们决定在前端实现加密后再传输。前端加密实现// 使用CryptoJS实现AES加密functionencryptFileChunk(chunk,key){constwordArrayCryptoJS.lib.WordArray.create(chunk);constencryptedCryptoJS.AES.encrypt(wordArray,key,{mode:CryptoJS.mode.CFB,padding:CryptoJS.pad.Pkcs7});returnencrypted.toString();}// 文件分片加密处理uploader.on(uploadAccept,function(file,data){if(data.chunk){constkeysessionStorage.getItem(encryptKey)||defaultEncryptKey;return{chunkData:encryptFileChunk(data.chunkData,key),isEncrypted:true};}returndata;});后端解密处理[WebMethod]publicstaticvoidUploadChunk(stringmd5,intchunk,intchunks,stringfileName,longfileSize,stringrelativePath,stringchunkData,boolisEncrypted){stringchunkPathPath.Combine(Server.MapPath(~/App_Data/Upload),md5);stringchunkFilePath.Combine(chunkPath,chunk.ToString());byte[]dataConvert.FromBase64String(chunkData);if(isEncrypted){// 解密处理dataAESHelper.Decrypt(data,ConfigurationManager.AppSettings[EncryptKey]);}File.WriteAllBytes(chunkFile,data);// 检查是否所有分片都已上传if(Directory.GetFiles(chunkPath).Lengthchunks){MergeFiles(md5,fileName,fileSize,relativePath);}}2023年11月18日 文件夹结构保持保持文件夹层级结构是项目的另一个挑战我们需要在前后端协同处理路径信息。前端路径处理// 文件添加到队列时记录相对路径uploader.on(fileQueued,function(file){file.relativePathfile.customRelativePath||;});后端C#路径处理privatestaticvoidMergeFiles(stringmd5,stringfileName,longfileSize,stringrelativePath){stringchunkPathPath.Combine(Server.MapPath(~/App_Data/Upload),md5);string[]chunkFilesDirectory.GetFiles(chunkPath).OrderBy(fint.Parse(Path.GetFileName(f))).ToArray();// 确保目标目录存在stringdestDirectoryPath.Combine(Server.MapPath(~/Uploads),Path.GetDirectoryName(relativePath));if(!Directory.Exists(destDirectory)){Directory.CreateDirectory(destDirectory);}stringdestFilePath.Combine(destDirectory,fileName);using(FileStreamfsnewFileStream(destFile,FileMode.Create,FileAccess.Write)){foreach(stringchunkFileinchunkFiles){byte[]bufferFile.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(){varuawindow.navigator.userAgent;varmsieua.indexOf(MSIE );if(msie0){returnparseInt(ua.substring(msie5,ua.indexOf(.,msie)),10);}returnfalse;}// 根据浏览器选择上传方式if(getIEVersion()getIEVersion()8){// IE8及以下使用Flash上传uploader.options.server/api/upload_flash;uploader.options.forceFlashtrue;}else{// 现代浏览器使用HTML5上传uploader.options.server/api/upload_html5;uploader.options.forceFlashfalse;}后端Flash上传处理[WebMethod]publicstaticvoidUploadFlash(){HttpPostedFilefileHttpContext.Current.Request.Files[0];stringfileNameHttpContext.Current.Request[name];stringrelativePathHttpContext.Current.Request[relativePath]??;stringmd5HttpContext.Current.Request[md5];// 处理文件保存逻辑stringdestDirectoryPath.Combine(Server.MapPath(~/Uploads),relativePath);if(!Directory.Exists(destDirectory)){Directory.CreateDirectory(destDirectory);}stringdestFilePath.Combine(destDirectory,fileName);file.SaveAs(destFile);// 上传到阿里云OSSUploadToOSS(destFile,Path.Combine(relativePath,fileName));}2023年11月20日 阿里云OSS集成我们需要将最终文件存储到阿里云OSS实现加密存储。C# OSS上传实现privatestaticvoidUploadToOSS(stringlocalFilePath,stringossPath){stringendpointConfigurationManager.AppSettings[OSSEndpoint];stringaccessKeyIdConfigurationManager.AppSettings[OSSAccessKeyId];stringaccessKeySecretConfigurationManager.AppSettings[OSSAccessKeySecret];stringbucketNameConfigurationManager.AppSettings[OSSBucketName];OssClientclientnewOssClient(endpoint,accessKeyId,accessKeySecret);try{// 读取文件内容byte[]fileContentFile.ReadAllBytes(localFilePath);// 加密存储if(ConfigurationManager.AppSettings[EncryptStorage]true){fileContentAESHelper.Encrypt(fileContent,ConfigurationManager.AppSettings[StorageEncryptKey]);}// 上传到OSSMemoryStreamstreamnewMemoryStream(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{constadocument.createElement(a);a.href/api/download?filePath${encodeURIComponent(file.path)};a.downloadfile.name;a.style.displaynone;document.body.appendChild(a);a.click();document.body.removeChild(a);});}});}后端文件夹列表和下载[WebMethod]publicstaticListListFolder(stringfolderPath){stringphysicalPathPath.Combine(Server.MapPath(~/Uploads),folderPath);varfilesnewList();if(Directory.Exists(physicalPath)){foreach(stringfileinDirectory.GetFiles(physicalPath,*,SearchOption.AllDirectories)){files.Add(newFileInfo{pathfile.Substring(Server.MapPath(~/Uploads).Length).Replace(\\,/).TrimStart(/),namePath.GetFileName(file),sizenewFileInfo(file).Length});}}returnfiles;}[WebMethod]publicstaticvoidDownload(stringfilePath){stringphysicalPathPath.Combine(Server.MapPath(~/Uploads),filePath);if(File.Exists(physicalPath)){byte[]fileBytesFile.ReadAllBytes(physicalPath);// 解密存储的文件if(ConfigurationManager.AppSettings[EncryptStorage]true){fileBytesAESHelper.Decrypt(fileBytes,ConfigurationManager.AppSettings[StorageEncryptKey]);}HttpContext.Current.Response.ContentTypeapplication/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.StatusCode404;}}项目总结这个项目确实极具挑战性特别是需要兼容IE8的同时还要实现20G大文件上传和文件夹结构保持。通过本次开发我总结了以下经验分片上传是大文件传输的关键需要合理设置分片大小断点续传依赖于文件唯一标识(MD5)和分片状态记录文件夹结构保持需要在前后端协同处理相对路径IE8兼容需要使用Flash后备方案加密传输应该在前端完成减少敏感数据暴露完整的项目代码和文档已经整理完毕欢迎同行在QQ群(374992201)交流讨论。期待与更多开发者合作共同承接更多优质项目。设置框架安装.NET Framework 4.7.2https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472框架选择4.7.2添加3rd引用编译项目NOSQLNOSQL无需任何配置可直接访问页面进行测试SQL使用IIS大文件上传测试推荐使用IIS以获取更高性能。使用IIS Express小文件上传测试可以使用IIS Express创建数据库配置数据库连接信息检查数据库配置访问页面进行测试相关参考文件保存位置效果预览文件上传文件刷新续传支持离线保存文件进度在关闭浏览器刷新浏览器后进行不丢失仍然能够继续上传文件夹上传支持上传文件夹并保留层级结构同样支持进度信息离线保存刷新页面关闭页面重启系统不丢失上传进度。下载完整示例下载完整示例