You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在CouchDB中实现大文件分块上传及下载合并功能

CouchDB 大文件分块上传/下载 C# 实现方案

实现思路

  • 采用两类文档存储结构:
    • 文件元数据文档:存储文件基础信息,包括文件名、总大小、分块大小、总分块数等,不存储实际二进制内容
    • 分块内容文档:每个文档存储单块二进制内容,关联对应元数据文档的id,以及当前分块的序号
  • 分块大小推荐设置为1MB,平衡单文档大小和请求次数,避免超过CouchDB单文档大小阈值

前置定义

// 文件元数据实体
public class CouchDbFileMeta
{
    public string _id { get; set; }
    public string _rev { get; set; }
    public string FileName { get; set; }
    public long TotalSize { get; set; }
    public int ChunkSize { get; set; }
    public int TotalChunks { get; set; }
}

// 分块内容实体
public class CouchDbFileChunk
{
    public string _id { get; set; }
    public string _rev { get; set; }
    public string ParentFileId { get; set; }
    public int ChunkIndex { get; set; }
    public byte[] Data { get; set; }
}

分块上传实现

替换原有InsertDataToCouchDb方法,默认采用异步调用避免线程死锁:

public async Task<string> InsertDataToCouchDb(string dbName, string fileId, string filename, byte[] fileContent)
{
    var connection = System.Configuration.ConfigurationManager.ConnectionStrings["CouchDb"].ConnectionString;
    // 分块大小设置为1MB,可根据业务调整
    const int chunkSize = 1 * 1024 * 1024;
    using var db = new MyCouchClient(connection, dbName);
    
    // 计算总分块数
    var totalChunks = (int)Math.Ceiling((double)fileContent.Length / chunkSize);
    var uploadedChunkIds = new List<string>();

    try
    {
        // 逐个上传分块
        for (var i = 0; i < totalChunks; i++)
        {
            var start = i * chunkSize;
            var end = Math.Min(start + chunkSize, fileContent.Length);
            var chunkData = new byte[end - start];
            Buffer.BlockCopy(fileContent, start, chunkData, 0, chunkData.Length);

            var chunk = new CouchDbFileChunk
            {
                // 分块id按规则生成,方便后续查询
                _id = $"{fileId}_chunk_{i}",
                ParentFileId = fileId,
                ChunkIndex = i,
                Data = chunkData
            };

            var chunkResp = await db.Entities.PutAsync(chunk);
            uploadedChunkIds.Add(chunk.Id);
        }

        // 所有分块上传成功后,存储元数据
        var meta = new CouchDbFileMeta
        {
            _id = fileId,
            FileName = filename,
            TotalSize = fileContent.Length,
            ChunkSize = chunkSize,
            TotalChunks = totalChunks
        };

        var metaResp = await db.Entities.PutAsync(meta);
        return metaResp.Id;
    }
    catch (Exception)
    {
        // 上传失败,清理已上传的分块,避免垃圾数据
        foreach (var chunkId in uploadedChunkIds)
        {
            await db.Documents.DeleteAsync(chunkId);
        }
        throw;
    }
}

分块下载实现

替换原有FetchDataFromCouchDb方法:

public async Task<byte[]> FetchDataFromCouchDb(string dbName, string fileId)
{
    var connection = System.Configuration.ConfigurationManager.ConnectionStrings["CouchDb"].ConnectionString;
    using var db = new MyCouchClient(connection, dbName);
    
    // 先拉取元数据
    var metaResp = await db.Entities.GetAsync<CouchDbFileMeta>(fileId);
    var meta = metaResp.Content;
    if (meta == null)
    {
        throw new KeyNotFoundException("指定文件不存在");
    }

    // 按序号拉取所有分块
    var chunks = new byte[meta.TotalChunks][];
    for (var i = 0; i < meta.TotalChunks; i++)
    {
        var chunkId = $"{fileId}_chunk_{i}";
        var chunkResp = await db.Entities.GetAsync<CouchDbFileChunk>(chunkId);
        chunks[i] = chunkResp.Content.Data;
    }

    // 合并分块为完整字节数组
    using var ms = new MemoryStream();
    foreach (var chunk in chunks)
    {
        await ms.WriteAsync(chunk, 0, chunk.Length);
    }
    return ms.ToArray();
}

注意事项

  • 原有代码中直接使用.Result调用异步方法存在线程死锁风险,推荐统一改为async/await异步调用模式,若必须保留同步方法签名,可自行调整为同步调用
  • 若文件大小超过100MB,可适当调大分块大小到2MB-4MB,减少请求次数
  • 生产环境可给分块文档的ParentFileId字段建立视图索引,方便批量查询分块以及后续的垃圾数据清理

内容的提问来源于stack exchange,提问作者Hardik Barot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 20:48:00