前言

前两天发表利用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) >= ;
} 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 >= && boundary[] == '"' &&
boundary[boundary.Length - ] == '"')
{
boundary = boundary.Substring(, boundary.Length - );
}
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, , buffer.Length);
stream.Write(buffer, , bytesRead); } while (bytesRead > );
} 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(, 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(, file.IndexOf(partToken)); var fileIndex = file.Substring(file.IndexOf(partToken) + partToken.Length); int.TryParse(fileIndex, out var number);
if (number <= ) { 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);
}
}

总结

以上基本上实现了大文件分片处理,一些细节并未过多考虑,比如网络问题,以及文件由于采取异步上传,若我们通过计算所有文件大小和URL参数文件大小进行比对这会有问题,因为此时可能文件流处于缓冲区内还未持久化到磁盘,借此实现希望对有需要的童鞋提供一点思考方向。

.NET Core Web APi大文件分片上传研究的更多相关文章

  1. java springboot 大文件分片上传处理

    参考自:https://blog.csdn.net/u014150463/article/details/74044467 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时 ...

  2. nodeJs + js 大文件分片上传

    简单的文件上传 一.准备文件上传的条件: 1.安装nodejs环境 2.安装vue环境 3.验证环境是否安装成功 二.实现上传步骤 1.前端部分使用 vue-cli 脚手架,搭建一个 demo 版本, ...

  3. Webuploader 大文件分片上传

    百度Webuploader 大文件分片上传(.net接收)   前阵子要做个大文件上传的功能,找来找去发现Webuploader还不错,关于她的介绍我就不再赘述. 动手前,在园子里找到了一篇不错的分片 ...

  4. vue+大文件分片上传

    最近公司在使用vue做工程项目,实现大文件分片上传. 网上找了一天,发现网上很多代码都存在很多问题,最后终于找到了一个符合要求的项目. 工程如下: 对项目的大文件上传功能做出分析,怎么实现大文件分片上 ...

  5. iOS大文件分片上传和断点续传

    总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件 ...

  6. js实现大文件分片上传的方法

    借助js的Blob对象FormData对象可以实现大文件分片上传的功能,关于Blob和FormData的具体使用方法可以到如下地址去查看FormData 对象的使用Blob 对象的使用以下是实现代码, ...

  7. Node + js实现大文件分片上传基本原理及实践(一)

    _ 阅读目录 一:什么是分片上传? 二:理解Blob对象中的slice方法对文件进行分割及其他知识点 三. 使用 spark-md5 生成 md5文件 四. 使用koa+js实现大文件分片上传实践 回 ...

  8. thinkphp+webuploader实现大文件分片上传

    大文件分片上传,简单来说就是把大文件切分为小文件,然后再一个一个的上传,到最后由这些小文件再合并成原来的文件 webuploader下载地址及其文档:http://fex.baidu.com/webu ...

  9. 在React中使用WebUploader实现大文件分片上传的踩坑日记!

    前段时间公司项目有个大文件分片上传的需求,项目是用React写的,大文件分片上传这个功能使用了WebUploader这个组件. 具体交互是: 1. 点击上传文件button后出现弹窗,弹窗内有选择文件 ...

随机推荐

  1. Kafka 信息整理

    请说明什么是传统的消息传递方法? 传统的消息传递方法包括两种: ·排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人. ·发布-订阅:在这个模型中,消息被广播给所有的用户. 为 ...

  2. springboot使用maven命令打包jar及配置文件配置

    sspringboot项目如果不想每次修改配置文件就要重新打包jar的话,可以进行以下配置进行打包 1.在resources下新建assembly文件夹package.xml <?xml ver ...

  3. 跳过Google开机设置/验证/向导

    Google 的开机设置向导,亦或称作开机验证,对于刷机党来说最熟悉不过了.一般情况下,刷类原生或是原生系统,再刷 Gapps,开机就需要进行一些 Google 验证.这些验证,与国内的手机厂商所设置 ...

  4. Angular 的前世今生

    目录 序言 AngularJS 简介 Angular 2.0 的动机 现如今的 Angular Angular 的核心概念 参考 序言 Angular 一般意义上是指 Angular v2 及以上版本 ...

  5. 做软件测试要月入20k?听听腾讯大牛怎么说

    作者:兰色链接:https://www.zhihu.com/question/373819487/answer/1183309514来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  6. MVC + EFCore 项目实战 - 数仓管理系统7 - 数据源管理中--新增数据源

    上篇我们完成了数据源列表展示功能(还未测试). 本篇我们来新增数据源,并查看列表展示功能.   接上篇: 二.数据源管理功能开发 2.新增数据源 我们用模态对话框来完成数据源的新增,效果如下图: 我们 ...

  7. git的核心命令使用和底层原理解析

    文章目录: GIT体系概述 GIT 核心命令使用 GIT 底层原理 一.GIT体系概述 GIT 与 svn 主要区别: 存储方式不一样 使用方式不一样 管理模式不一样 1.存储方式区别 GIT把内容按 ...

  8. SQL语法入门

    SQL语句概述 ·SQL定义:是一种特定目的编程语言,用于管理关系数据库 ·GaussDB T是一种关系数据库,SQL语句包括 1.DDL 数据定义语言,用于定义或修改数据库中的对象(表,视图,序列, ...

  9. php imap 那些坑

    今天调试php 接收邮件,遇见的几大坑! 第一,返回错误 关键字imap_open返回flase 原来{{$mailServer}:143}INBOX  的端口,不是根据outlook给的,,,,,是 ...

  10. JPA第二天

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"springdata"获取视频和教程资料! b站在线视 ...