• 后端实现大文件分片上传


    项目框架 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(x =>
        {
            x.AllowSynchronousIO = true; // 配置可以同步请求读取流数据
            x.Limits.MaxRequestBodySize = int.MaxValue;
        })
        .Configure(x =>
        {
            x.AllowSynchronousIO = true;
            x.MaxRequestBodySize = int.MaxValue; // 设置请求体可接收的最大值
        })
        .Configure(x =>
        {
            // 设置表单上传文件的大小限制
            // 如果不配置,默认是128兆
            x.MultipartBodyLengthLimit = int.MaxValue;
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    设置 nginx 或 iis 中的大小限制

    IIS 层

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

    
      	  
        	    <!-- 1000 MB in bytes -->
        	    "1048576000" />
      	  </requestFiltering>
    	</security>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    nginx 层

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

    client_max_body_size 1000m;
    
    • 1

    image.png

    分片上传代码实现

    请求参数 UploadFileInChunksVO 类

    /// 
    /// 功 能: N/A
    /// V0.01 2023/10/24 17:56:36 xliu  初版
    /// </summary>
    public class UploadFileInChunksVO
    {        
    		/// 
        ///  分片后的文件
        /// </summary>
        public IFormFile File { get; set; }
        /// 
        /// 当前块,从1开始
        /// </summary>
        public int ChunkNumber { get; set; }
    
        /// 
        /// 总块数
        /// </summary>
        public int TotalChunks { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    添加控制器 Controller

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

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

    public async Task 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("接收成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    服务接口定义 IUploadFileService

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

    /// 
    /// 功 能: 上传文件服务
    /// V0.01 2023/10/24 15:01:01 xliu  初版
    /// </summary>
    public interface IUploadFileService
    {
        /// 
        /// 分片上传文件
        /// </summary>
        /// <param name="file">正在上传的文件</param>
        /// <param name="tempFilePath">临时存储分片数据的目录</param>
        /// <param name="chunkNumber">当前分片块</param>
        /// 最终文件保存路径</returns>
        Task UploadFileInChunksAsync(IFormFile file, string tempFilePath , int chunkNumber);
    
        /// 
        /// 用于合并文件块并处理完整文件的方法
        /// </summary>
        /// <param name="mergedFilePath">合并后文件的保存地址</param>
        /// <param name="tempPath">分片文件的保存地址</param>
        /// <param name="fileName"></param>
        /// <param name="totalChunks"></param>
        /// </returns>
        Task MergeFileAsync(string mergedFilePath, string tempPath, string fileName, int totalChunks);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    服务接口实现 UploadFileService

    /// 
    /// 功 能: N/A
    /// V0.01 2023/10/24 15:05:09 xliu  初版
    /// </summary>
    public class UploadFileService : IUploadFileService
    {
        
        public async Task 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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    上传测试

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

  • 相关阅读:
    git使用,一点点
    .NET Core 中的 ORM 框架对比
    详解nginx的root与alias
    如何用jxTMS开发一个功能(三)
    ARM中断实验
    OkHttp原理 一篇文章就够了
    【Python&GIS】解决GIS属性表、矢量字段乱码,中文乱码
    【实践篇】Redis最强Java客户端(四)之Ression分布式集合使用指南
    数据结构与算法笔记五(哈希函数和哈希表,有序表并查集,KMP)
    【数据分享】1901-2022年1km分辨率逐年降水栅格数据(免费获取/全国/分省)
  • 原文地址:https://blog.csdn.net/weixin_44517477/article/details/134058652