前言

前两天发表利用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. DirectX11 With Windows SDK--33 曲面细分阶段(Tessellation)

    前言 曲面细分是Direct3D 11带来的其中一项重要的新功能.它引入了两个可编程着色器阶段以及一个固定的镶嵌处理过程.简单来说,曲面细分技术可以将几何体细分为更小的三角形,并以某种方式把这些新生成 ...

  2. c++运行程序 改变字和背景的颜色与窗口大小和位置 (c++)(windows)

    关于改变字体的颜色和背景颜色: 在#include <windows.h> 库里 0=黑色 1=蓝色 2=绿色 3=湖蓝色 4=红色 5=紫色 6=黄色 7=白色 8=灰色 9=淡蓝色 A ...

  3. 小白从零开始阿里云部署react项目+node服务接口(三:部署到服务器)

    服务器 准备工具 依次安装即可 nginx 安装nginx https://www.runoob.com/linux/nginx-install-setup.html 配置全局nginx命令 http ...

  4. Oracle Database Tools

    The following are some products, tools, and utilities you can use to achieve your goals as a databas ...

  5. Intelij DataGrip 的安装和使用

    链接: Intelij DataGrip 安装教程以及汉化教程 Intelij DataGrip 使用教程 以上两个教程已使用过,没有问题 如有侵权请联系删除

  6. Linux文件搜索

    一.whereis及which命令 这两个命令用来搜索命令的路径(也遵循/etc/updatedb.conf配置文件的筛选规则) whereis 命令名                        ...

  7. HashMap源码实现分析

    HashMap源码实现分析 一.前言 HashMap 顾名思义,就是用hash表的原理实现的Map接口容器对象,那什么又是hash表呢. 我们对数组都很熟悉,数组是一个占用连续内存的数据结构,学过C的 ...

  8. 06 . ELK Stack + kafka集群

    简介 Filebeat用于收集本地文件的日志数据. 它作为服务器上的代理安装,Filebeat监视日志目录或特定的日志文件,尾部文件,并将它们转发到Elasticsearch或Logstash进行索引 ...

  9. 【Gin-API系列】需求设计和功能规划(一)

    场景需求 数据库存储2个模型,每个模型都有一个或多个IP字段,需要通过 Golang Http Api(Restful Api) 返回 IP 信息. 模型1 - 服务器 ID 主机名 IP 内存大小 ...

  10. Python while 中简单的语句组

    Python while 中简单的语句组: 只使用 while: # 简单的语句组 a = 4 b = 8 num = 0 while a < b: print("a 比 b 小&qu ...