后端实现大文件分片上传

项目框架 net6 webapi

放开上传大小限制

放开代码 | 框架层限制

在 Program.cs 文件中添加如下代码
不然会出现下面的限制错误

From表单限制:Failed to read the request form. Multipart body length limit 134217728 exceeded
请求体超长:Request body too large. The max request body size is 30000000 bytes.

builder.Services
    .Configure<KestrelServerOptions>(x =>
    {
        x.AllowSynchronousIO = true; // 配置可以同步请求读取流数据
        x.Limits.MaxRequestBodySize = int.MaxValue;
    })
    .Configure<IISServerOptions>(x =>
    {
        x.AllowSynchronousIO = true;
        x.MaxRequestBodySize = int.MaxValue; // 设置请求体可接收的最大值
    })
    .Configure<FormOptions>(x =>
    {
        // 设置表单上传文件的大小限制
        // 如果不配置,默认是128兆
        x.MultipartBodyLengthLimit = int.MaxValue;
    });

设置 nginx 或 iis 中的大小限制

IIS 层

找到对应程序的 web.config
添加如下代码配置:

<security>
  	  <requestFiltering>
    	    <!-- 1000 MB in bytes -->
    	    <requestLimits maxAllowedContentLength="1048576000" />
  	  </requestFiltering>
	</security>

若缺少 system.webServer 等节点,添加上即可
image.png

nginx 层

在 conf 文件里的 nginx.conf 配置文件 http 中添加节点

client_max_body_size 1000m;

image.png

分片上传代码实现

请求参数 UploadFileInChunksVO 类

/// <summary>
/// 功 能: N/A
/// V0.01 2023/10/24 17:56:36 xliu  初版
/// </summary>
public class UploadFileInChunksVO
{        
		/// <summary>
    ///  分片后的文件
    /// </summary>
    public IFormFile File { get; set; }
    /// <summary>
    /// 当前块,从1开始
    /// </summary>
    public int ChunkNumber { get; set; }

    /// <summary>
    /// 总块数
    /// </summary>
    public int TotalChunks { get; set; }
}

添加控制器 Controller

必须添加 [FromForm] 标识,不然 FIle 识别不到

AppSettings 是一个自行实现读取配置文件的方法
RunInterceptException 是自定义的异常类,统一错误捕获处会对这个做 400 的异常处理

public async Task<IActionResult> UploadFile([FromForm] UploadFileInChunksVO chunksVO)
{
    if (chunksVO.ChunkNumber == 0 || chunksVO.TotalChunks == 0)
        throw new RunInterceptException("上传的数据块标识能为0");

    // 创建用于存储上传文件的文件夹
    // 可以是读取当前服务的地址,我这边项目是集群化的所有存储地址必须是一个地方不然没办法合并
    var path = AppSettings.app(new string[] { "Startup", "AppData" }); 
    if (path == null || path.IsNullOrEmpty())
        throw new RunInterceptException("文件存储服务路径为空");

    var folderPath = Path.Combine(path, "Uploads", "JD_EDI");
    var tempPath = Path.Combine(folderPath, "Temp");

    await _fileService.UploadFileInChunksAsync(chunksVO.File, tempPath, chunksVO.ChunkNumber);

    // 上传最后一块了 进行合并
    if (chunksVO.ChunkNumber == chunksVO.TotalChunks)
    {
        // 构造合并后的文件路径
        var mergedFilePath = Path.Combine(folderPath, chunksVO.File.FileName);
        await _fileService.MergeFileAsync(mergedFilePath, tempPath, chunksVO.File.FileName, chunksVO.TotalChunks);
        // 合并后的操作
        var res = await _ediService.SalesStockAsync(mergedFilePath);
        return Ok("处理成功数:" + res);
    }

    return Ok("接收成功");
}

服务接口定义 IUploadFileService

项目做了接口、服务分离。使用依赖注入的方式
若没这项要求的 可以直接使用后面的方法实现

/// <summary>
/// 功 能: 上传文件服务
/// V0.01 2023/10/24 15:01:01 xliu  初版
/// </summary>
public interface IUploadFileService
{
    /// <summary>
    /// 分片上传文件
    /// </summary>
    /// <param name="file">正在上传的文件</param>
    /// <param name="tempFilePath">临时存储分片数据的目录</param>
    /// <param name="chunkNumber">当前分片块</param>
    /// <returns>最终文件保存路径</returns>
    Task<string> UploadFileInChunksAsync(IFormFile file, string tempFilePath , int chunkNumber);

    /// <summary>
    /// 用于合并文件块并处理完整文件的方法
    /// </summary>
    /// <param name="mergedFilePath">合并后文件的保存地址</param>
    /// <param name="tempPath">分片文件的保存地址</param>
    /// <param name="fileName"></param>
    /// <param name="totalChunks"></param>
    /// <returns></returns>
    Task MergeFileAsync(string mergedFilePath, string tempPath, string fileName, int totalChunks);
}

服务接口实现 UploadFileService

/// <summary>
/// 功 能: N/A
/// V0.01 2023/10/24 15:05:09 xliu  初版
/// </summary>
public class UploadFileService : IUploadFileService
{
    
    public async Task<string> UploadFileInChunksAsync(IFormFile file, string tempPath, int chunkNumber)
    {
        if (!Directory.Exists(tempPath))
        {
            Directory.CreateDirectory(tempPath);
        }

        // 构造当前块文件的路径
        var filePath = Path.Combine(tempPath, file.FileName + "_" + chunkNumber);
        // 将文件块写入磁盘
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await file.CopyToAsync(fileStream);
        }
        
        return filePath;
    }

    public async Task MergeFileAsync(string mergedFilePath, string tempPath, string fileName, int totalChunks)
    {
        // 创建用于存储合并后文件的流
        using var mergedFileStream = new FileStream(mergedFilePath, FileMode.Create);
        // 循环处理每个文件块
        for (int i = 1; i <= totalChunks; i++)
        {
            // 构造当前文件块的路径
            var chunkFilePath = Path.Combine(tempPath, fileName + "_" + i);
            // 创建用于读取文件块的流
            using (var chunkFileStream = new FileStream(chunkFilePath, FileMode.Open))
            {
                // 将文件块内容复制到合并文件流中
                await chunkFileStream.CopyToAsync(mergedFileStream);
            }
            // 删除已合并的文件块
            System.IO.File.Delete(chunkFilePath);
        }
    }

上传测试

这边只给到 postman 的示例
前端实现 无非就是根据文件大小切分成多个文件 单次上传一部分
每次上传变化 file 和 chuckNumber 即可,当 chunkNumber 和 totalChunks 相等时便上传完成
image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值