后端控制器:
//用于保存的文件夹
static readonly string uploadFolder = "UploadFolder";
//目录分隔符,兼容不同系统
static readonly char dirSeparator = Path.DirectorySeparatorChar;
string GetTmpChunkDir(string fileName) => HttpContext.Session.TryGetValue(fileName, out byte[] bytes) ? Encoding.Default.GetString(bytes) : "";
//[UnifyResult(typeof(JsonResult))]
//保存文件
[HttpPost]
public async Task<JsonResult> SaveChunkFile(IFormFile chunk, string fileName, int chunkIndex, int chunkCount)
{
try
{
if (chunk.Length == 0)
{
return Json(new {
success = false,
msg = "File Length 0",
});
}
if (chunkIndex == 0)
{
//第一次上传时,生成一个随机id,做为保存块的临时文件夹,记录到session
HttpContext.Session.Set(fileName, Encoding.Default.GetBytes(Guid.NewGuid().ToString("N")));
}
if (!Directory.Exists(uploadFolder))
Directory.CreateDirectory(uploadFolder);
var fullChunkDir = uploadFolder + dirSeparator + GetTmpChunkDir(fileName);
if (!Directory.Exists(fullChunkDir))
Directory.CreateDirectory(fullChunkDir);
var blob = chunk.FileName;
var newFileName = blob + chunkIndex + Path.GetExtension(fileName);
var filePath = fullChunkDir + Path.DirectorySeparatorChar + newFileName;
//保存文件块
using (var stream = new FileStream(filePath, FileMode.Create))
{
await chunk.CopyToAsync(stream);
}
//所有块上传完成
if (chunkIndex == chunkCount - 1)
{
//也可以在这合并,在这合并就不用ajax调用CombineChunkFile合并
//CombineChunkFile(fileName);
}
var obj = new {
success = true,
date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
newFileName,
originalFileName = fileName,
size = chunk.Length,
nextIndex = chunkIndex + 1,
};
return Json(obj);
}
catch (Exception ex)
{
return Json(new {
success = false,
msg = ex.Message,
});
}
}
[UnifyResult(typeof(JsonResult))]
//合并文件
public async Task<JsonResult> CombineChunkFile(string fileName)
{
try
{
return await Task.Run(() =>
{
var tmpDir = GetTmpChunkDir(fileName);
var fullChunkDir = uploadFolder + dirSeparator + tmpDir;
var beginTime = DateTime.Now;
var newFileName = tmpDir + Path.GetExtension(fileName);
var destFile = uploadFolder + dirSeparator + newFileName;
//获取临时文件夹内的所有文件块,排好序
var files = Directory.GetFiles(fullChunkDir).OrderBy(x => x.Length).ThenBy(x => x).ToList();
using (var destStream = System.IO.File.OpenWrite(destFile))
{
files.ForEach(chunk =>
{
using (var chunkStream = System.IO.File.OpenRead(chunk))
{
chunkStream.CopyTo(destStream);
}
System.IO.File.Delete(chunk);
});
Directory.Delete(fullChunkDir);
}
var totalTime = DateTime.Now.Subtract(beginTime).TotalSeconds;
return Json(new {
success = true,
destFile = destFile.Replace('\\', '/'),
msg = $"combine completed ! {totalTime} s",
});
});
}
catch (Exception ex)
{
return Json(new {
success = false,
msg = ex.Message,
});
}
finally
{
HttpContext.Session.Remove(fileName);
}
}
本人用的百小僧MVC框架
前端视图代码:
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<div>
<input type="file" id="file1" value="" />
<input type="button" id="btnUplaod" value="Upload" multiple="multiple" />
</div>
<div id="completedChunks"></div>
<div id="percent">0%</div>
<div id="progress" style="width:200px;height:10px;background:linear-gradient(45deg, #ff0084 0%, #e8c5d7 0%);"></div>
@section Scripts{
<script src="~/lib/vue/axios.min.js"></script>
<script>
//important
//slice()函数要IE10+才支持。
//Firefox 12及更早版本的使用mozSlice()
//Safari使用webkitSlice()
$(function () {
{
var pause = false;//是否暂停
var $file;
var $fileInput;//file input
var $completedChunks = $('#completedChunks');//上传完成块数
var $progress = $('#progress');//上传进度条
var $percent = $('#percent');//上传百分比
var MiB = 1024 * 1024;
var chunkSize = 8.56 * MiB;//xx MiB
var chunkIndex = 0;//上传到的块
var $btnUpload = $('#btnUplaod');
var totalSize;//文件总大小
var totalSizeH;//文件总大小M
var chunkCount;//分块数
var fileName;//文件名
$btnUpload.click(function () {
var val = $.trim($(this).val());
if (val === 'Upload') {
$fileInput = $('#file1');
$file = $fileInput[0].files[0];
if ($file === undefined) {
$completedChunks.html('please select a file !');
return false;
}
totalSize = $file.size;
chunkCount = Math.ceil(totalSize / chunkSize * 1.0);
totalSizeH = (totalSize / MiB).toFixed(2);
fileName = $file.name;
val = 'Pause';
pause = false;
chunkIndex = 0;
}
else if (val === 'Pause') {
val = 'Resume';
pause = true;
}
else if (val === 'Resume') {
val = 'Pause';
pause = false;
}
else {
val = '-';
}
$(this).val(val);
postChunk();
});
function postChunk() {
if (pause)
return false;
var isLastChunk = chunkIndex === chunkCount - 1;
var fromSize = chunkIndex * chunkSize;
var chunk = !isLastChunk ? $file.slice(fromSize, fromSize + chunkSize) : $file.slice(fromSize, totalSize);
var fd = new FormData();
fd.append('chunk', chunk);
fd.append('chunkIndex', chunkIndex);
fd.append('chunkCount', chunkCount);
fd.append('fileName', fileName);
fd.append('__RequestVerificationToken', $("input[name=__RequestVerificationToken]").val());
$.ajax({
url: '/HtAdmin/Upload/SaveChunkFile',
type: 'POST',
data: fd,
cache: false,
contentType: false,
processData: false,
success: function (d) {
if (!d.success) {
$completedChunks.html(d.msg);
return false;
}
chunkIndex = d.nextIndex;
if (isLastChunk) {
$completedChunks.html('combining .. ');
$btnUpload.val('Upload').prop('disabled', true);
//合并文件
$.post('/HtAdmin/Upload/CombineChunkFile', { fileName: fileName }, function (d) {
$completedChunks.html(d.msg);
$completedChunks.append('destFile: ' + d.destFile);
$btnUpload.val('Upload').prop('disabled', false);
$fileInput.val('');//清除文件
});
}
else {
postChunk();//递归上传文件块
//$completedChunks.html(chunkIndex + '/' + chunkCount );
$completedChunks.html((chunkIndex * chunkSize / MiB).toFixed(2) + 'M/' + totalSizeH + 'M');
}
var completed = chunkIndex / chunkCount * 100;
$percent.html(completed.toFixed(2) + '%').css('margin-left', parseInt(completed / 100 * $progress.width()) + 'px');
$progress.css('background', 'linear-gradient(to right, #ff0084 ' + completed + '%, #e8c5d7 ' + completed + '%)');
},
error: function (ex) {
$completedChunks.html('ex:' + ex.responseText);
}
});
}
}
});
</script>
}
</div>