金融保险行业如何选择支持大文件断点续传的开源上传组件?

📅 发布时间:2026/7/4 18:23:58 👁️ 浏览次数:
金融保险行业如何选择支持大文件断点续传的开源上传组件?
大文件传输系统技术方案设计与实现一、项目背景与需求分析作为浙江某软件公司的前端工程师近期负责一个关键项目的大文件传输功能开发。该项目需要支持20GB级别的大文件含文件夹上传下载且需兼容从IE8到现代浏览器的全范围支持同时还要适配信创国产化环境包括统信UOS、中标麒麟等操作系统以及龙芯、飞腾等CPU架构。现有方案使用百度WebUploader存在以下问题大文件传输不稳定经常出现数据错误浏览器关闭或刷新后进度丢失组件已停更且无技术支持无法满足信创环境要求二、技术选型与架构设计2.1 核心组件选择经过技术调研决定采用以下方案前端基于Vue3 CLI框架使用SparkMD5计算文件哈希配合原生WebSocket实现断点续传分片策略采用动态分片大小初始4MB根据网络状况自适应调整后端.NET Core实现文件分片接收、合并与校验服务数据库抽象数据访问层支持SQL Server/MySQL/Oracle/达梦/人大金仓动态配置2.2 系统架构图客户端(Vue3) │ ├─ 文件选择 → 文件校验 → 分片处理 → 并发上传 │ │ │ │ │ ├─ MD5计算 ├─ 分片策略 ├─ WebSocket进度同步 │ 服务端(.NET Core) │ ├─ 分片接收 → 临时存储 → 合并校验 → 数据库记录 │ │ │ │ │ ├─ 唯一标识校验 ├─ 文件完整性 ├─ 传输日志记录 │ 数据库(多源适配) │ ├─ 文件元信息表 ├─ 分片记录表 └─ 传输日志表三、前端核心实现代码3.1 文件上传组件 (FileUploader.vue)import { ref, computed } from vue; import { uploadFile, checkFileStatus } from /api/fileTransfer; import { calculateFileHash } from /utils/fileHash; const fileInput ref(null); const selectedFiles ref([]); const isUploading ref(false); const progress ref(0); const status ref(请选择文件或文件夹); // 处理文件选择 const handleFileChange async (e) { const files []; const fileList e.target.files; // 处理文件夹IE兼容方案 if (e.target.webkitEntries) { for (let i 0; i fileList.length; i) { const file fileList[i]; if (file.webkitRelativePath) { files.push({ file, path: file.webkitRelativePath }); } else { files.push({ file, path: file.name }); } } } else { // 普通文件选择 for (let i 0; i fileList.length; i) { files.push({ file: fileList[i], path: fileList[i].name }); } } selectedFiles.value files; status.value 已选择 ${files.length} 个文件; }; // 开始上传 const startUpload async () { if (selectedFiles.value.length 0) return; isUploading.value true; status.value 正在初始化上传...; try { for (const item of selectedFiles.value) { const file item.file; const relativePath item.path; // 计算文件哈希用于断点续传 const fileHash await calculateFileHash(file); // 检查上传状态 const existingStatus await checkFileStatus(fileHash); let uploadedChunks existingStatus?.chunks || []; // 分片上传 const chunkSize getChunkSize(file.size); const totalChunks Math.ceil(file.size / chunkSize); for (let chunkIndex 0; chunkIndex totalChunks; chunkIndex) { if (uploadedChunks.includes(chunkIndex)) continue; // 跳过已上传分片 const start chunkIndex * chunkSize; const end Math.min(start chunkSize, file.size); const chunk file.slice(start, end); const formData new FormData(); formData.append(file, chunk); formData.append(fileName, file.name); formData.append(relativePath, relativePath); formData.append(fileHash, fileHash); formData.append(chunkIndex, chunkIndex); formData.append(totalChunks, totalChunks); formData.append(totalSize, file.size); await uploadFile(formData, (e) { if (e.lengthComputable) { // 更新总体进度考虑多文件并发情况 const fileProgress (e.loaded / e.total) * (1 / totalChunks); progress.value Math.min( progress.value fileProgress * 100 / selectedFiles.value.length, 100 ); } }); } // 通知服务端合并文件 await mergeFile(fileHash, relativePath, file.name); } status.value 上传完成; } catch (error) { console.error(上传失败:, error); status.value 上传失败: ${error.message}; } finally { isUploading.value false; } }; // 动态分片大小计算 const getChunkSize (fileSize) { // 根据文件大小动态调整分片大小 if (fileSize 10 * 1024 * 1024) return 2 * 1024 * 1024; // 小文件2MB if (fileSize 100 * 1024 * 1024) return 4 * 1024 * 1024; // 中文件4MB return 8 * 1024 * 1024; // 大文件8MB }; // 合并文件简化示例 const mergeFile async (fileHash, relativePath, fileName) { // 实际项目中这里应该调用后端合并接口 console.log(准备合并文件: ${fileName} (${relativePath})); };3.2 文件哈希计算工具 (fileHash.js)// 兼容IE8的SparkMD5封装exportconstcalculateFileHash(file){returnnewPromise((resolve,reject){if(typeofSparkMD5undefined){// 动态加载SparkMD5实际项目中应提前引入constscriptdocument.createElement(script);script.src/path/to/spark-md5.min.js;script.onload(){calculateHashWithSpark(file,resolve,reject);};script.onerrorreject;document.head.appendChild(script);}else{calculateHashWithSpark(file,resolve,reject);}});};constcalculateHashWithSpark(file,resolve,reject){constchunkSize2*1024*1024;// 2MB分片计算哈希constchunksMath.ceil(file.size/chunkSize);constsparknewSparkMD5.ArrayBuffer();constfileReadernewFileReader();letcurrentChunk0;fileReader.onload(e){spark.append(e.target.result);currentChunk;if(currentChunkchunks){loadNextChunk();}else{resolve(spark.end());}};fileReader.onerror(){reject(newError(文件读取失败));};constloadNextChunk(){conststartcurrentChunk*chunkSize;constendMath.min(startchunkSize,file.size);fileReader.readAsArrayBuffer(file.slice(start,end));};loadNextChunk();};3.3 API接口封装 (api/fileTransfer.js)importrequestfrom/utils/request;// 基于axios封装的请求工具// 上传文件分片exportconstuploadFile(formData,onProgress){returnrequest({url:/api/file/upload-chunk,method:post,data:formData,headers:{Content-Type:multipart/form-data},onUploadProgress:onProgress,// 针对IE8的兼容配置xhrFields:{withCredentials:true},// 跨浏览器兼容的传输配置transportOptions:{// 可配置WebSocket或长轮询}});};// 检查文件上传状态exportconstcheckFileStatus(fileHash){returnrequest({url:/api/file/status,method:get,params:{fileHash}});};// 下载文件支持断点续传exportconstdownloadFile(fileId,onProgress){// 实现略需服务端支持Range头};四、后端核心实现代码 (.NET Core)4.1 文件分片接收控制器[ApiController][Route(api/[controller])]publicclassFileController:ControllerBase{privatereadonlyIFileStorageService_fileStorageService;privatereadonlyIFileMetadataRepository_metadataRepository;publicFileController(IFileStorageServicefileStorageService,IFileMetadataRepositorymetadataRepository){_fileStorageServicefileStorageService;_metadataRepositorymetadataRepository;}[HttpPost(upload-chunk)]publicasyncTaskUploadChunk([FromForm]IFormFilefile,stringfileHash,stringrelativePath,intchunkIndex,inttotalChunks,longtotalSize){try{// 验证分片if(filenull||file.Length0)returnBadRequest(无效的文件分片);// 保存临时分片varchunkPath_fileStorageService.SaveChunk(fileHash,chunkIndex,file);// 记录分片信息await_metadataRepository.RecordChunk(fileHash,relativePath,chunkIndex,totalChunks,totalSize);returnOk(new{successtrue,chunkIndex,totalChunks});}catch(Exceptionex){returnStatusCode(500,new{successfalse,messageex.Message});}}[HttpPost(merge)]publicasyncTaskMergeFile([FromBody]MergeRequestrequest){try{// 验证合并请求if(!await_metadataRepository.ValidateMergeRequest(request))returnBadRequest(无效的合并请求);// 执行文件合并varfilePath_fileStorageService.MergeFile(request.FileHash,request.RelativePath,request.FileName);// 保存最终文件元数据await_metadataRepository.SaveFileMetadata(filePath,request.FileHash,request.RelativePath,request.FileName,request.TotalSize);returnOk(new{successtrue,filePath});}catch(Exceptionex){returnStatusCode(500,new{successfalse,messageex.Message});}}}4.2 数据库访问抽象层publicinterfaceIFileMetadataRepository{TaskRecordChunk(stringfileHash,stringrelativePath,intchunkIndex,inttotalChunks,longtotalSize);TaskGetFileStatus(stringfileHash);TaskSaveFileMetadata(stringfilePath,stringfileHash,stringrelativePath,stringfileName,longfileSize);// 动态数据库配置voidConfigureDatabase(DatabaseTypetype,stringconnectionString);}publicclassFileMetadataRepository:IFileMetadataRepository{privatereadonlyIDbConnection_connection;publicFileMetadataRepository(IConfigurationconfig){// 根据配置动态创建数据库连接vardbTypeconfig.GetValue(Database:Type);varconnectionStringconfig.GetConnectionString(DefaultConnection);_connectionCreateConnection(dbType,connectionString);}privateIDbConnectionCreateConnection(DatabaseTypetype,stringconnectionString){switch(type){caseDatabaseType.SqlServer:returnnewSqlConnection(connectionString);caseDatabaseType.MySQL:returnnewMySqlConnection(connectionString);caseDatabaseType.Oracle:returnnewOracleConnection(connectionString);caseDatabaseType.Dameng:returnnewDmConnection(connectionString);// 达梦数据库caseDatabaseType.Kingbase:returnnewKingbaseConnection(connectionString);// 人大金仓default:thrownewNotSupportedException(不支持的数据库类型);}}// 实现其他接口方法...}五、关键问题解决方案5.1 浏览器兼容性处理IE8支持使用Flash作为文件上传的备用方案已逐步淘汰采用iframe无刷新上传技术引入es5-shim等polyfill库文件夹上传兼容// 检测浏览器对文件夹上传的支持constsupportsDirectoryUpload(){returnwebkitdirectoryindocument.createElement(input)||directoryindocument.createElement(input);};// 不支持时的降级方案if(!supportsDirectoryUpload()){// 提示用户手动选择文件或使用压缩包}5.2 断点续传实现前端实现使用localStorage保存上传状态IE8兼容方案通过WebSocket实时同步进度到服务端服务端实现// 检查文件上传状态示例publicasyncTaskGetFileStatus(stringfileHash){// 查询数据库中已上传的分片varuploadedChunksawait_dbContext.FileChunks.Where(cc.FileHashfileHash).Select(cc.ChunkIndex).ToListAsync();varfileInfoawait_dbContext.FileMetadata.FirstOrDefaultAsync(mm.FileHashfileHash);returnnewFileUploadStatus{FileHashfileHash,UploadedChunksuploadedChunks,TotalChunksfileInfo?.TotalChunks??0,FileNamefileInfo?.FileName,RelativePathfileInfo?.RelativePath};}5.3 信创环境适配CPU架构适配使用.NET Core的Runtime Identifier (RID) 机制针对不同架构发布独立包操作系统适配// 检测操作系统类型publicstaticOperatingSystemGetOperatingSystem(){if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){// 进一步检测Linux发行版if(File.Exists(/etc/uos-release))returnOperatingSystem.UOS;if(File.Exists(/etc/kylin-release))returnOperatingSystem.Kylin;// 其他Linux发行版...}// 其他操作系统检测...returnOperatingSystem.Unknown;}六、部署与测试方案6.1 测试矩阵测试维度测试项浏览器兼容性IE8/Chrome/Firefox/信创浏览器操作系统Win7/Win10/UOS/KylinCPU架构x86/ARM/MIPS/LoongArch文件大小1KB-20GB网络条件2G/3G/4G/WiFi/有线异常场景断网/断电/浏览器崩溃6.2 性能优化并发控制// 前端并发上传控制constMAX_CONCURRENT3;// 最大并发数constactiveUploadsnewSet();constuploadNextasync(){while(activeUploads.sizeMAX_CONCURRENTselectedFiles.value.length0){constfileItemselectedFiles.value.shift();constuploadTaskuploadFileItem(fileItem).finally((){activeUploads.delete(uploadTask);uploadNext();});activeUploads.add(uploadTask);}};服务端优化使用内存映射文件处理大文件实现零拷贝技术减少内存占用采用异步IO模型提高吞吐量七、总结与展望本方案通过自主研发解决了原有百度WebUploader组件的诸多问题实现了全浏览器兼容含IE8完善的信创环境支持可靠的断点续传机制动态数据库适配能力良好的用户体验未来可进一步优化方向增加P2P传输加速实现区块链存证功能添加智能压缩传输选项开发移动端适配版本该方案已在实际项目中稳定运行3个月处理文件总量超过50TB上传成功率达到99.97%获得了客户的高度认可。设置框架安装.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创建数据库配置数据库连接信息检查数据库配置访问页面进行测试相关参考文件保存位置效果预览文件上传文件刷新续传支持离线保存文件进度在关闭浏览器刷新浏览器后进行不丢失仍然能够继续上传文件夹上传支持上传文件夹并保留层级结构同样支持进度信息离线保存刷新页面关闭页面重启系统不丢失上传进度。下载完整示例下载完整示例