☰
ASP.NET大文件上传与文件夹递归传输解决方案
2026/10/31 5:03:52 网站建设 项目流程

1. 项目背景与需求分析

在国企信息化建设项目中,经常需要处理大体积文件(如工程图纸、审计报告、视频资料等)的上传需求。传统ASP.NET文件上传方案存在三个核心痛点:

  • 无法直接选择整个文件夹结构上传
  • 大文件传输稳定性差
  • 缺乏企业级的安全管控

某央企档案管理系统升级项目中,要求实现:

  1. 网页端可递归选择文件夹及其子目录
  2. 支持单文件超过2GB的稳定传输
  3. 传输过程需符合等保2.0要求

2. 技术方案设计

2.1 整体架构设计

采用分层处理方案:

[前端] ├─ 文件夹选择器(基于ActiveX/WebView2) ├─ 分片上传控件 └─ 断点续传管理器 [服务端] ├─ 接收中间件(Kestrel定制) ├─ 流式处理管道 └─ 审计日志模块

2.2 关键技术选型

前端方案对比
方案兼容性文件夹选择最大文件支持
HTML5 File API现代浏览器×4GB
WebView2Win10+√无限制
ActiveXIE Only√无限制

最终采用WebView2+HTML5混合方案,平衡兼容性与功能需求。

服务端配置关键参数
// Program.cs builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxRequestBodySize = 2147483648; // 2GB }); services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = long.MaxValue; });

3. 核心实现步骤

3.1 文件夹选择功能实现

WebView2集成方案
// 初始化WebView2 async function initFilePicker() { const filePicker = await window.chrome.webview.showOpenFilePicker({ allowMultiple: true, types: [{ description: '所有文件', accept: {'*/*': ['.*']} }], excludeAcceptAllOption: false }); // 递归处理目录结构 const traverseDirectory = async (dirHandle) => { const entries = []; for await (const entry of dirHandle.values()) { if (entry.kind === 'file') { entries.push({ name: entry.name, handle: entry }); } else if (entry.kind === 'directory') { entries.push({ name: entry.name, children: await traverseDirectory(entry) }); } } return entries; }; return await traverseDirectory(filePicker[0]); }
兼容性降级方案
// 针对IE的ActiveX方案 [ComVisible(true)] public class FolderBrowser { public string SelectedPath { get; set; } public bool ShowDialog() { var shell = new Shell32.Shell(); var folder = shell.BrowseForFolder(0, "选择上传目录", 0); if (folder != null) { SelectedPath = folder.Self.Path; return true; } return false; } }

3.2 大文件分片上传

前端分片逻辑
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB分片 async function uploadFile(file, path) { const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const fileHash = await calculateMD5(file); for (let i = 0; i < totalChunks; i++) { const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE); const formData = new FormData(); formData.append('chunk', chunk); formData.append('chunkIndex', i); formData.append('totalChunks', totalChunks); formData.append('fileHash', fileHash); formData.append('relativePath', path); await fetch('/api/upload', { method: 'POST', body: formData, headers: { 'X-Resume-Token': getResumeToken() } }); } }
服务端接收处理
[HttpPost] [RequestSizeLimit(2147483648)] public async Task<IActionResult> UploadChunk( IFormFile chunk, int chunkIndex, int totalChunks, string fileHash, string relativePath) { // 验证分片有效性 if (chunk == null || chunk.Length == 0) return BadRequest("无效分片"); // 创建临时存储目录 var tempDir = Path.Combine(_config["TempPath"], fileHash); Directory.CreateDirectory(tempDir); // 保存分片 var chunkPath = Path.Combine(tempDir, $"{chunkIndex}.part"); using (var stream = new FileStream(chunkPath, FileMode.Create)) { await chunk.CopyToAsync(stream); } // 检查是否全部完成 if (Directory.GetFiles(tempDir).Length == totalChunks) { await MergeChunks(tempDir, relativePath); } return Ok(new { chunkIndex }); }

4. 企业级功能增强

4.1 安全控制措施

文件类型白名单验证
private static readonly Dictionary<string, List<byte[]>> _fileSignatures = new() { { ".docx", new List<byte[]> { new byte[] { 0x50, 0x4B, 0x03, 0x04 } } }, { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } } }; bool IsValidFileType(IFormFile file) { using var reader = new BinaryReader(file.OpenReadStream()); var signatures = _fileSignatures[Path.GetExtension(file.FileName).ToLower()]; var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length)); return signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature)); }
传输加密方案
// 采用国密SM4加密分片 public static byte[] EncryptChunk(byte[] data, string key) { using var sm4 = new SM4Engine(); var keyBytes = Encoding.UTF8.GetBytes(key); var iv = new byte[16]; sm4.Init(true, new KeyParameter(keyBytes)); sm4.ProcessBlock(data, 0, data, 0); return data; }

4.2 性能优化策略

内存流池技术
public class MemoryStreamPool { private readonly ConcurrentBag<MemoryStream> _pool = new(); public MemoryStream Rent() { if (_pool.TryTake(out var stream)) return stream; return new MemoryStream(81920); // 预分配80KB } public void Return(MemoryStream stream) { stream.SetLength(0); _pool.Add(stream); } }
异步处理管道
// Program.cs builder.Services.AddHostedService<UploadProcessingService>(); // 后台处理服务 public class UploadProcessingService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await _fileQueue.ProcessNextAsync(ProcessFile); await Task.Delay(100, stoppingToken); } } private async Task ProcessFile(UploadTask task) { using var processor = new FileProcessor(task); await processor.ValidateAsync(); await processor.StoreAsync(); await processor.WriteLogAsync(); } }

5. 部署配置要点

5.1 IIS特殊配置

<!-- web.config --> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="2147483648" /> </requestFiltering> </security> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" /> </handlers> </system.webServer>

5.2 Kestrel调优参数

builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxConcurrentConnections = 100; serverOptions.Limits.MaxConcurrentUpgradedConnections = 100; serverOptions.Limits.MaxRequestBodySize = 2147483648; serverOptions.Limits.MinRequestBodyDataRate = null; });

6. 典型问题解决方案

6.1 大文件上传中断处理

断点续传实现
// 前端检查已上传分片 async function checkUploadProgress(fileHash) { const response = await fetch(`/api/upload/progress?hash=${fileHash}`); return await response.json(); } // 服务端记录 public class UploadProgressTracker { public Dictionary<string, List<int>> ProgressRecords { get; } = new(); public void RecordChunk(string fileHash, int chunkIndex) { if (!ProgressRecords.ContainsKey(fileHash)) ProgressRecords[fileHash] = new List<int>(); ProgressRecords[fileHash].Add(chunkIndex); } public List<int> GetProgress(string fileHash) { return ProgressRecords.TryGetValue(fileHash, out var progress) ? progress : new List<int>(); } }

6.2 企业网络限制突破

分片并行上传
// 使用Promise.all实现可控并发 async function parallelUpload(file, maxConcurrent = 3) { const semaphore = new Semaphore(maxConcurrent); const uploadTasks = []; for (let i = 0; i < totalChunks; i++) { await semaphore.acquire(); uploadTasks.push( uploadChunk(i).finally(() => semaphore.release()) ); } await Promise.all(uploadTasks); } class Semaphore { constructor(max) { this.max = max; this.queue = []; } acquire() { return new Promise(resolve => { if (this.max > 0) { this.max--; resolve(); } else { this.queue.push(resolve); } }); } release() { if (this.queue.length > 0) { this.queue.shift()(); } else { this.max++; } } }

7. 安全审计与监控

7.1 上传行为日志

public class UploadAuditMiddleware { public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); await _next(context); sw.Stop(); if (context.Request.Path.StartsWithSegments("/api/upload")) { _logger.LogInformation($"Upload {context.Response.StatusCode} | " + $"User: {context.User.Identity.Name} | " + $"Size: {context.Request.ContentLength} | " + $"Duration: {sw.ElapsedMilliseconds}ms"); await _auditService.RecordAsync( context.User.Identity.Name, context.Request.Headers["X-File-Name"], context.Request.ContentLength ?? 0, context.Connection.RemoteIpAddress); } } }

7.2 实时流量监控

// 基于SignalR的监控看板 public class UploadHub : Hub { private readonly IUploadMonitor _monitor; public async Task<IEnumerable<UploadMetric>> GetMetrics() { return await _monitor.GetCurrentMetrics(); } public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, "Monitors"); await base.OnConnectedAsync(); } } // 前端监控面板 const connection = new signalR.HubConnectionBuilder() .withUrl("/uploadHub") .build(); connection.on("UpdateMetrics", metrics => { updateDashboard(metrics); }); connection.start();

8. 实际部署经验

在某能源集团部署时遇到的关键问题及解决方案:

  1. 问题:跨国传输速度仅50KB/s

    • 解决:启用Azure Front Door加速,传输速度提升至2MB/s
  2. 问题:AD域用户无法访问WebView2

    • 解决:通过组策略推送注册表配置:
      [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge] "WebView2RuntimeInstallPolicy"=dword:00000001
  3. 问题:防病毒软件锁定临时文件

    • 解决:将临时目录加入杀毒软件白名单:
      Add-MpPreference -ExclusionPath "D:\UploadTemp"

9. 性能测试数据

在模拟国企典型环境中的测试结果(100并发):

文件大小传统方案本方案提升
100MB45s12s275%
1GB失败68s-
10GB失败11m-

关键指标:

  • CPU利用率降低40%
  • 内存消耗减少65%
  • 失败率从12%降至0.3%

10. 扩展功能实现

10.1 文件自动归类

public class FileClassifier { public string Classify(string path) { var ext = Path.GetExtension(path).ToLower(); return ext switch { ".docx" or ".pdf" => "文档", ".dwg" or ".rvt" => "设计图纸", ".mp4" or ".avi" => "视频资料", _ => "其他" }; } }

10.2 与OA系统集成

// 公文系统回调接口 [HttpPost] public async Task<IActionResult> NotifyOA([FromBody] OANotification notification) { var fileInfo = await _db.Files .Where(f => f.Hash == notification.FileHash) .FirstOrDefaultAsync(); if (fileInfo == null) return NotFound(); await _oaService.CreateDocument( notification.DocId, fileInfo.Name, fileInfo.Path, fileInfo.UploadUser); return Ok(); }

在具体实施过程中,我们发现国企项目有三个特别需要注意的细节:

  1. 必须保留完整的文件树结构,不能扁平化处理
  2. 上传完成后需要生成符合档案管理规范的MD5校验文件
  3. 所有操作必须留痕且不可篡改

通过本文方案的实施,某省级国资平台已稳定运行3年,累计处理文件超过120TB,成功支撑了企业数字化转型中的核心文件传输需求。

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

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

立即咨询