.NetCore+WebUploader实现大文件分片上传
项目要求通过网站上传大文件,比如视频文件,通过摸索实现了文件分片来上传,然后后台进行合并。
使用了开源的前台上传插件WebUploader(http://fex.baidu.com/webuploader/)
WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。
采用大文件分片并发上传,极大的提高了文件上传效率。
最新升级版请访问:https://www.cnblogs.com/wdw984/p/14702514.html
直接上代码,前台cshtml
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <link href="~/js/webuploader/webuploader.css" rel="stylesheet" /> <link href="~/js/bootstrap.min.css" rel="stylesheet" /> <script src="~/js/jquery.min.js"></script> <script src="~/js/webuploader/webuploader.js"></script> <title>Upload</title> <script> jQuery(function () { var $ = jQuery, $list = $('#thelist'), $btn = $('#ctlBtn'), state = 'pending', fileMd5, flag = true, dataState, fm = [], fnum, Token, uploader; var FileExt = ["mpg", "mpeg", "mp4", "avi"]; Token = '@ViewBag.Token'; if (Token == '' || Token== 'undefined') { $("#uploader").hide(); alert("登录超时,请重新登录。"); } //监听分块上传过程中的三个时间点 WebUploader.Uploader.register({ "before-send-file": "beforeSendFile", "before-send": "beforeSend", "after-send-file": "afterSendFile", }, { beforeSendFile: function (file) { var startTime = new Date(file.lastModifiedDate); fileName = file.name; var deferred = WebUploader.Deferred(); (new WebUploader.Uploader()).md5File(file, 0, 10 * 1024 * 1024) .progress(function (percentage) { console.log("正在读取文件"); }) .then(function (val) { fileMd5 = val; fm.push(fileMd5); deferred.resolve(); }); return deferred.promise(); }, //时间点2:如果有分块上传,则每个分块上传之前调用此函数 beforeSend: function (block) { var deferred = WebUploader.Deferred(); //上传前ajax检测一下此文件块是否已经上传 this.owner.options.formData.fileMd5 = fileMd5; this.owner.options.formData.chunk = block.chunk; deferred.resolve(); return deferred.promise(); }, //时间点3:所有分块上传成功后调用此函数 afterSendFile: function (file) { var deferred = $.Deferred(); $('#' + file.id).find('p.state').text('执行最后一步'); console.log(file); console.log(file.guid); $.ajax({ type: "POST", url: "/api/v1/Check/FileMerge", data: { guid: file.guid, fileMd5: fm[fnum], fileName: file.name }, cache: false, async: false, success: function (response) { fnum++; console.log(response); if (response.success == true) { dataState = response; flag = true; } else { flag = false; } deferred.resolve(); }, error: function () { fnum++; dataState = undefined; flag = false; deferred.reject(); } }); return deferred.promise(); } }); uploader = WebUploader.create({ resize: false, fileNumLimit: 10, swf: '/js/Uploader.swf', server: '/api/v1/Check/FileSave', pick: '#picker', chunked: true, chunkSize: 10 * 1024 * 1024, chunkRetry: 5 //, formData: { // guid: GUID //} }); uploader.on('beforeFileQueued', function (file) { var isAdd = false; for (var i = 0; i < FileExt.length; i++) { if (file.ext == FileExt[i]) { file.guid = WebUploader.Base.guid(); isAdd = true; break; } } return isAdd; }); uploader.on('uploadBeforeSend', function (object, data, headers) { //console.log(object); headers.Authorization =Token; data.guid = object.file.guid; }); // 当有文件添加进来的时候 uploader.on('fileQueued', function (file) { $list.append('<div id="' + file.id + '" class="item">' + '<h4 class="info">' + file.name + '</h4>' + '<input type="hidden" id="h_' + file.id + '" value="' + file.guid + '" />' + '<p class="state">等待上传...</p>' + '</div>'); }); // 文件上传过程中创建进度条实时显示。 uploader.on('uploadProgress', function (file, percentage) { var $li = $('#' + file.id), $percent = $li.find('.progress .progress-bar'); // 避免重复创建 if (!$percent.length) { $percent = $('<div class="progress progress-striped active">' + '<div class="progress-bar" role="progressbar" style="width: 0%">' + '</div>' + '</div>').appendTo($li).find('.progress-bar'); } $li.find('p.state').text('上传中'); $percent.css('width', percentage * 100 + '%'); }); uploader.on('uploadSuccess', function (file) { if (dataState == undefined) { $('#' + file.id).find('p.state').text('上传失败'); $('#' + file.id).find('button').remove(); $('#' + file.id).find('p.state').before('<button id="retry" type="button" class="btn btn-primary fright retry pbtn">重新上传</button>'); flag = false; file.setStatus('error'); } if (dataState.success == true) { $('#' + file.id).find('p.state').text('已上传'); $('#' + file.id).find('button').remove(); } else { $('#' + file.id).find('p.state').text('上传失败'); flag = false; } }); uploader.on('uploadError', function (file) { $('#' + file.id).find('p.state').text('上传出错'); }); uploader.on('uploadComplete', function (file) { $('#' + file.id).find('.progress').fadeOut(); }); uploader.on('all', function (type) { if (type === 'startUpload') { state = 'uploading'; } else if (type === 'stopUpload') { state = 'paused'; } else if (type === 'uploadFinished') { state = 'done'; } if (state === 'done') { $btn.text('继续上传'); } else if (state === 'uploading') { $btn.text('暂停上传'); } else { $btn.text('开始上传'); } }); $btn.on('click', function () { if (state === 'uploading') { uploader.stop(); } else if (state == 'done') { window.location.reload(); } else { uploader.upload(); } }); }); </script> </head> <body> <div id="uploader" class="wu-example"> <span style="color:red">只能上传mpg、mpeg、mp4、avi格式的视频文件</span> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="picker" class="webuploader-container"><div class="webuploader-pick">选择文件</div><div style="position: absolute; top: 0px; left: 0px; width: 88px; height: 34px; overflow: hidden; bottom: auto; right: auto;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple"><label style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);"></label></div></div> <button id="ctlBtn" class="btn btn-default">开始上传</button> </div> </div> </body> </html>
后台代码:
#region 上传视频 public IActionResult Upload() { ViewBag.Token = HttpContext.Request.Headers["Authorization"];//获取认证信息,传递给前台,方便Ajax请求时提供 return View(); } /// <summary> /// 上传文件 /// </summary> /// <returns></returns> [HttpPost] public async Task<IActionResult> FileSave() { var date = Request; var files = Request.Form.Files; long size = files.Sum(f => f.Length); foreach (var formFile in files) { if (formFile.Length > 0) { string fileExt = formFile.FileName.Substring(formFile.FileName.IndexOf('.')); //文件扩展名,不含“.” long fileSize = formFile.Length; //获得文件大小,以字节为单位 //string newFileName = Guid.NewGuid().ToString() + "." + fileExt; //随机生成新的文件名 string DirPath = Path.Combine(_uploadConfig.TmpPath, Request.Form["guid"]); if (!Directory.Exists(DirPath)) { Directory.CreateDirectory(DirPath); } var filePath = DirPath + "/" + Request.Form["chunk"] + fileExt; using (var stream = new FileStream(filePath, FileMode.Create)) { await formFile.CopyToAsync(stream); } } } return Ok(new { count = files.Count, size }); } /// <summary> /// 合并请求 /// </summary> /// <returns></returns> [HttpPost] public async Task<IActionResult> FileMerge() { bool ok = false; string errmsg = ""; try { var temporary = Path.Combine(_uploadConfig.TmpPath, Request.Form["guid"]);//临时文件夹 string fileName = Request.Form["fileName"];//文件名 string fileExt = Path.GetExtension(fileName);//获取文件后缀 var files = Directory.GetFiles(temporary);//获得下面的所有文件 var finalFilePath = Path.Combine(_uploadConfig.UpLoadPath + fileName);//最终的文件名 //var fs = new FileStream(finalFilePath, FileMode.Create); using (var fs = new FileStream(finalFilePath, FileMode.Create)) { foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x)) { var bytes = System.IO.File.ReadAllBytes(part); await fs.WriteAsync(bytes, 0, bytes.Length); bytes = null; System.IO.File.Delete(part);//删除分块 } Directory.Delete(temporary);//删除文件夹 ok = true; } } catch (Exception ex) { ok = false; errmsg = ex.Message; log4net.Error(errmsg); } if (ok) { return Ok(new { success = true, msg = "" }); } else { return Ok(new { success = false, msg = errmsg }); ; } } #endregion