.NET Core Web APi大文件分片上传研究实现

前言

前两天发表利用FormData进行文件上传,然后有人问要是大文件几个G上传怎么搞,常见的不就是分片再搞下断点续传,动动手差不多也能搞出来,只不过要深入的话,考虑的东西还是很多。由于断点续传之前写个几篇,这里试试利用FormData来进行分片上传。

.NET Core Web APi文件分片上传

这里我们依然是使用FormData来上传,只不过在上传之前对文件进行分片处理,如下HTML代码

<div class="form-horizontal" style="margin-top:80px;">
  <div class="form-group">
    <div class="col-md-10">
      <input name="file" id="file" type="file" />
    </div>
  </div>
  <div class="form-group">
    <div class="col-md-offset-2 col-md-10">
      <input type="submit" id="submit" value="上传" class="btn btn-success" />
    </div>
  </div>
</div>

接下来则是上传脚本,如下:

$(function () {
  $('#submit').click(function () {
    UploadFile($('#file')[0].files);
  });
});

简单来说只需实现上述UploadFile方法,对大文件进行分片处理,然后上传就完事,文件上传后大致如下图所示,最后只需将所有文件进行合并处理为目标文件即可

接下来我们详细讲讲如何实现,当然重点就在于如何进行分片处理,我们拿到上传目标文件,然后通过slice方法进行分片,在分片处理之前我们定义缓冲区大小(默认为8兆),然后循环遍历文件大小,然后将分片数据塞入分片数组,最后利用循环或者队列先进先出机制获取数组分片元素上传。

function UploadFile(targetFile) {
    // 创建上传文件分片缓冲区
    var fileChunks = [];
    // 目标文件
    var file = targetFile[0];
    // 设置分片缓冲区大小
    var maxFileSizeMB = 8;
    var bufferChunkSize = maxFileSizeMB * (1024 * 1024);
    // 读取文件流起始位置
    var fileStreamPos = 0;
    // 设置下一次读取缓冲区初始大小
    var endPos = bufferChunkSize;
    // 文件大小
    var size = file.size;
    // 将文件进行循环分片处理塞入分片数组
    while (fileStreamPos < size) {
      var fileChunkInfo = {
        file: file.slice(fileStreamPos, endPos),
        start: fileStreamPos,
        end: endPos
      }
      fileChunks.push(fileChunkInfo);
      fileStreamPos = endPos;
      endPos = fileStreamPos + bufferChunkSize;
    }
    // 获取上传文件分片总数量
    var totalParts = fileChunks.length;
    var partCount = 0;
    // 循环调用上传每一片
    while (chunk = fileChunks.shift()) {
      partCount++;
      // 上传文件命名约定
      var filePartName = file.name + ".partNumber-" + partCount;
      chunk.filePartName = filePartName;
      // url参数
      var url = 'partNumber=' + partCount + '&chunks=' + totalParts + '&size=' + bufferChunkSize + '&start=' + chunk.start + '&end=' + chunk.end + '&total=' + size;
      chunk.urlParameter = url;
      // 上传文件
      UploadFileChunk(chunk);
    }
}

上述关于分片塞入数组就不用再废话,这里我们将每一片文件命名先进行一个约定(文件名+“.partNumber” + 分片号),以便所有分片上传完成后获取按照文件名中的分片号对其进行排序合并,这也就是合并文件的依据。接下来就是上传每一片文件

function UploadFileChunk(chunk) {
  var data = new FormData();
  data.append("file", chunk.file, chunk.filePartName);
  $.ajax({
    url: '/api/upload/upload?' + chunk.urlParameter,
    type: "post",
    cache: false,
    contentType: false,
    processData: false,
    data: data,
  });
}

我们可以看到在URL上额外加了其他参数,为什么要加上这些参数呢?主要为解决几个问题,其一:前端确认缓冲区大小,我们获取前端确认的缓冲区大小,这样后台不用写死,更加灵活,万一后续进行了修改,谁知道呢?其二:我们怎么确定文件是否已经全部上传完了呢?在URL上我们添加分片总数和文件实际大小来完全确定文件已经全部上传和文件完整无缺。当然也额外添加了每一片读取的起始位置和结束位置,若有所需也可以利用。多余的就不用我再解释。接下来我们看看后台如何对每一片进行处理呢?在.NET Core中实际上提供了对应APi来专门读取FormData数据,利用Microsoft.AspNetCore.WebUtilities命名空间下的MultipartReader类。

首先我们判断是否请求内容是否为FormData,同时通过上下文获取上述文件读取类的参数boundary,如下:

private bool IsMultipartContentType(string contentType)
{
  return
    !string.IsNullOrEmpty(contentType) &&
    contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}

private string GetBoundary(string contentType)
{
  var elements = contentType.Split(' ');
  var element = elements.Where(entry => entry.StartsWith("boundary=")).First();
  var boundary = element.Substring("boundary=".Length);
  if (boundary.Length >= 2 && boundary[0] == '"' &&
    boundary[boundary.Length - 1] == '"')
  {
    boundary = boundary.Substring(1, boundary.Length - 2);
  }
  return boundary;
}

private string GetFileName(string contentDisposition)
{
  return contentDisposition
    .Split(';')
    .SingleOrDefault(part => part.Contains("filename"))
    .Split('=')
    .Last()
    .Trim('"');
}

接下来我们定义分片类而获取URL上每一片的参数,如下:

public class FileChunk
  {
    //文件名
    public string FileName { get; set; }
    /// <summary>
    /// 当前分片
    /// </summary>
    public int PartNumber { get; set; }
    /// <summary>
    /// 缓冲区大小
    /// </summary>
    public int Size { get; set; }
    /// <summary>
    /// 分片总数
    /// </summary>
    public int Chunks { get; set; }
    /// <summary>
    /// 文件读取起始位置
    /// </summary>
    public int Start { get; set; }
    /// <summary>
    /// 文件读取结束位置
    /// </summary>
    public int End { get; set; }
    /// <summary>
    /// 文件大小
    /// </summary>
    public int Total { get; set; }
  }

接下来在提交控制器方法上去读取每一片数据如下

if (!IsMultipartContentType(context.Request.ContentType))
{
  return BadRequest();
}

var boundary = GetBoundary(context.Request.ContentType);
if (string.IsNullOrEmpty(boundary))
{
  return BadRequest();
}

var reader = new MultipartReader(boundary, context.Request.Body);

var section = await reader.ReadNextSectionAsync();

然后就是循环每一片(section),若不为空说明还存有分片文件,然后读取URL上的缓冲区大小,如下:

while (section != null)
{
  //chunk为控制器方法上类FileChunk参数
  var buffer = new byte[chunk.Size];
  var fileName = GetFileName(section.ContentDisposition);
  //这里获取文件名便于查找指定文件夹下所有文件
  chunk.FileName = fileName;
  var path = Path.Combine(_environment.WebRootPath, DEFAULT_FOLDER, fileName);
  using (var stream = new FileStream(path, FileMode.Append))
  {
    int bytesRead;
    do
    {
      bytesRead = await section.Body.ReadAsync(buffer, 0, buffer.Length);
      stream.Write(buffer, 0, bytesRead);

    } while (bytesRead > 0);
  }

  section = await reader.ReadNextSectionAsync();
}

在利用内置APi读取FormData数据时,在.NET Core 3.x会抛出如下异常:

大致原因出在.NET Core内置提供了对于参数的绑定和此方法读取貌似有点冲突导致,我们实现如下特性移除对应绑定,然后将其添加到文件上传方法上即可

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
  public void OnResourceExecuting(ResourceExecutingContext context)
  {
    var factories = context.ValueProviderFactories;
    factories.RemoveType<FormValueProviderFactory>();
    factories.RemoveType<FormFileValueProviderFactory>();
    factories.RemoveType<JQueryFormValueProviderFactory>();
  }

  public void OnResourceExecuted(ResourceExecutedContext context)
  {
  }
}

所有分片文件上传完成后则是合并文件,合并的依据则是判断URL上当前分片数和分片总数是否相等,如下:

//计算上传文件大小实时反馈进度(TODO)

//合并文件(可能涉及转码等)
if (chunk.PartNumber == chunk.Chunks)
{
  await MergeChunkFile(chunk);
}

既然是合并文件那就需要通过分片文件名称上末尾的分片号进行排序和拿到每一个分片文件路径以便合并后删除所有分片文件,所以我们定义如下类

public class FileSort
{
  public const string PART_NUMBER = ".partNumber-";
  /// <summary>
  /// 文件名
  /// </summary>
  public string FileName { get; set; }
  /// <summary>
  /// 文件分片号
  /// </summary>
  public int PartNumber { get; set; }
}

最终合并文件方法,如下:

public async Task MergeChunkFile(FileChunk chunk)
{
  //文件上传目录名
  var uploadDirectoryName = Path.Combine(_environment.WebRootPath, DEFAULT_FOLDER, chunk.FileName);

  //分片文件命名约定
  var partToken = FileSort.PART_NUMBER;

  //上传文件实际名称
  var baseFileName = chunk.FileName.Substring(0, chunk.FileName.IndexOf(partToken));

  //根据命名约定查询指定目录下符合条件的所有分片文件
  var searchpattern = $"{Path.GetFileName(baseFileName)}{partToken}*";

  //获取所有分片文件列表
  var filesList = Directory.GetFiles(Path.GetDirectoryName(uploadDirectoryName), searchpattern);
  if (!filesList.Any()) { return; }

  var mergeFiles = new List<FileSort>();
  foreach (string file in filesList)
  {    var sort = new FileSort
    {
      FileName = file
    };

    baseFileName = file.Substring(0, file.IndexOf(partToken));

    var fileIndex = file.Substring(file.IndexOf(partToken) + partToken.Length);

    int.TryParse(fileIndex, out var number);
    if (number <= 0) { continue; }

    sort.PartNumber = number;

    mergeFiles.Add(sort);
  }  // 按照分片排序
  var mergeOrders = mergeFiles.OrderBy(s => s.PartNumber).ToList();

  // 合并文件
  using var fileStream = new FileStream(baseFileName, FileMode.Create);
  foreach (var fileSort in mergeOrders)
  {
    using FileStream fileChunk =
       new FileStream(fileSort.FileName, FileMode.Open);
    await fileChunk.CopyToAsync(fileStream);
  }

  //删除分片文件
  DeleteFile(mergeFiles);

}

public void DeleteFile(List<FileSort> files)
{
  foreach (var file in files)
  {
    System.IO.File.Delete(file.FileName);
  }
}

总结

下载地址:https://github.com/wangpengxpy/.NETCoreSliceUpload。以上采用技巧式方案上实现了大文件分片处理,一些细节并未过多考虑,比如网络问题,中断问题。有些童鞋下载源码使用时所发现的文件合并被占用bug已解决并优化部分代码,借此实现希望对有需要的童鞋提供一点思考方向。

到此这篇关于.NET Core Web APi大文件分片上传研究实现的文章就介绍到这了,更多相关.NET Core Web APi分片上传内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • asp.net MVC实现无组件上传图片实例介绍

    例子: 如我想上传一个图片到服务器端:asp页面 复制代码 代码如下: <form id="form1" runat="server" action="/bookIndex/fileUpLoad/(你准备处理的 ActionResult)" method="post" enctype="multipart/form-data"> <input type="file" i

  • asp.net 文件上传实例汇总

    ASP.NET依托.net framework类库,封装了大量的功能,使得上传文件非常简单,主要有以下三种基本方法. 方法一:用Web控件FileUpload,上传到网站根目录. Test.aspx关键代码: 复制代码 代码如下: <form id="form1" runat="server">     <asp:FileUpload ID="FileUpload1" runat="server" />

  • asp.net下文件上传和文件删除的代码

    文件上传 HttpPostedFile postFile = Request.Files["imgFile"]; if(postFile.FileName!=String.Empty){        ex=postFile.FileName.Substring(postFile.FileName.LastIndexOf("."));        fileName= DateTime.Now.ToString("yyyyMMdd") + ex;

  • ASP.NET MVC实现图片上传、图片预览显示

    先看看效果(下面gif动画制作有点大,5.71MB): 题外话:上面选择图片来源于Insus.NET的新浪微博,言归正传,由于以前的asp.net mvc的练习文件上传文件,显示或是下载等博文,均是存储于站点目录之中.这次练习是把图片存储于数据库,也就是以图片的数据流存储,在上传时我们需要把文件处理为数据库,显示时,我们需要把数据流处理为文件. 一看上面的演示,我们还会看到一个预览区.选择图片时,预览区会预先显示选择图片.确认正确之后,我们再上传至数据库中. 使用下面SQL语句创建表[dbo].

  • Asp.Net 无刷新文件上传并显示进度条的实现方法及思路

    相信通过Asp.Net的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦苦来 实现呢?我并不否认"拿来主义",只是我个人更喜欢凡是求个所以然.本篇将阐述通过Html,IHttpHandler和 IHttpAsyncHandler实现文件上传和上传进度的原理,希望对你有多帮助. 效果图: 本文涉及到的知识点:1.前台用到Html,Ajax,JQuery,JQuery UI 2.后台用到一般处理程序(IHttpHandler)和

  • Asp.net实现MVC处理文件的上传下载功能实例教程

    上传于下载功能是程序设计中非常常见的一个功能,在ASP.NET程序开发中有着非常广泛的应用.本文就以实例形式来实现这一功能. 一.概述 如果你仅仅只有Asp.net Web Forms背景转而学习Asp.net MVC的,我想你的第一个经历或许是那些曾经让你的编程变得愉悦无比的服务端控件都驾鹤西去了.FileUpload就是其中一个,而这个控件的缺席给我们带来一些小问题.这篇文章主要说如何在Asp.net MVC中上传文件,然后如何再从服务器中把上传过的文件下载下来. 二.实现方法 1.文件上传

  • asp.net图片上传实例

    第一.图片上传,代码如下:xxx.aspx 复制代码 代码如下: <td class="style1">                 <asp:FileUpload ID="FileUpload1" runat="server"  />                <asp:Button ID="Button1" runat="server" Text="上传一

  • asp.net(C#)中上传大文件的几中常见应用方法

    几种常见的方法,本文主要内容包括: 第一部分:首先我们来说一下如何解决ASP.net中的文件上传大小限制的问题,我们知道在默认情况下ASP.NET的文件上传大小限制为2M,一般情况下,我们可以采用更改Web.Config文件来自定义最大文件大小,如下: 这样上传文件的最大值就变成了4M,但这样并不能让我们无限的扩大 MaxRequestLength的值,因为ASP.NET会将全部文件载入内存后,再加以处理.解决的方法是利用隐含的 HttpWorkerRequest,用它的GetPreloaded

  • .NET Core Web APi大文件分片上传研究实现

    前言 前两天发表利用FormData进行文件上传,然后有人问要是大文件几个G上传怎么搞,常见的不就是分片再搞下断点续传,动动手差不多也能搞出来,只不过要深入的话,考虑的东西还是很多.由于断点续传之前写个几篇,这里试试利用FormData来进行分片上传. .NET Core Web APi文件分片上传 这里我们依然是使用FormData来上传,只不过在上传之前对文件进行分片处理,如下HTML代码 <div class="form-horizontal" style="ma

  • Java超详细大文件分片上传代码

    目录 Java 大文件分片上传 首先是交互的控制器 上传文件分片参数接收 大文件分片上传服务类实现 文件分片上传定义公共服务类接口 文件分片上传文件操作接口实现类 OSS阿里云对象存储分片上传实现 京东云对象存储实现 腾讯云对象存储分片上传 分片上传前端代码实现 Java 大文件分片上传 原理:前端通过js读取文件,并将大文件按照指定大小拆分成多个分片,并且计算每个分片的MD5值.前端将每个分片分别上传到后端,后端在接收到文件之后验证当前分片的MD5值是否与上传的MD5一致,待所有分片上传完成之

  • webuploader在springMVC+jquery+Java开发环境下的大文件分片上传的实例代码

    注意: 1,webuploader上传组件会和jQuery自带的上传组件冲突,所以不要使用<form>标签中添加上传文件的属性; enctype="multipart/form-data" 2.并且屏蔽ApplicationContext-mvc.xml里面的拦截配置! <!-- 上传拦截,如最大上传值及最小上传值 --> <!--新增加的webuploader上传组件,必须要屏蔽这里的拦截机制 <bean id="multipartRes

  • Java实现浏览器端大文件分片上传

    目录 背景介绍 项目介绍 需要知识点 启动项目 项目示范 核心讲解 核心原理 功能分析 分块上传 秒传功能 断点续传 总结 参考文献 背景介绍   Breakpoint-http,是不是觉得这个名字有点low,break point断点.这是一个大文件上传的一种实现.因为本来很久没写过前端了,本来想自己好好写一番js,可惜因为种种原因而作罢了.该项目是基于一款百度开源的前端上传控件:WebUploader(百度开源的东西文档一如既往的差,哈哈.或者是我理解能力差).   Breakpoint-h

  • vue 大文件分片上传(断点续传、并发上传、秒传)

    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送.接收都是不可取,很容易导致内存问题.所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率. 本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章: springboot 大文件上传.分片上传.断点续传.秒传 上传分步: 本人分析上传总共分为: MD5读取文件,获取文件的

  • 前端使用koa实现大文件分片上传

    目录 引言 前端 拆分上传的文件流 后端 接收文件片段 合并文件片段 总结 引言 一个文件资源服务器,很多时候需要保存的不只是图片,文本之类的体积相对较小的文件,有时候,也会需要保存音视频之类的大文件.在上传这些大文件的时候,我们不可能一次性将这些文件数据全部发送,网络带宽很多时候不允许我们这么做,而且这样也极度浪费网络资源. 因此,对于这些大文件的上传,往往会考虑用到分片传输. 分片传输,顾名思义,也就是将文件拆分成若干个文件片段,然后一个片段一个片段的上传,服务器也一个片段一个片段的接收,最

  • PHP大文件分片上传的实现方法

    一.前言 在网站开发中,经常会有上传文件的需求,有的文件size太大直接上传,经常会导致上传过程中耗时太久,大量占用带宽资源,因此有了分片上传. 分片上传主要是前端将一个较大的文件分成等分的几片,标识当前分片是第几片和总共几片,待所有的分片均上传成功的时候,在后台进行合成文件即可. 二.开发过程中遇到的问题 分片的时候每片该分多大size?太大会出现"413 request entity too large" 分片上传的时候并不是严格按照分片的序号顺序上传,如何判断所有的分片均上传成功

  • JavaScript实现大文件分片上传处理

    很多时候我们在处理文件上传时,如视频文件,小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题: 1.文件过大,超出服务端的请求大小限制: 2.请求时间过长,请求超时: 3.传输中断,必须重新上传导致前功尽弃 这些问题很影响用户的体验感,所以下面介绍一种基于原生JavaScript进行文件分片处理上传的方案,具体实现过程如下: 1.通过dom获取文件对象,并且对文件进行MD5加密(文件内容+文件标题形式),采用SparkMD5进行文件加密: 2.进行分片设置,文件Fil

  • React+Node实现大文件分片上传、断点续传秒传思路

    目录 1.整体思路 2.实现步骤 2.1 文件切片加密 2.2 查询上传文件状态 2.3 秒传 2.4 上传分片.断点续传 2.5 合成分片还原完整文件 3.总结 4.后续扩展与思考 5.源码 1.整体思路 将文件切成多个小的文件: 将切片并行上传: 所有切片上传完成后,服务器端进行切片合成: 当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分实现断点续传: 当切片合成为完整的文件,通知客户端上传成功: 已经传到服务器的完整文件,则不需要重新上传到服务器,实现秒传功能: 2.实现步骤

  • 利用Vue3+Element-plus实现大文件分片上传组件

    目录 一.背景 二.技术栈 三.核心代码实现 四.总结 一.背景 实际项目中遇到需要上传几十个G的3d模型文件,传统上传就不适用了. 结合element提供的上传组件自己封装了文件分片上传的组件. 思路: 把文件拆分成若干分片 依次上传分片(每次上传前可校验该分片是否已经上传) 发起合并分片的请求 二.技术栈 Vue3+Ts+Element-Plus 其他库:spark-md5 后端接口: 上传分片接口 校验分片是否已上传接口 合并分片接口 三.核心代码实现 Element组件基础配置 <el-

随机推荐