大文件分段上传以及续传、秒传功能实现
13
2021-02-22
dotNet在上传方面,默认上限是30M,即超过30M的文件上传,你需要同时修改Web服务组件的配置和dotNet自身的上传限制,才允许上传更多的。
此外,对于超大文件上传,即时是修改配置,也会有很多疑难问题,因为这里讲解一下如何在DotNet上使用大文件分段上传,以及续传、秒传功能的实现。
上传后台部分(为表达顺序逻辑,代码未进行封装处理)
FileModel file;
//第一个分段文件,创建基础文件信息
if (model.Index == 0)
{
file = new FileModel
{
Name = model.FileName,
Code = Guid.NewGuid().ToString("N"),
Extension = Path.GetExtension(model.FileName),
Size = model.Size,
SHA256 = model.SHA256?.ToUpper(),
Status = "L"
};
//加入到数据库
file = Add(file);
if (file != null)
{
model.Code = file.Code;
}
else
{
return (0, "保存文件信息出错", null, true);
}
}
else
{
//从数据库查询文件信息
file = Query<FileModel>(model.Code);
}
//保存文件信息
var localFileName = file.AbsolutePath + $"{model.Index.ToString().PadLeft(model.Count.ToString().Length, '0')}";
var saveResult = await model.File.SaveAsync(localFileName, true, true);
if (!saveResult.status)
return (0, saveResult.msg, null, true);
//最后一个,合并文件
if (model.Count == model.Index + 1)
{
var folder = Path.GetDirectoryName(file.AbsolutePath);
var baseFileName = Path.GetFileName(file.AbsolutePath);
var files = new DirectoryInfo(folder).GetFiles().Where(t => t.Name.StartsWith(baseFileName)).OrderBy(t => t.Name).ToList();
//强制删除同名文件
if (File.Exists(file.AbsolutePath))
File.Delete(file.AbsolutePath);
//合并文件
using var fs = new FileStream(file.AbsolutePath, FileMode.Create);
foreach (var part in files)
{
var reader = new FileStream(part.FullName, FileMode.Open);
var bytes = new byte[reader.Length];
reader.Read(bytes, 0, (int)reader.Length);
reader.Close();
await fs.WriteAsync(bytes, 0, bytes.Length);
}
//校验SHA256
using (SHA256 mySHA256 = SHA256.Create())
{
try
{
using FileStream fileStream = new FileStream(file.AbsolutePath, FileMode.Open);
fileStream.Position = 0;
byte[] hashValue = mySHA256.ComputeHash(fileStream);
var enText = new StringBuilder();
foreach (byte encryptbyte in hashValue)
{
enText.AppendFormat("{0:x2}", encryptbyte);
}
var sha256 = enText.ToString().ToUpper();
if (sha256 == file.SHA256?.ToUpper())
{
//删除分段文件
foreach (var item in files)
{
item.Delete();
}
return (1, "成功", file, true);
}
return (0, $"校验SHA256不一致({sha256})", null, true);
}
catch (IOException e)
{
return (0, $"IO异常,{e.Message}", null, true);
}
catch (UnauthorizedAccessException e)
{
return (0, $"读写权限异常,{e.Message}", null, true);
}
}
}
//数据库同步上传状态index
......
return (1, "成功", file, false);
上传前后台检查部分
var file = XSharp.Queryable<FileModel>().FirstOrDefault(t => t.SHA256 == sha256);
if (file == null || file.Status != "L")
return file;
if (上传者是上次同一个人)
{
var folder = Path.GetDirectoryName(file.AbsolutePath);
var baseFileName = Path.GetFileName(file.AbsolutePath);
//检查历史是否完整,并删除记录索引之后的未完成文件
var count = 0;
foreach (var item in new DirectoryInfo(folder).GetFiles().Where(t => t.Name.StartsWith(baseFileName)))
{
var name = item.Name.Replace(baseFileName, "").ToInt();
if (file.Index <= name)
item.Delete();
else
count++;
}
if (file.Index == count)
return file;
else
{
foreach (var item in new DirectoryInfo(folder).GetFiles().Where(t => t.Name.StartsWith(baseFileName)))
{
item.Delete();
}
}
}
return null;
前端代码
3.1.需要的依赖
CryptoJS
sha256
3.2.代码部分(因为懒,未进行有效封装)
function sliceUpload() {
element.render();
//注册上传
upload.render({
elem: '#uploadFile',
url: 'api',
accept: 'file',
auto: false,
choose: function (obj) {
//预读本地文件,如果是多文件,则会遍历。(不支持ie8/9)
obj.preview(function (index, file, result) {
loadFile(file, (progress) => {
//进度
progress = (progress * 0.3 + 10.00).toFixed(2);
}, (sha256) => {
//对比文件hash
admin.req(api.fileCheck(), { sha256: sha256 }, function (dataResult) {
if (0 === dataResult.errcode && dataResult.data && dataResult.data.status == "A") {
layer.msg("文件秒传成功", { icon: 1 });
element.progress('uploadFileProgress', '100%');
$('#uploadFile').attr('fileId', dataResult.data.id);
} else {
//切片上传
doSliceUpload(file, sha256, (dataResult.data ? dataResult.data.index : 0), (dataResult.data ? dataResult.data.code : 0), (progress) => {
progress = (progress * 0.5 + 50.00).toFixed(2);
}, (result) => {
if (result && result.result) {
$('#uploadFile').attr('fileId', result.data.data.id);
layer.msg("文件上传成功", { icon: 1 });
}
else {
layer.msg("文件上传失败,请重试", { icon: 2 });
}
});
}
}, 'post');
});
//obj.upload(index, file); //对上传失败的单个文件重新上传
});
}
});
}
function loadFile(contractFile, progress, complete) {
var reader = new FileReader();
var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
// 指定文件分块大小(2M)
var chunkSize = 2 * 1024 * 1024;
// 计算文件分块总数
var chunks = Math.ceil(contractFile.size / chunkSize);
// 指定当前块指针
var currentChunk = 0;
// 创建SHA256
var hasher = CryptoJS.algo.SHA256.create();
hasher.reset();
// FileReader分片式读取文件
// 计算开始读取的位置
var start = currentChunk * chunkSize;
// 计算结束读取的位置
var end = start + chunkSize >= contractFile.size ? contractFile.size : start + chunkSize;
reader.readAsArrayBuffer(blobSlice.call(contractFile, start, end));
reader.onload = function (evt) {
var tmpWordArray = CryptoJS.lib.WordArray.create(evt.target.result);
hasher.update(tmpWordArray);
progress((currentChunk * chunkSize * 100 / (contractFile.size * 1.00)).toFixed(2) * 1.00);
currentChunk += 1;
tmpWordArray = null;
// 判断文件是否都已经读取完
if (currentChunk < chunks) {
// 计算开始读取的位置
var start = currentChunk * chunkSize;
// 计算结束读取的位置
var end = start + chunkSize >= contractFile.size ? contractFile.size : start + chunkSize;
reader.readAsArrayBuffer(blobSlice.call(contractFile, start, end));
}
};
reader.onloadend = function () {
if (currentChunk >= chunks) {
progress(100.00);
var hash = hasher.finalize();
complete(hash.toString());
hasher = null;
blobSlice = null;
reader = null;
hash = null;
}
};
}
function doSliceUpload(file, sha256, index, code, progress, complete) {
var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
// 指定文件分块大小(4M)
var chunkSize = 4 * 1024 * 1024;
// 计算文件分块总数
var chunks = Math.ceil(file.size / chunkSize);
// 计算开始读取的位置
var currentChunk = 0;
var start = currentChunk * chunkSize;
// 计算结束读取的位置
var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
progress(0.00);
var formData = new FormData();
formData.append("file", blobSlice.call(file, start, end));
formData.append("fileName", file.name);
formData.append("code", code);
formData.append("index", currentChunk);
formData.append("SHA256", sha256);
formData.append("size", file.size);
formData.append("count", chunks);
sliceUploadRequest(file, blobSlice, index, formData, currentChunk, chunkSize, chunks, progress, complete);
}
function sliceUploadRequest(file, blobSlice, index, formData, currentChunk, chunkSize, chunks, progress, complete) {
if (index > currentChunk) {
currentChunk++;
// 计算开始读取的位置
var start = currentChunk * chunkSize;
// 计算结束读取的位置
var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
progress((currentChunk * chunkSize * 100 / (file.size * 1.00)).toFixed(2) * 1.00);
formData.set('index', currentChunk);
formData.set('file', blobSlice.call(file, start, end));
sliceUploadRequest(file, blobSlice, index, formData, currentChunk, chunkSize, chunks, progress, complete);
return;
}
var loadUpload = new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: api.sliceUpload(),
data: formData,
contentType: false,
processData: false,
async: true,
dataType: "json",
beforeSend: function (request) {
},
success: function (dataResult) {
if (dataResult && dataResult.data) {
formData.set('code', dataResult.data.code);
resolve({ result: true, data: dataResult });
} else {
resolve({ result: false });
}
},
error: function (xhr, status, errorData) {
resolve({ result: false });
}
});
});
loadUpload.then((result) => {
currentChunk++;
if (currentChunk >= chunks) {
complete(result);
} else {
// 计算开始读取的位置
var start = currentChunk * chunkSize;
// 计算结束读取的位置
var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
progress((currentChunk * chunkSize * 100 / (file.size * 1.00)).toFixed(2) * 1.00);
formData.set('index', currentChunk);
formData.set('file', blobSlice.call(file, start, end));
sliceUploadRequest(file, blobSlice, index, formData, currentChunk, chunkSize, chunks, progress, complete);
}
});
}
- 0
- 0
-
分享