.NET 8實現(xiàn)大文件分片上傳的高效方案匯總
一、分片上傳的優(yōu)勢
- 提高上傳穩(wěn)定性:單個分片上傳失敗只需重傳該分片,而非整個文件
- 降低內(nèi)存占用:每次只處理文件的一小部分,避免大文件完全加載到內(nèi)存
- 支持?jǐn)帱c續(xù)傳:記錄已上傳分片,可從斷點繼續(xù)上傳
- 并行上傳:可同時上傳多個分片,提高上傳速度
- 進(jìn)度顯示:可精確顯示上傳進(jìn)度,提升用戶體驗
二、.NET 8 分片上傳實現(xiàn)
2.1 前端實現(xiàn)(JavaScript)
// 文件選擇處理 document.getElementById('fileInput').addEventListener('change', async function(e) { const file = e.target.files[0]; if (!file) return; const chunkSize = 5 * 1024 * 1024; // 5MB分片 const totalChunks = Math.ceil(file.size / chunkSize); const fileId = generateFileId(file.name, file.size); // 生成唯一文件ID // 并行上傳控制(限制同時上傳的分片數(shù)) const parallelLimit = 3; let currentChunk = 0; let activeUploads = 0; let uploadedChunks = 0; while (currentChunk < totalChunks || activeUploads > 0) { if (activeUploads < parallelLimit && currentChunk < totalChunks) { activeUploads++; const chunkStart = currentChunk * chunkSize; const chunkEnd = Math.min(file.size, chunkStart + chunkSize); const chunk = file.slice(chunkStart, chunkEnd); try { await uploadChunk(fileId, currentChunk, chunk, totalChunks, file.name); uploadedChunks++; updateProgress(uploadedChunks / totalChunks * 100); } catch (error) { console.error(`分片 ${currentChunk} 上傳失敗:`, error); // 可加入重試邏輯 continue; // 重新嘗試當(dāng)前分片 } finally { activeUploads--; } currentChunk++; } else { // 等待有上傳完成 await new Promise(resolve => setTimeout(resolve, 100)); } } // 所有分片上傳完成,通知服務(wù)器合并 await notifyServerToMerge(fileId, file.name, totalChunks); console.log('文件上傳完成'); }); async function uploadChunk(fileId, chunkNumber, chunkData, totalChunks, fileName) { const formData = new FormData(); formData.append('fileId', fileId); formData.append('chunkNumber', chunkNumber); formData.append('totalChunks', totalChunks); formData.append('fileName', fileName); formData.append('chunk', chunkData); const response = await fetch('/api/upload/chunk', { method: 'POST', body: formData }); if (!response.ok) { throw new Error('上傳失敗'); } } function updateProgress(percent) { console.log(`上傳進(jìn)度: ${percent.toFixed(2)}%`); // 更新UI進(jìn)度條 document.getElementById('progressBar').style.width = `${percent}%`; }
2.2 后端實現(xiàn)(.NET 8 Web API)
控制器代碼
[ApiController] [Route("api/[controller]")] public class UploadController : ControllerBase { private readonly IFileUploadService _uploadService; private readonly ILogger<UploadController> _logger; public UploadController(IFileUploadService uploadService, ILogger<UploadController> logger) { _uploadService = uploadService; _logger = logger; } [HttpPost("chunk")] [DisableRequestSizeLimit] // 禁用請求大小限制 public async Task<IActionResult> UploadChunk() { try { var form = await Request.ReadFormAsync(); var chunk = form.Files["chunk"]; if (chunk == null || chunk.Length == 0) return BadRequest("無效的分片數(shù)據(jù)"); var fileId = form["fileId"].ToString(); var chunkNumber = int.Parse(form["chunkNumber"].ToString()); var totalChunks = int.Parse(form["totalChunks"].ToString()); var fileName = form["fileName"].ToString(); await _uploadService.SaveChunkAsync(fileId, chunkNumber, totalChunks, fileName, chunk); return Ok(new { chunkNumber, fileId }); } catch (Exception ex) { _logger.LogError(ex, "分片上傳失敗"); return StatusCode(500, $"分片上傳失敗: {ex.Message}"); } } [HttpPost("merge")] public async Task<IActionResult> MergeChunks([FromBody] MergeRequest request) { try { var filePath = await _uploadService.MergeChunksAsync(request.FileId, request.FileName, request.TotalChunks); return Ok(new { filePath }); } catch (Exception ex) { _logger.LogError(ex, "分片合并失敗"); return StatusCode(500, $"分片合并失敗: {ex.Message}"); } } } public record MergeRequest(string FileId, string FileName, int TotalChunks);
文件上傳服務(wù)實現(xiàn)
public interface IFileUploadService { Task SaveChunkAsync(string fileId, int chunkNumber, int totalChunks, string fileName, IFormFile chunk); Task<string> MergeChunksAsync(string fileId, string fileName, int totalChunks); } public class FileUploadService : IFileUploadService { private readonly string _uploadPath; private readonly ILogger<FileUploadService> _logger; public FileUploadService(IConfiguration configuration, ILogger<FileUploadService> logger) { _uploadPath = configuration["FileUpload:Path"] ?? Path.Combine(Directory.GetCurrentDirectory(), "Uploads"); _logger = logger; if (!Directory.Exists(_uploadPath)) { Directory.CreateDirectory(_uploadPath); } } public async Task SaveChunkAsync(string fileId, int chunkNumber, int totalChunks, string fileName, IFormFile chunk) { // 為每個文件創(chuàng)建臨時目錄 var tempDir = Path.Combine(_uploadPath, fileId); if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } var chunkPath = Path.Combine(tempDir, $"{chunkNumber}.part"); // 使用文件流寫入,避免內(nèi)存占用過高 await using var stream = new FileStream(chunkPath, FileMode.Create); await chunk.CopyToAsync(stream); _logger.LogInformation("保存分片 {ChunkNumber}/{TotalChunks} 成功,文件ID: {FileId}", chunkNumber, totalChunks, fileId); } public async Task<string> MergeChunksAsync(string fileId, string fileName, int totalChunks) { var tempDir = Path.Combine(_uploadPath, fileId); if (!Directory.Exists(tempDir)) { throw new DirectoryNotFoundException($"臨時目錄不存在: {tempDir}"); } // 驗證所有分片是否都存在 for (int i = 0; i < totalChunks; i++) { var chunkPath = Path.Combine(tempDir, $"{i}.part"); if (!System.IO.File.Exists(chunkPath)) { throw new FileNotFoundException($"分片 {i} 不存在", chunkPath); } } // 最終文件路徑 var filePath = Path.Combine(_uploadPath, $"{fileId}_{fileName}"); // 合并分片 await using var outputStream = new FileStream(filePath, FileMode.Create); for (int i = 0; i < totalChunks; i++) { var chunkPath = Path.Combine(tempDir, $"{i}.part"); await using var chunkStream = new FileStream(chunkPath, FileMode.Open); await chunkStream.CopyToAsync(outputStream); _logger.LogDebug("已合并分片 {ChunkNumber}/{TotalChunks}", i, totalChunks); } // 刪除臨時分片 try { Directory.Delete(tempDir, true); _logger.LogInformation("文件合并完成,臨時目錄已刪除: {TempDir}", tempDir); } catch (Exception ex) { _logger.LogWarning(ex, "刪除臨時目錄失敗: {TempDir}", tempDir); } return filePath; } }
2.3 配置與注冊服務(wù)
在 Program.cs
中添加服務(wù)注冊和配置:
var builder = WebApplication.CreateBuilder(args); // 添加服務(wù) builder.Services.AddScoped<IFileUploadService, FileUploadService>(); // 配置上傳路徑 builder.Services.Configure<FileUploadOptions>(builder.Configuration.GetSection("FileUpload")); var app = builder.Build(); // 啟用靜態(tài)文件服務(wù)(如果需要下載) app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(builder.Environment.ContentRootPath, "Uploads")), RequestPath = "/uploads" }); app.MapControllers(); app.Run();
在 appsettings.json
中添加配置:
{ "FileUpload": { "Path": "Uploads", "MaxFileSize": "1073741824" // 1GB } }
三、高級功能實現(xiàn)
3.1 斷點續(xù)傳
[HttpGet("check")] public IActionResult CheckChunks(string fileId, int totalChunks) { var tempDir = Path.Combine(_uploadPath, fileId); if (!Directory.Exists(tempDir)) { return Ok(new { uploadedChunks = Array.Empty<int>() }); } var uploaded = Directory.GetFiles(tempDir) .Select(f => Path.GetFileNameWithoutExtension(f)) .Where(f => int.TryParse(f, out _)) .Select(int.Parse) .ToArray(); return Ok(new { uploadedChunks = uploaded }); }
前端相應(yīng)修改:
// 在上傳前檢查已上傳的分片 const checkResponse = await fetch(`/api/upload/check?fileId=${fileId}&totalChunks=${totalChunks}`); const { uploadedChunks } = await checkResponse.json(); // 跳過已上傳的分片 while (currentChunk < totalChunks) { if (uploadedChunks.includes(currentChunk)) { currentChunk++; uploadedChunks++; updateProgress(uploadedChunks / totalChunks * 100); continue; } // ...原有上傳邏輯 }
3.2 文件校驗(MD5/SHA)
public async Task<string> CalculateFileHash(string filePath) { await using var stream = System.IO.File.OpenRead(filePath); using var sha256 = SHA256.Create(); var hashBytes = await sha256.ComputeHashAsync(stream); return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); } // 在上傳完成后驗證文件完整性 var calculatedHash = await CalculateFileHash(filePath); if (calculatedHash != expectedHash) { System.IO.File.Delete(filePath); throw new Exception("文件校驗失敗,可能在上傳過程中損壞"); }
3.3 分片大小動態(tài)調(diào)整
根據(jù)網(wǎng)絡(luò)狀況動態(tài)調(diào)整分片大?。?/strong>
// 動態(tài)調(diào)整分片大小 let chunkSize = 1 * 1024 * 1024; // 初始1MB let uploadSpeeds = []; async function uploadChunk(...) { const startTime = performance.now(); // ...上傳邏輯 const endTime = performance.now(); const duration = (endTime - startTime) / 1000; // 秒 const speed = chunkData.size / duration; // bytes/s uploadSpeeds.push(speed); if (uploadSpeeds.length > 5) { uploadSpeeds.shift(); } const avgSpeed = uploadSpeeds.reduce((sum, val) => sum + val, 0) / uploadSpeeds.length; // 根據(jù)平均速度調(diào)整分片大小 (目標(biāo): 每個分片上傳時間在5-15秒之間) const targetChunkTime = 10; // 10秒 chunkSize = Math.min( 50 * 1024 * 1024, // 最大50MB Math.max( 1 * 1024 * 1024, // 最小1MB Math.round(avgSpeed * targetChunkTime) ) ); }
四、性能優(yōu)化與安全考慮
性能優(yōu)化:
- 使用流式處理而非緩沖整個文件
- 并行上傳控制
- 動態(tài)分片大小調(diào)整
- 內(nèi)存管理優(yōu)化
安全考慮:
- 文件類型檢查
- 文件大小限制
- 病毒掃描集成
- 訪問控制與權(quán)限驗證
- 文件名校驗與處理
錯誤處理:
- 網(wǎng)絡(luò)中斷重試機制
- 分片校驗
- 超時處理
- 并發(fā)沖突處理
五、測試建議
單元測試:
- 分片保存與合并功能
- 文件校驗邏輯
- 異常情況處理
集成測試:
- 完整上傳流程
- 斷點續(xù)傳場景
- 網(wǎng)絡(luò)不穩(wěn)定的情況
性能測試:
- 不同文件大小的上傳時間
- 并發(fā)上傳測試
- 內(nèi)存占用監(jiān)控
六、總結(jié)
本文詳細(xì)介紹了在.NET 8中實現(xiàn)大文件分片上傳的完整方案,包括前端分片處理、后端分片接收與合并、斷點續(xù)傳等高級功能。該方案具有以下特點:
- 高效穩(wěn)定,適合大文件上傳場景
- 內(nèi)存占用低,使用流式處理
- 支持?jǐn)帱c續(xù)傳,提升用戶體驗
- 可擴展性強,易于添加文件校驗、病毒掃描等功能
通過合理配置和優(yōu)化,該方案可以滿足企業(yè)級應(yīng)用對大文件上傳的需求,為用戶提供流暢可靠的上傳體驗。
以上就是.NET 8實現(xiàn)大文件分片上傳的高效方案匯總的詳細(xì)內(nèi)容,更多關(guān)于.NET 8大文件分片上傳的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
.Net Core如何對MongoDB執(zhí)行多條件查詢
這篇文章主要介紹了.Net Core如何對MongoDB執(zhí)行多條件查詢,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07ajax.net +jquery 無刷新三級聯(lián)動的實例代碼
ajax.net +jquery 無刷新三級聯(lián)動的實例代碼,需要的朋友可以參考一下2013-05-05ASP.NET?Core項目中調(diào)用WebService的方法
這篇文章介紹了ASP.NET?Core項目中調(diào)用WebService的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03.NET?Core?使用委托實現(xiàn)動態(tài)流程組裝的思路詳解
模擬管道模型中間件(Middleware)部分,運用委托,進(jìn)行動態(tài)流程組裝,本次代碼實現(xiàn)就直接我之前寫的動態(tài)代理實現(xiàn)AOP的基礎(chǔ)上改的,就不另起爐灶了,主要思路就是運用委托,具體實現(xiàn)過程跟隨小編一起看看吧2022-01-01visual Studio 2017創(chuàng)建簡單控制臺程序
這篇文章主要為大家詳細(xì)介紹了visual Studio 2017創(chuàng)建簡單控制臺程序,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11Asp.Net實現(xiàn)無限分類生成表格的方法(后臺自定義輸出table)
這篇文章主要介紹了Asp.Net實現(xiàn)無限分類生成表格的方法,同時后臺自定義輸出table表格,詳細(xì)分析了asp.net生成表格的相關(guān)技巧,需要的朋友可以參考下2016-04-04DataTable轉(zhuǎn)成字符串復(fù)制到txt文本的小例子
DataTable轉(zhuǎn)成字符串復(fù)制到txt文本的小例子,需要的朋友可以參考一下2013-03-03