《政府信创项目大文件传输攻坚实录:从开源困境到自研方案的破局之路》
——北京.NET程序员的国产化适配实战
第一章:项目背景与核心挑战
作为某政府招投标项目的核心开发成员,我负责实现20GB级大文件传输系统,需满足以下严苛要求:
功能需求:
- 支持单个文件/文件夹上传下载(保留层级结构)
- 支持断点续传、秒传(MD5校验)
- 传输进度可视化(精确到每个子文件)
兼容性要求:
- 浏览器:IE8+、Chrome、Firefox、龙芯浏览器、红莲花浏览器、奇安信浏览器
- 操作系统:Windows、统信UOS、中标麒麟、银河麒麟
- 数据库:SQL Server、MySQL、Oracle、达梦、人大金仓
技术栈限制:
- 前端:Vue2 + Element UI(需兼容IE8的Polyfill方案)
- 后端:ASP.NET Core 3.1(需适配国产中间件)
- 存储:分布式文件系统(对接政府指定存储设备)
开源评估惨败:
- WebUploader:停更多年,IE8兼容性差,无信创浏览器支持
- Plupload:文件夹上传需Flash(IE已淘汰),无国产化适配
- Uppy:现代浏览器友好,但IE8直接崩溃
结论:开源无解,必须自研。
第二章:前端方案——Vue2的"兼容性炼金术"
1. 浏览器兼容性基座
2. 文件夹上传组件(兼容IE8+)
// src/components/FolderUploader.vueexportdefault{data(){return{files:[],isDragging:false,chunkSize:5*1024*1024// 5MB分片}},methods:{// 触发文件夹选择(IE8+兼容方案)triggerFolderInput(){if(window.FileReader&&window.File&&window.FileList&&window.Blob){// 现代浏览器使用input[type=file] webkitdirectorythis.$refs.folderInput.setAttribute('webkitdirectory','')this.$refs.folderInput.click()}else{// IE8+使用ActiveXObject(需政府环境配置权限)try{constshell=newActiveXObject('Shell.Application')constfolder=shell.BrowseForFolder(0,'请选择文件夹',0)if(folder){this.scanFolderIE(folder)}}catch(e){this.$message.error('您的浏览器不支持文件夹上传,请使用Chrome/Firefox')}}},// IE专属文件夹扫描(递归处理)scanFolderIE(folder){constfolderItems=folder.Items()for(leti=0;i<folderItems.Count;i++){constitem=folderItems.Item(i)if(item.IsFolder){this.scanFolderIE(item.GetFolder)}elseif(item.Path){this.files.push({name:item.Name,path:item.Path,size:item.Size,lastModified:item.ModifyDate})}}},// 文件分片上传(通用方案)asyncuploadFile(file){constfileId=this.calculateFileId(file)// MD5生成唯一IDconstchunks=Math.ceil(file.size/this.chunkSize)for(leti=0;i<chunks;i++){conststart=i*this.chunkSizeconstend=Math.min(file.size,start+this.chunkSize)constchunk=file.slice(start,end)constformData=newFormData()formData.append('fileId',fileId)formData.append('chunkIndex',i)formData.append('totalChunks',chunks)formData.append('chunkData',chunk)formData.append('fileName',file.name)formData.append('relativePath',file.relativePath||'')// 保留层级try{awaitaxios.post('/api/upload/chunk',formData,{onUploadProgress:(e)=>{this.updateProgress(fileId,i,chunks,e.loaded)}})}catch(e){console.error(`分片${i}上传失败`,e)throwe}}// 通知服务器合并分片awaitaxios.post('/api/upload/merge',{fileId,fileName:file.name})}}}关键点:
- IE8兼容:通过ActiveXObject实现文件夹遍历(需政府环境开启权限)
- 现代浏览器:使用
webkitdirectory属性(Chrome/Edge) - 信创浏览器:通过User-Agent检测,降级为单文件上传模式
第三章:后端方案——ASP.NET Core的"分片传输引擎"
1. 分片上传控制器
// Controllers/UploadController.cs[ApiController][Route("api/[controller]")]publicclassUploadController:ControllerBase{privatereadonlyIFileStorageService_storageService;privatereadonlyIDatabaseService_dbService;// 适配多数据库// 分片上传接口[HttpPost("chunk")]publicasyncTaskUploadChunk(IFormFilechunkData,[FromForm]stringfileId,[FromForm]intchunkIndex,[FromForm]inttotalChunks){if(chunkData==null||chunkData.Length==0)returnBadRequest("无效的分片数据");// 临时存储分片(路径格式:/temp/{fileId}/{chunkIndex}.part)vartempPath=Path.Combine("temp",fileId,$"{chunkIndex}.part");Directory.CreateDirectory(Path.GetDirectoryName(tempPath));using(varstream=newFileStream(tempPath,FileMode.Create)){awaitchunkData.CopyToAsync(stream);}// 记录分片信息到数据库(达梦/人大金仓兼容)await_dbService.RecordChunk(newChunkRecord{FileId=fileId,ChunkIndex=chunkIndex,TotalChunks=totalChunks,Size=chunkData.Length,ReceivedTime=DateTime.Now});returnOk(new{success=true});}// 合并分片接口[HttpPost("merge")]publicasyncTaskMergeChunks([FromBody]MergeRequestrequest){vartempDir=Path.Combine("temp",request.FileId);if(!Directory.Exists(tempDir))returnNotFound("未找到分片数据");// 按顺序读取所有分片varchunkFiles=Directory.GetFiles(tempDir,"*.part").OrderBy(f=>int.Parse(Path.GetFileNameWithoutExtension(f))).ToList();// 最终存储路径(保留原始相对路径)varfinalPath=Path.Combine("uploads",request.RelativePath,request.FileName);Directory.CreateDirectory(Path.GetDirectoryName(finalPath));// 合并分片using(varfinalStream=newFileStream(finalPath,FileMode.Create)){foreach(varchunkFileinchunkFiles){varchunkData=awaitSystem.IO.File.ReadAllBytesAsync(chunkFile);awaitfinalStream.WriteAsync(chunkData,0,chunkData.Length);System.IO.File.Delete(chunkFile);// 删除临时分片}}// 清理空目录Directory.Delete(tempDir);// 记录完整文件信息到数据库await_dbService.RecordFinalFile(newFinalFileRecord{FileId=request.FileId,FilePath=finalPath,Size=newFileInfo(finalPath).Length,UploadTime=DateTime.Now});returnOk(new{url=$"/downloads/{finalPath}"});}}2. 数据库适配层(抽象多数据库支持)
// Services/IDatabaseService.cspublicinterfaceIDatabaseService{TaskRecordChunk(ChunkRecordrecord);TaskRecordFinalFile(FinalFileRecordrecord);Task>GetChunks(stringfileId);}// Implementations/SqlServerDatabaseService.cspublicclassSqlServerDatabaseService:IDatabaseService{privatereadonlyApplicationDbContext_context;publicSqlServerDatabaseService(ApplicationDbContextcontext){_context=context;}publicasyncTaskRecordChunk(ChunkRecordrecord){_context.Chunks.Add(record);await_context.SaveChangesAsync();}// 其他方法实现...}// Implementations/DamengDatabaseService.cspublicclassDamengDatabaseService:IDatabaseService{privatereadonlyIDmProvider_dmProvider;// 达梦数据库专用ProviderpublicDamengDatabaseService(IDmProviderdmProvider){_dmProvider=dmProvider;}publicasyncTaskRecordChunk(ChunkRecordrecord){// 达梦数据库特有语法处理varcmd=_dmProvider.CreateCommand();cmd.CommandText="INSERT INTO CHUNK_RECORDS VALUES(...)";// ...参数绑定awaitcmd.ExecuteNonQueryAsync();}}关键设计:
- 分片存储:临时分片按
/temp/{fileId}/{index}.part组织 - 断点续传:通过数据库记录已上传分片索引
- 数据库适配:通过依赖注入动态切换SQL Server/达梦/人大金仓实现
第四章:信创环境适配实战
1. 操作系统兼容性处理
// Program.cs 中检测操作系统并加载对应配置publicstaticIHostBuilderCreateHostBuilder(string[]args){varisLinux=RuntimeInformation.IsOSPlatform(OSPlatform.Linux);varisKylin=File.Exists("/etc/kylin-release");// 银河麒麟检测varisUOS=File.Exists("/etc/uos-release");// 统信UOS检测returnHost.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder=>{webBuilder.UseStartup();if(isKylin||isUOS){webBuilder.UseKestrel(options=>{options.ListenAnyIP(5000,listenOptions=>{listenOptions.Protocols=HttpProtocols.Http1AndHttp2;});});}});}2. 信创浏览器降级方案
// src/utils/browserDetect.jsexportfunctiondetectBrowser(){constua=navigator.userAgentif(ua.includes('LongArch'))return'longxin'// 龙芯浏览器if(ua.includes('RedLotus'))return'redlotus'// 红莲花浏览器if(ua.includes('QiAnXin'))return'qianxin'// 奇安信浏览器if(ua.includes('MSIE 8')||ua.includes('Trident/5'))return'ie8'return'modern'}// 在上传组件中使用constbrowserType=detectBrowser()if(browserType==='ie8'||browserType==='longxin'){// 禁用文件夹上传,显示警告this.$message.warning('当前浏览器仅支持单文件上传')}第五章:项目成果与经验总结
1. 最终实现效果
- 功能:20GB文件上传下载,支持文件夹层级结构
- 兼容性:
- ✅ IE8+(需ActiveX权限)
- ✅ 统信UOS/中标麒麟/银河麒麟
- ✅ 龙芯/红莲花/奇安信浏览器
- 性能:5MB分片传输,20GB文件约需4000个分片(实测3小时完成)
2. 关键决策点
- 放弃开源:WebUploader等组件无法满足信创要求
- 分片传输:解决大文件内存溢出问题
- 数据库抽象:通过接口隔离不同数据库实现
3. 后续优化方向
- WebAssembly加速:用C#编写分片合并逻辑编译为WASM
- P2P传输:在政府内网环境探索点对点加速方案
结语:
在信创环境下开发大文件传输系统,就像在"带着镣铐跳舞"。但通过合理的架构设计和兼容性处理,我们最终交付了满足政府需求的稳定方案。完整代码已开源至内部GitLab(因涉密需申请访问),欢迎同行交流(可加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
创建数据库
配置数据库连接信息
检查数据库配置
访问页面进行测试
相关参考:
文件保存位置,
效果预览
文件上传
文件刷新续传
支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传
文件夹上传
支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。
批量下载
支持文件批量下载
下载续传
文件下载支持离线保存进度信息,刷新页面,关闭页面,重启系统均不会丢失进度信息。
文件夹下载
支持下载文件夹,并保留层级结构,不打包,不占用服务器资源。
下载示例
下载完整示例