参考代码:https://blog.csdn.net/tangyan1207/article/details/74937645javascript
解决了FileReader不支持readAsBinaryString方法的问题, 解决了MD5相同的问题css
HTML+JS部分:html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width" /> <title>大文件分块上传</title> <style type="text/css"> * { font-family: "微软雅黑"; margin: 0; padding: 0; } .container { padding-top: 10px; padding-left: 10px; } .container input { width: 120px; height: 30px; background-color: blue; color: white; border: 0; line-height: 30px; border-radius: 5px; margin-right: 5px; outline: none; cursor: pointer; } #filelist { width: 800px; border: solid 1px #eee; border-collapse: collapse; margin: 10px; } #filelist td { border-bottom: solid 1px #eee; height: 30px; font-size: 12px; /*line-height:30px ;*/ padding: 0 3px; } .filename { width: 200px; text-align: center; } .fileselectchk { width: 100px; text-align: center; } .filestatus { width: 100px; text-align: center; } .fileprogress { text-align: center; } .domprogress { width: 320px; } .domsize { display: block; } #tdmsg { text-align: center; } #fileselect { display: none; } span.domtime { display: block; } </style> </head> <body> <div class="container"> <input type="file" name="fileselect" id="fileselect" value="" multiple /> <input type="button" id="btnselect" value="选择上传的文件" /> <input type="button" id="btnupload" value="开始上传" /> </div> <table cellspacing="0" cellpadding="0" id="filelist"> <tr> <td class="fileselectchk">选择</td> <td class="filename">文件名</td> <td class="fileprogress">进度</td> <td class="filestatus">状态</td> </tr> </table> <script src="~/Scripts/jquery-3.3.1.js"></script> <script src="~/Scripts/SparkMd5.js"></script> <script type="text/javascript"> $("#btnselect").click(function () { $("#fileselect").click(); }); $("#fileselect").change(function () { var files = this.files; if (files.length > 0) { $("#trmsg").remove(); $(files).each(function (index, item) { console.log(index, item); var filesize = 0; if ((item.size / 1024 / 1024 / 1024) >= 1) { filesize = (item.size / 1024 / 1024 / 1024).toFixed(2) + "GB"; // b=>kb=>mb=>gb } else if ((item.size / 1024 / 1024 / 1024) < 1 && (item.size / 1024 / 1024) >= 1) { filesize = (item.size / 1024 / 1024).toFixed(2) + "MB"; } else if ((item.size / 1024 / 1024) < 1 && (item.size / 1024) >= 1) { filesize = (item.size / 1024).toFixed(2) + "KB"; } else { filesize = item.size + "B"; } var htmlstr = '<tr><td style="text-align:center"><input type="checkbox" name="fileChk" value="' + index +'" /></td><td style="text-align:center">' + item.name + '</td><td><progress value="0" max="100" class="domprogress"></progress><span class="dompercent"> 0/' + filesize + '</span><span class="domtime">总共耗时:0 秒</span></td><td class="filestatus"><span class="domstatus">排队中</span></td></tr>'; $("#filelist").append(htmlstr); }); } }); $('fileChk').each(function (index, item) { }); $("#btnupload").click(function () { var files = $("#fileselect")[0].files; $(files).each(function (index, item) { console.log('begin'); console.log(index); yyupload(files[index], $("span.domstatus").eq(index), $("span.dompercent").eq(index), $(".domprogress").eq(index), $("span.domtime").eq(index)); }); }); //文件上传 function yyupload(file, dommsg, dompercentmb, domprogress, domtime, fn) { var startTime = new Date(); var fileReader = new FileReader(); calculate(file); //获取文件的加密字符串 function calculate(file) { var fileReader = new FileReader(); var chunkSize = 1024 * 1024 * 5; //每次读取5MB var chunksCount = Math.ceil(file.size / chunkSize); //回大于参数x的最小整数 8=》8 8.4=》9 8.5=》9 -8.5=》-8 var currentChunk = 0; //当前块的索引 fileReader.onload = function (e) { console.log((currentChunk + 1) + "/" + chunksCount) dommsg.text("正在检查文件: " + (currentChunk + 1) + "/" + chunksCount); console.log(e.target.result); currentChunk++; if (currentChunk < chunksCount) { console.log('currentChunk'); console.log(currentChunk); loadNext(); } else { var md5value = SparkMD5.ArrayBuffer.hash(e.target.result); console.log("文件加密结束,密钥为:" + md5value); checkfile(md5value, file); //检查服务器是否存在该文件,存在就从断点继续上传 } }; loadNext(); function loadNext() { var start = currentChunk * chunkSize; //计算读取开始位置 var end = start + chunkSize >= file.size ? file.size : start + chunkSize; //计算读取结束位置 fileReader.readAsArrayBuffer(file.slice(start, end)); //读取为二进制字符串 console.log('loadNext') console.log(end) }; } var repeatcount = 0; //检查文件是否已经存在 function checkfile(md5value, file) { console.log('checkfile_now') var fd = new FormData(); fd.append('rquesttype', "chekcfile"); fd.append('filename', file.name); fd.append('md5value', md5value); var xhr = new XMLHttpRequest(); xhr.open('post', '../Files/FileUpload', true); xhr.onreadystatechange = function (res) { if (xhr.readyState == 4 && xhr.status == 200) { var jsonobj = JSON.parse(xhr.responseText); //能够将json字符串转换成json对象 //JSON.stringify(jsonobj); //能够将json对象转换成json对符串 console.log("继续上传的位置:" + jsonobj.startindex); switch (jsonobj.flag) { case "0": doUpload(md5value, file, 0); break; case "1": doUpload(md5value, file, parseInt(jsonobj.startindex)); break; case "2": secondUpload(file); break; } repeatcount = 0; } else if (xhr.status == 500) { setTimeout(function () { if (repeatcount < 3) { checkfile(md5value, file); } repeatcount++; }, 3000); } } //开始发送 xhr.send(fd); } //实现秒传功能 function secondUpload(file) { console.log('secondUpload'); var timerange = (new Date().getTime() - startTime.getTime()) / 1000; domtime.text("耗时" + timerange + "秒"); //显示结果进度 var percent = 100; dommsg.text(percent.toFixed(2) + "%"); domprogress.val(percent); var total = file.size; if (total > 1024 * 1024 * 1024) { dompercentmb.text((total / 1024 / 1024 / 1024).toFixed(2) + "GB/" + (total / 1024 / 1024 / 1024).toFixed(2) + "GB"); } else if (total > 1024 * 1024) { dompercentmb.text((total / 1024 / 1024).toFixed(2) + "MB/" + (total / 1024 / 1024).toFixed(2) + "MB"); } else if (total > 1024 && total < 1024 * 1024) { dompercentmb.text((total / 1024).toFixed(2) + "KB/" + (total / 1024).toFixed(2) + "KB"); } else { dompercentmb.text((total).toFixed(2) + "B/" + (total).toFixed(2) + "B"); } } //上传文件 function doUpload(md5value, file, startindex) { var reader = new FileReader();//新建一个读文件的对象 var step = 1024 * 2000; //每次读取文件大小 200KB var cuLoaded = startindex; //当前已经读取总数 var total = file.size;//文件的总大小 //读取一段成功 reader.onload = function (e) { //处理读取的结果 var result = reader.result; //本次读取的数据 var loaded = e.loaded; //本次读取的数据长度 uploadFile(result, cuLoaded, function () { //将分段数据上传到服务器 cuLoaded += loaded; //若是没有读完,继续 var timerange = (new Date().getTime() - startTime.getTime()) / 1000; if (total > 1024 * 1024 * 1024) { dompercentmb.text((cuLoaded / 1024 / 1024 / 1024).toFixed(2) + "GB/" + (total / 1024 / 1024 / 1024).toFixed(2) + "GB"); } else if (total > 1024 * 1024) { dompercentmb.text((cuLoaded / 1024 / 1024).toFixed(2) + "MB/" + (total / 1024 / 1024).toFixed(2) + "MB"); } else if (total > 1024 && total < 1024 * 1024) { dompercentmb.text((cuLoaded / 1024).toFixed(2) + "KB/" + (total / 1024).toFixed(2) + "KB"); } else { dompercentmb.text((cuLoaded).toFixed(2) + "B/" + (total).toFixed(2) + "B"); } domtime.text("耗时" + timerange + "秒"); if (cuLoaded < total) { readBlob(cuLoaded); } else { console.log('总共用时:' + timerange); cuLoaded = total; sendfinish(); //告知服务器上传完毕 domtime.text("上传完成,总共耗时" + timerange + "秒"); } //显示结果进度 var percent = (cuLoaded / total) * 100; dommsg.text(percent.toFixed(2) + "%"); domprogress.val(percent); }); } var k = 0; function sendfinish() { var fd = new FormData(); fd.append('rquesttype', "finishupload"); fd.append('filename', file.name); fd.append('md5value', md5value); fd.append('totalsize', file.size); var xhr = new XMLHttpRequest(); xhr.open('post', '../Files/FileUpload', true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { var jsonobj = JSON.parse(xhr.responseText) console.log(jsonobj); console.log(fn); if (fn) { fn(); //若是上传成功,继续上传下一个文件 } k = 0; } else if (xhr.status == 500) { setTimeout(function () { if (k < 3) { sendfinish(); //上传完毕的前端处理 } k++ }, 3000); } } //开始发送 xhr.send(fd); } var m = 0; //关键代码上传到服务器 function uploadFile(result, startIndex, onSuccess) { var blob = new Blob([result]); //提交到服务器 var fd = new FormData(); fd.append('file', blob); fd.append('rquesttype', "uploadblob"); fd.append('filename', file.name); fd.append('md5value', md5value); fd.append('loaded', startIndex); var xhr = new XMLHttpRequest(); xhr.open('post', '../Files/FileUpload', true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { m = 0; if (onSuccess) onSuccess(); } else if (xhr.status == 500) { setTimeout(function () { if (m < 3) { containue(); m++; } }, 1000); } } //开始发送 xhr.send(fd); } //指定开始位置,分块读取文件 function readBlob(start) { //指定开始位置和结束位置读取文件 var blob = file.slice(start, start + step); //读取开始位置和结束位置的文件 reader.readAsArrayBuffer(blob); //读取切割好的文件块 } //继续 function containue() { readBlob(cuLoaded); } readBlob(cuLoaded); } } </script> </body> </html>
服务器代码(c# mvc)前端
public ActionResult FileUpload() { string basefilename = HttpContext.Server.MapPath("/FileTemp/"); HttpRequestBase req = HttpContext.Request; string rquesttype = req.Form["rquesttype"]; string result = string.Empty; switch (rquesttype) { case "chekcfile": result = chekcfile(req); break; case "uploadblob": result = uploadblob(req); break; case "finishupload": result = finishupload(req); break; } return Content(result); } /// <summary> /// 结束文件上传,修改上传后的名称 /// </summary> /// <param name="req"></param> public string finishupload(HttpRequestBase req) { string basefilename = HttpContext.Server.MapPath("/FileTemp/"); //接收前端传递过来的参数 var md5value = req.Form["md5value"];//文件md5加密的字符串 var filename = req.Form["filename"];//文件的名称 var totalsize = req.Form["totalsize"];//文件的总大小 string fullname = basefilename + md5value + ".part";//上传的时候的名称 string okname = basefilename + md5value + ".ok";//上传完毕以后再该目录下建立一个.ok后缀的文件,这个文件的大小为0,没有内容,不占空间,主要用于前端检查这个文件是否已经存在了 var oldname = basefilename + filename; FileInfo okFile = new FileInfo(okname); okFile.Create(); FileInfo oldFile = new FileInfo(oldname); if (oldFile.Exists) { oldFile.Delete(); } FileInfo fullFile = new FileInfo(fullname); fullFile.MoveTo(oldname); var str = string.Format("{{\"data\":\"ok\"}}"); return str; } /// <summary> /// 处理文件分块上传的数据 /// </summary> /// <param name="req"></param> public string uploadblob(HttpRequestBase req) { string basefilename = HttpContext.Server.MapPath("/FileTemp/"); long totalCount = 0; if (req.Files.Count <= 0) { return "获取服务器上传文件失败"; } var _file = req.Files[0]; //获取参数 string filename = req.Form["filename"]; string md5value = req.Form["md5value"]; var tempfilename = md5value + ".part"; //若是是int 类型当文件大的时候会出问题 最大也就是 1.9999999990686774G long loaded = Convert.ToInt64(req.Form["loaded"]); totalCount += loaded; string newname = basefilename + tempfilename; Stream stream = _file.InputStream; if (stream.Length <= 0) throw new Exception("接收的数据不能为空"); byte[] dataOne = new byte[stream.Length]; stream.Read(dataOne, 0, dataOne.Length); FileStream fs; try { fs = new FileStream(newname, FileMode.Append, FileAccess.Write, FileShare.Read, 1024); fs.Write(dataOne, 0, dataOne.Length); fs.Close(); } catch (Exception ex) { } finally { stream.Close(); //检查文件已经上传的大小是否等于文件的总大小 } return "分段数据保存成功"; } /// <summary> /// 检查文件是否存在 /// </summary> /// <param name="req"></param> public string chekcfile(HttpRequestBase req) { string basefilename = HttpContext.Server.MapPath("/FileTemp/"); var md5value = req.Form["md5value"];//获得前端传递过来的文件的mdf字符串 var path_ok = basefilename + md5value + ".ok"; var path_part = basefilename + md5value + ".part"; int flag = 0; string json = string.Empty; FileInfo okFile = new FileInfo(path_ok); FileInfo partFile = new FileInfo(path_part); if (okFile.Exists)//传完了 { flag = 2; json = string.Format("{{\"flag\":\"{0}\"}}", flag); } else if (partFile.Exists)//传了一部分 { flag = 1; var startindex = new FileInfo(path_part).Length.ToString(); json = string.Format("{{\"flag\":\"{0}\",\"startindex\":\"{1}\"}}", flag, startindex); } else//新文件 { flag = 0; json = string.Format("{{\"flag\":\"{0}\",\"startindex\":\"0\"}}", flag); } return json; }