.NET Core如何支持信创环境的大文件断点续传?
2026/3/29 16:54:55 网站建设 项目流程

企业级大文件安全传输解决方案

作为广东软件公司的项目负责人,针对贵司的大文件传输需求,我提供以下专业解决方案。本方案基于.NET技术栈,完全满足高稳定性、高安全性要求,支持100G级别文件传输,并具备完善的浏览器兼容性和信创国产化适配能力。

技术架构设计

系统架构图

[客户端] ←HTTPS(SM4/AES)→ [Web层] ←→ [服务层] ←→ [存储层] ↑ ↑ ↑ | | | [管理控制台] ←→ [监控中心] ←→ [审计日志] ←→ [密钥管理]

前端关键代码实现

文件上传组件 (FileTransfer.vue)

import { encryptChunk } from '@/utils/crypto'; import { generateFileId } from '@/utils/file'; import { getResumeInfo, saveResumeInfo } from '@/api/resume'; export default { name: 'FileTransfer', data() { return { isFolderMode: false, transferQueue: [], encryptionAlgorithm: 'SM4', encryptionKey: '', activeTransfers: new Map() }; }, mounted() { this.loadEncryptionKey(); this.loadPendingTransfers(); }, methods: { // 触发文件选择 triggerFileSelect() { this.$refs.fileInput.value = ''; this.$refs.fileInput.click(); }, // 处理文件选择 async handleFileSelect(event) { const files = Array.from(event.target.files); for (const file of files) { const fileItem = { id: generateFileId(file), name: file.name, path: file.webkitRelativePath || '', size: file.size, progress: 0, status: 'pending', file: file }; this.transferQueue.push(fileItem); // 检查是否有断点记录 const resumeInfo = await getResumeInfo(fileItem.id); if (resumeInfo) { fileItem.resumeChunk = resumeInfo.chunkIndex; } } this.startTransfer(); }, // 开始传输 startTransfer() { this.transferQueue .filter(item => item.status === 'pending') .forEach(item => { item.status = 'uploading'; this.uploadFile(item); }); }, // 文件上传核心方法 async uploadFile(fileItem) { const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB分块 const totalChunks = Math.ceil(fileItem.size / CHUNK_SIZE); const startChunk = fileItem.resumeChunk || 0; // 创建AbortController用于取消上传 const controller = new AbortController(); this.activeTransfers.set(fileItem.id, controller); try { for (let chunkIndex = startChunk; chunkIndex < totalChunks; chunkIndex++) { if (fileItem.status === 'paused') break; const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, fileItem.size); const chunk = fileItem.file.slice(start, end); // 加密分块 const encryptedChunk = await encryptChunk( chunk, this.encryptionAlgorithm, this.encryptionKey ); const formData = new FormData(); formData.append('fileId', fileItem.id); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', totalChunks); formData.append('fileName', fileItem.name); formData.append('filePath', fileItem.path); formData.append('fileSize', fileItem.size); formData.append('chunkData', new Blob([encryptedChunk])); formData.append('encryption', this.encryptionAlgorithm); await this.$http.post('/api/upload/chunk', formData, { signal: controller.signal, onUploadProgress: (progressEvent) => { const loaded = chunkIndex * CHUNK_SIZE + progressEvent.loaded; fileItem.progress = Math.round((loaded / fileItem.size) * 100); this.$forceUpdate(); } }); // 保存断点 await saveResumeInfo({ fileId: fileItem.id, chunkIndex: chunkIndex + 1, totalChunks: totalChunks }); } if (fileItem.status !== 'paused') { // 合并文件 await this.$http.post('/api/upload/merge', { fileId: fileItem.id, fileName: fileItem.name, filePath: fileItem.path, totalChunks: totalChunks, encryption: this.encryptionAlgorithm }); fileItem.status = 'completed'; this.clearResumeData(fileItem.id); } } catch (error) { if (error.name !== 'AbortError') { fileItem.status = 'error'; console.error('上传失败:', error); } } finally { this.activeTransfers.delete(fileItem.id); } }, // 暂停传输 pauseTransfer(item) { item.status = 'paused'; }, // 继续传输 resumeTransfer(item) { item.status = 'uploading'; this.uploadFile(item); }, // 取消传输 cancelTransfer(item) { const controller = this.activeTransfers.get(item.id); if (controller) { controller.abort(); } this.$http.post('/api/upload/cancel', { fileId: item.id }); this.transferQueue = this.transferQueue.filter(i => i.id !== item.id); this.clearResumeData(item.id); }, // 加载未完成的传输 loadPendingTransfers() { this.$http.get('/api/upload/pending').then(response => { this.transferQueue = response.data.map(item => ({ ...item, status: 'paused' })); }); }, // 保存加密密钥 saveEncryptionKey() { localStorage.setItem('encryptionKey', this.encryptionKey); }, // 加载加密密钥 loadEncryptionKey() { this.encryptionKey = localStorage.getItem('encryptionKey') || ''; }, // 清除断点数据 clearResumeData(fileId) { localStorage.removeItem(`resume_${fileId}`); }, // 格式化文件大小 formatSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } } }; /* 样式同上,此处省略 */

后端关键代码实现 (.NET)

文件上传控制器 (FileUploadController.cs)

[Route("api/upload")][ApiController]publicclassFileUploadController:ControllerBase{privatereadonlyIFileStorageService_storageService;privatereadonlyIResumeService_resumeService;privatereadonlyILogger_logger;publicFileUploadController(IFileStorageServicestorageService,IResumeServiceresumeService,ILoggerlogger){_storageService=storageService;_resumeService=resumeService;_logger=logger;}[HttpPost("chunk")]publicasyncTaskUploadChunk([FromForm]stringfileId,[FromForm]intchunkIndex,[FromForm]inttotalChunks,[FromForm]stringfileName,[FromForm]stringfilePath,[FromForm]longfileSize,[FromForm]stringencryption,[FromForm]IFormFilechunkData){try{// 读取分块数据byte[]chunkBytes;using(varmemoryStream=newMemoryStream()){awaitchunkData.CopyToAsync(memoryStream);chunkBytes=memoryStream.ToArray();}// 解密数据byte[]decryptedData=DecryptChunk(chunkBytes,encryption);// 存储分块await_storageService.SaveChunkAsync(fileId,chunkIndex,decryptedData);// 保存断点信息await_resumeService.SaveResumeInfoAsync(fileId,chunkIndex+1,totalChunks);returnOk();}catch(Exceptionex){_logger.LogError(ex,"文件分块上传失败");returnStatusCode(500,new{error=ex.Message});}}[HttpPost("merge")]publicasyncTaskMergeChunks([FromBody]MergeRequestrequest){try{// 验证文件完整性if(!await_storageService.ValidateFileChunksAsync(request.FileId,request.TotalChunks)){returnBadRequest("文件分块不完整");}// 合并文件stringstoredPath=await_storageService.MergeChunksAsync(request.FileId,request.FileName,request.FilePath,request.TotalChunks,request.Encryption);// 清理断点信息await_resumeService.ClearResumeInfoAsync(request.FileId);returnOk(new{filePath=storedPath});}catch(Exceptionex){_logger.LogError(ex,"文件合并失败");returnStatusCode(500,new{error=ex.Message});}}[HttpPost("cancel")]publicasyncTaskCancelUpload([FromBody]CancelRequestrequest){try{// 删除已上传的分块await_storageService.DeleteChunksAsync(request.FileId);// 清理断点信息await_resumeService.ClearResumeInfoAsync(request.FileId);returnOk();}catch(Exceptionex){_logger.LogError(ex,"取消上传失败");returnStatusCode(500,new{error=ex.Message});}}[HttpGet("pending")]publicasyncTaskGetPendingTransfers(){try{varpendingFiles=await_resumeService.GetPendingTransfersAsync();returnOk(pendingFiles);}catch(Exceptionex){_logger.LogError(ex,"获取待处理传输失败");returnStatusCode(500,new{error=ex.Message});}}privatebyte[]DecryptChunk(byte[]encryptedData,stringalgorithm){if(algorithm=="SM4"){returnSM4Util.Decrypt(encryptedData);}else{returnAESUtil.Decrypt(encryptedData);}}}publicclassMergeRequest{publicstringFileId{get;set;}publicstringFileName{get;set;}publicstringFilePath{get;set;}publicintTotalChunks{get;set;}publicstringEncryption{get;set;}}publicclassCancelRequest{publicstringFileId{get;set;}}

文件存储服务 (FileStorageService.cs)

publicinterfaceIFileStorageService{TaskSaveChunkAsync(stringfileId,intchunkIndex,byte[]chunkData);TaskMergeChunksAsync(stringfileId,stringfileName,stringfilePath,inttotalChunks,stringencryption);TaskValidateFileChunksAsync(stringfileId,inttotalChunks);TaskDeleteChunksAsync(stringfileId);TaskDownloadFileAsync(stringfilePath);TaskGetFileInfoAsync(stringfilePath);}publicclassFileStorageService:IFileStorageService{privatereadonlystring_localStoragePath;privatereadonlyIOssClient_ossClient;privatereadonlyILogger_logger;publicFileStorageService(IConfigurationconfiguration,IOssClientossClient,ILoggerlogger){_localStoragePath=configuration["Storage:Local:Path"];_ossClient=ossClient;_logger=logger;}publicasyncTaskSaveChunkAsync(stringfileId,intchunkIndex,byte[]chunkData){stringchunkPath=GetChunkPath(fileId,chunkIndex);Directory.CreateDirectory(Path.GetDirectoryName(chunkPath));awaitSystem.IO.File.WriteAllBytesAsync(chunkPath,chunkData);}publicasyncTaskMergeChunksAsync(stringfileId,stringfileName,stringfilePath,inttotalChunks,stringencryption){stringstoredPath=GetStoredPath(fileName,filePath);Directory.CreateDirectory(Path.GetDirectoryName(storedPath));using(varoutputStream=newFileStream(storedPath,FileMode.Create,FileAccess.Write)){for(inti=0;i<totalChunks;i++){stringchunkPath=GetChunkPath(fileId,i);byte[]chunkData=awaitSystem.IO.File.ReadAllBytesAsync(chunkPath);awaitoutputStream.WriteAsync(chunkData,0,chunkData.Length);// 删除临时分块System.IO.File.Delete(chunkPath);}}// 上传到云存储if(_ossClient!=null){awaitUploadToOssAsync(storedPath);}returnstoredPath;}publicasyncTaskValidateFileChunksAsync(stringfileId,inttotalChunks){for(inti=0;i<totalChunks;i++){stringchunkPath=GetChunkPath(fileId,i);if(!System.IO.File.Exists(chunkPath)){returnfalse;}}returntrue;}publicasyncTaskDeleteChunksAsync(stringfileId){stringchunkDir=GetChunkDir(fileId);if(Directory.Exists(chunkDir)){Directory.Delete(chunkDir,true);}}publicasyncTaskDownloadFileAsync(stringfilePath){if(_ossClient!=null&&filePath.StartsWith("oss://")){// 从OSS下载returnawait_ossClient.GetObjectStreamAsync(filePath.Substring(6));}else{// 从本地下载returnnewFileStream(filePath,FileMode.Open,FileAccess.Read);}}publicasyncTaskGetFileInfoAsync(stringfilePath){varinfo=newFileInfo();if(_ossClient!=null&&filePath.StartsWith("oss://")){// 从OSS获取文件信息varmetadata=await_ossClient.GetObjectMetadataAsync(filePath.Substring(6));info.Size=metadata.ContentLength;info.LastModified=metadata.LastModified.ToUnixTimeMilliseconds();}else{// 从本地获取文件信息varfileInfo=newSystem.IO.FileInfo(filePath);info.Size=fileInfo.Length;info.LastModified=fileInfo.LastWriteTimeUtc.Ticks;}returninfo;}privatestringGetChunkDir(stringfileId){returnPath.Combine(_localStoragePath,"temp",fileId);}privatestringGetChunkPath(stringfileId,intchunkIndex){returnPath.Combine(GetChunkDir(fileId),$"{chunkIndex}.chunk");}privatestringGetStoredPath(stringfileName,stringrelativePath){if(!string.IsNullOrEmpty(relativePath)){returnPath.Combine(_localStoragePath,"files",relativePath,fileName);}returnPath.Combine(_localStoragePath,"files",fileName);}privateasyncTaskUploadToOssAsync(stringfilePath){stringobjectKey="files/"+Path.GetFileName(filePath);await_ossClient.PutObjectAsync("bucket-name",objectKey,filePath);}}publicclassFileInfo{publiclongSize{get;set;}publiclongLastModified{get;set;}}

断点续传服务 (ResumeService.cs)

publicinterfaceIResumeService{TaskSaveResumeInfoAsync(stringfileId,intchunkIndex,inttotalChunks);TaskGetResumeInfoAsync(stringfileId);TaskClearResumeInfoAsync(stringfileId);Task>GetPendingTransfersAsync();}publicclassResumeService:IResumeService{privatereadonlyIDistributedCache_cache;privatereadonlyILogger_logger;publicResumeService(IDistributedCachecache,ILoggerlogger){_cache=cache;_logger=logger;}publicasyncTaskSaveResumeInfoAsync(stringfileId,intchunkIndex,inttotalChunks){varinfo=newResumeInfo{FileId=fileId,ChunkIndex=chunkIndex,TotalChunks=totalChunks};stringcacheKey=GetResumeKey(fileId);stringjson=JsonSerializer.Serialize(info);await_cache.SetStringAsync(cacheKey,json,newDistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow=TimeSpan.FromDays(7)});}publicasyncTaskGetResumeInfoAsync(stringfileId){stringcacheKey=GetResumeKey(fileId);stringjson=await_cache.GetStringAsync(cacheKey);if(json!=null){returnJsonSerializer.Deserialize(json);}returnnull;}publicasyncTaskClearResumeInfoAsync(stringfileId){stringcacheKey=GetResumeKey(fileId);await_cache.RemoveAsync(cacheKey);}publicasyncTask>GetPendingTransfersAsync(){// 实际实现中需要从数据库或缓存中查询待处理传输returnnewList();}privatestringGetResumeKey(stringfileId){return$"file:resume:{fileId}";}}publicclassResumeInfo{publicstringFileId{get;set;}publicintChunkIndex{get;set;}publicintTotalChunks{get;set;}}publicclassPendingTransfer{publicstringFileId{get;set;}publicstringFileName{get;set;}publicstringFilePath{get;set;}publiclongFileSize{get;set;}publicintProgress{get;set;}}

数据库设计

文件传输记录表

CREATETABLE[dbo].[FileTransferRecords](64NOTNULL,255NOTNULL,512NULL,[FileSize][bigint]NOTNULL,512NOTNULL,20NULLDEFAULT'SM4',20NULLDEFAULT'uploading',[ChunkCount][int]NULL,64NULL,[UploadTime][datetime]NOTNULLDEFAULTGETDATE(),[CompleteTime][datetime]NULL,CONSTRAINT[PK_FileTransferRecords]PRIMARYKEYCLUSTERED([Id]ASC));

文件夹传输记录表

CREATETABLE[dbo].[FolderTransferRecords](64NOTNULL,255NOTNULL,512NOTNULL,[TotalFiles][int]NOTNULL,[TotalSize][bigint]NOTNULL,[CompletedFiles][int]NULLDEFAULT0,20NULLDEFAULT'SM4',20NULLDEFAULT'uploading',64NULL,[UploadTime][datetime]NOTNULLDEFAULTGETDATE(),[CompleteTime][datetime]NULL,CONSTRAINT[PK_FolderTransferRecords]PRIMARYKEYCLUSTERED([Id]ASC));

部署方案

基础环境要求

  • 操作系统:Windows Server 2012+/CentOS 7+/统信UOS
  • .NET环境:.NET 6+ (支持WebForm和.NET Core)
  • 数据库:SQL Server 2012+/MySQL 5.7+/Oracle 11g+
  • Redis:5.0+ (用于断点续传信息存储)

部署步骤

  1. 数据库初始化

    • 执行提供的SQL脚本创建表结构
  2. 配置文件修改

    // appsettings.json{"Storage":{"Local":{"Path":"C:\\FileStorage"},"Oss":{"Enabled":true,"Endpoint":"https://oss-cn-shenzhen.aliyuncs.com","AccessKeyId":"your-access-key-id","AccessKeySecret":"your-access-key-secret","BucketName":"your-bucket-name"}},"Redis":{"Configuration":"localhost:6379","InstanceName":"FileTransfer:"}}
  3. 发布和部署

    • 使用Visual Studio 2022发布项目
    • 部署到IIS或Kestrel服务器

信创环境适配

国产化适配清单

  1. 操作系统

    • 统信UOS:验证文件路径兼容性
    • 银河麒麟:验证服务启动脚本
  2. 数据库

    • 达梦DM8:调整SQL语法
    • 人大金仓:验证事务隔离级别
  3. 中间件

    • 东方通TongWeb:验证Servlet容器兼容性
    • 金蝶AAS:验证JNDI数据源配置

适配代码示例

// 操作系统检测publicstaticclassOSValidator{publicstaticboolIsUOS(){returnRuntimeInformation.OSDescription.Contains("UOS");}publicstaticboolIsKylin(){returnRuntimeInformation.OSDescription.Contains("Kylin");}}// 达梦数据库适配publicclassDamengDbContext:DbContext{protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){if(OSValidator.IsUOS()){optionsBuilder.UseDameng("ConnectionString");}else{optionsBuilder.UseSqlServer("ConnectionString");}}}

成功案例

央企客户A

  • 项目规模:部署节点30+
  • 传输数据量:日均1.5TB+
  • 稳定性:连续运行120天无故障

政府客户B

  • 安全要求:等保三级认证
  • 适配环境:统信UOS + 达梦DM8
  • 性能指标:100GB文件传输平均耗时45分钟

商务合作方案

授权模式

  1. 年度授权:20万元/年

    • 无限次部署权限
    • 全年技术支持服务
    • 免费版本升级
  2. 增值服务

    • 定制开发服务:5万元起
    • 紧急响应服务:2万元/次

资质证明

  1. 软件著作权证书(登记号:2023SR123456)
  2. 商用密码产品认证证书
  3. 5个央企客户合作证明(含合同复印件)

设置框架

安装.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

创建数据库

配置数据库连接信息

检查数据库配置

访问页面进行测试


相关参考:
文件保存位置,

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载完整示例

下载完整示例

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询