欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET 8實現(xiàn)大文件分片上傳的高效方案匯總

 更新時間:2025年04月23日 08:43:10   作者:Microi風(fēng)閑  
在當(dāng)今互聯(lián)網(wǎng)應(yīng)用中,大文件上傳是一個常見需求,尤其是對于云存儲、視頻網(wǎng)站、企業(yè)文檔管理系統(tǒng)等場景,傳統(tǒng)的單次文件上傳方式在面對大文件時往往會遇到網(wǎng)絡(luò)不穩(wěn)定、內(nèi)存占用高、上傳失敗需重傳整個文件等問題,本文將介紹如何在.NET 8中實現(xiàn)高效穩(wěn)定的大文件分片上傳方案

一、分片上傳的優(yōu)勢

  1. 提高上傳穩(wěn)定性:單個分片上傳失敗只需重傳該分片,而非整個文件
  2. 降低內(nèi)存占用:每次只處理文件的一小部分,避免大文件完全加載到內(nèi)存
  3. 支持?jǐn)帱c續(xù)傳:記錄已上傳分片,可從斷點繼續(xù)上傳
  4. 并行上傳:可同時上傳多個分片,提高上傳速度
  5. 進(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)化與安全考慮

  1. 性能優(yōu)化

    • 使用流式處理而非緩沖整個文件
    • 并行上傳控制
    • 動態(tài)分片大小調(diào)整
    • 內(nèi)存管理優(yōu)化
  2. 安全考慮

    • 文件類型檢查
    • 文件大小限制
    • 病毒掃描集成
    • 訪問控制與權(quán)限驗證
    • 文件名校驗與處理
  3. 錯誤處理

    • 網(wǎng)絡(luò)中斷重試機制
    • 分片校驗
    • 超時處理
    • 并發(fā)沖突處理

五、測試建議

  1. 單元測試:

    • 分片保存與合并功能
    • 文件校驗邏輯
    • 異常情況處理
  2. 集成測試:

    • 完整上傳流程
    • 斷點續(xù)傳場景
    • 網(wǎng)絡(luò)不穩(wěn)定的情況
  3. 性能測試:

    • 不同文件大小的上傳時間
    • 并發(fā)上傳測試
    • 內(nèi)存占用監(jiān)控

六、總結(jié)

本文詳細(xì)介紹了在.NET 8中實現(xiàn)大文件分片上傳的完整方案,包括前端分片處理、后端分片接收與合并、斷點續(xù)傳等高級功能。該方案具有以下特點:

  1. 高效穩(wěn)定,適合大文件上傳場景
  2. 內(nèi)存占用低,使用流式處理
  3. 支持?jǐn)帱c續(xù)傳,提升用戶體驗
  4. 可擴展性強,易于添加文件校驗、病毒掃描等功能

通過合理配置和優(yōu)化,該方案可以滿足企業(yè)級應(yīng)用對大文件上傳的需求,為用戶提供流暢可靠的上傳體驗。

以上就是.NET 8實現(xiàn)大文件分片上傳的高效方案匯總的詳細(xì)內(nèi)容,更多關(guān)于.NET 8大文件分片上傳的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論