FormData是HTML5新增的一個對象,通過FormData對象可以組裝一組用 XMLHttpRequest
發送請求的鍵/值對。它可以更靈活方便的發送表單數據,因為可以獨立於表單使用。如果你把表單的編碼類型設置為multipart/form-data ,則通過FormData傳輸的數據格式和表單通過submit()
方法傳輸的數據格式相同。具體用法參考 FormData對象的使用。
實現邏輯:客戶端首先請求接口,獲取一個唯一的UploadID,然后每次按照固定大小讀取文件塊,同時計算需要上傳的總塊數total,將UploadID、total、當前上傳文件塊的下標index、文件名稱以及文件塊上傳到服務端,服務端根據以上參數把文件塊保存到臨時目錄,同時判斷文件塊是否已經全部上傳完,如果已全部上傳,則需要進行合並操作,最后會返回合並后的文件信息。我的例子中,把獲取UploadID的步驟跟上傳放在了一起,上傳接口中會判斷如果UploadID為空並且index為1,則會生成一個UploadID並返回,后續每次上傳都需要帶上UploadID參數。
一下前端代碼
前端代碼

1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>斷點上傳</title> 11 <script src="@Url.Content("~/Scripts/jquery-3.1.1.min.js")"></script> 12 </head> 13 <body> 14 <input id="myFile" type="file"> 15 <button onclick="Start()">上傳</button> 16 <button onclick="Pause()">暫停</button> 17 <button onclick="Continue()">繼續</button> 18 <label>當前進度:<span id="progress"></span></label> 19 <script> 20 var uploadId = ''; 21 var index = 1; 22 var pause = false; //暫停 23 24 function Start() { 25 index = 1; 26 uploadId = ''; 27 Upload(); 28 } 29 30 function Upload() { 31 var files = document.getElementById('myFile').files; 32 if (files.length < 1) { 33 alert('請選擇文件~'); 34 return; 35 } 36 var file = files[0]; 37 var totalSize = file.size;//文件大小 38 var blockSize = 1024 * 1024 * 2;//塊大小 39 var blockCount = Math.ceil(totalSize / blockSize);//總塊數 40 41 //創建FormData對象 42 var formData = new FormData(); 43 formData.append('fileName', file.name);//文件名 44 formData.append('total', blockCount);//總塊數 45 formData.append('index', index);//當前上傳的塊下標 46 formData.append('uploadId', uploadId);//上傳編號 47 formData.append('data', null); 48 49 UploadPost(file, formData, totalSize, blockCount, blockSize); 50 } 51 52 function UploadPost(file, formData, totalSize, blockCount, blockSize) { 53 if (pause) { 54 return; //暫停 55 } 56 try { 57 var start = index * blockSize; 58 var end = Math.min(totalSize, start + blockSize); 59 var block = file.slice(start, end); 60 formData.set('data', block); 61 formData.set('index', index); 62 formData.set('uploadId', uploadId); 63 64 $.ajax({ 65 url: '', 66 type: 'post', 67 data: formData, 68 processData: false, 69 contentType: false, 70 success: function (res) { 71 block = null; 72 if (res.Code === 1) { 73 if (index === 1) 74 uploadId = res.UploadID; 75 76 $('#progress').text((index / blockCount * 100).toFixed(2) + '%'); 77 if (index < blockCount) { 78 index++; 79 UploadPost(file, formData, totalSize, blockCount, blockSize); 80 } 81 } 82 } 83 }); 84 } catch (e) { 85 alert(e); 86 } 87 } 88 ///暫停 89 function Pause() { 90 pause = true; 91 } 92 //繼續 93 function Continue() { 94 pause = false; 95 Upload(); 96 } 97 </script> 98 </body> 99 </html>
服務端代碼

1 using System; 2 using System.IO; 3 using System.Linq; 4 using System.Web; 5 6 namespace UploadTest 7 { 8 public class UploadHelper 9 { 10 11 private UploadHelper() 12 { 13 14 } 15 16 public UploadHelper(string fileRootPath) 17 { 18 if (string.IsNullOrWhiteSpace(fileRootPath)) 19 throw new ArgumentNullException("fileRootPath", "fileRootPath is null"); 20 21 FileRootPath = fileRootPath; 22 BlockRootPath = fileRootPath + "/blocktmp/"; 23 } 24 /// <summary> 25 /// 塊文件存儲根路徑 26 /// </summary> 27 private string BlockRootPath { get; set; } 28 29 /// <summary> 30 /// 文件存儲根路徑 31 /// </summary> 32 public string FileRootPath { get; set; } 33 34 /// <summary> 35 /// 分塊上傳 36 /// </summary> 37 public UploadResult Upload(string uploadId, int blockCount, int currIndex, string fileName, HttpPostedFileBase file) 38 { 39 try 40 { 41 if (file == null) 42 return new UploadResult { Msg = "請選擇文件~" }; 43 if (blockCount < 1) 44 return new UploadResult { Msg = "塊數量不能小於1~" }; 45 if (currIndex < 0) 46 return new UploadResult { Msg = "塊數量小於0~" }; 47 if (string.IsNullOrWhiteSpace(uploadId) && currIndex > 1) 48 return new UploadResult { Msg = "上傳編號為空~" }; 49 50 var result = new UploadResult { Code = 1, Msg = "上傳成功~" }; 51 52 //首次上傳需創建上傳編號 53 if (string.IsNullOrWhiteSpace(uploadId) || uploadId.Equals("undefind")) 54 uploadId = GenerateUploadId(); 55 56 result.UploadID = uploadId; 57 58 #region ==塊處理== 59 60 //塊文件名稱 61 var blockName = $"{uploadId}_{currIndex}.block"; 62 //塊文件目錄路徑 63 var blockPath = Path.Combine(BlockRootPath, uploadId); 64 //塊文件目錄對象 65 DirectoryInfo blockDirectoryInfo = Directory.Exists(blockPath) ? new DirectoryInfo(blockPath) : Directory.CreateDirectory(blockPath); 66 //塊文件完整路徑 67 var blockFullPath = Path.Combine(blockPath, blockName); 68 if (File.Exists(blockFullPath)) 69 { 70 //塊已上傳,不做失敗處理 71 return new UploadResult { Code = 1, Msg = "該文件塊已上傳~" }; 72 } 73 74 file.SaveAs(blockFullPath); 75 76 #endregion 77 78 #region ==塊合並處理== 79 80 //判斷塊文件是否已將上傳完,上傳完合並文件 81 if (blockDirectoryInfo.GetFiles().Count().Equals(blockCount)) 82 { 83 var timestamp = DateTime.Now.ToString("yyyMMdd"); 84 fileName = uploadId + "." + GetExtension(fileName); 85 var filePath = Path.Combine(FileRootPath, timestamp); 86 if (!Directory.Exists(filePath)) 87 { 88 Directory.CreateDirectory(filePath); 89 } 90 //完整文件存儲路徑 91 var fileFullPath = Path.Combine(filePath, fileName); 92 using (var fs = new FileStream(fileFullPath, FileMode.Create)) 93 { 94 for (var i = 1; i <= blockCount; i++) 95 { 96 var path = Path.Combine(blockPath, $"{uploadId}_{i}.block"); 97 var bytes = File.ReadAllBytes(path); 98 fs.Write(bytes, 0, bytes.Length); 99 } 100 Directory.Delete(blockPath, true); 101 102 result.FileInfo = new UploadFileInfo 103 { 104 FileName = fileName, 105 FilePath = Path.Combine(timestamp, fileName) 106 }; 107 } 108 } 109 110 return result; 111 #endregion 112 } 113 catch (Exception ex) 114 { 115 return new UploadResult { Msg = ex.Message }; 116 } 117 } 118 119 /// <summary> 120 /// 生成上傳唯一編號 121 /// </summary> 122 /// <returns></returns> 123 public string GenerateUploadId() 124 { 125 var guid = Guid.NewGuid().ToString(); 126 return guid.Replace("-", ""); 127 } 128 129 /// <summary> 130 /// 獲取文件擴展名 131 /// </summary> 132 /// <param name="fileName"></param> 133 /// <returns></returns> 134 public string GetExtension(string fileName) 135 { 136 if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOf(".") < 0) 137 { 138 return string.Empty; 139 } 140 var arr = fileName.Split('.'); 141 return arr[arr.Length - 1]; 142 } 143 } 144 /// <summary> 145 /// 文件上傳結果 146 /// </summary> 147 public class UploadResult 148 { 149 /// <summary> 150 /// 狀態碼 0失敗 1成功 151 /// </summary> 152 public int Code { get; set; } 153 /// <summary> 154 /// 消息 155 /// </summary> 156 public string Msg { get; set; } 157 /// <summary> 158 /// 上傳編號,唯一 159 /// </summary> 160 public string UploadID { get; set; } 161 /// <summary> 162 /// 文件保存信息 163 /// </summary> 164 public UploadFileInfo FileInfo { get; set; } 165 166 } 167 public class UploadFileInfo 168 { 169 /// <summary> 170 /// 文件保存名稱 171 /// </summary> 172 public string FileName { get; set; } 173 /// <summary> 174 /// 文件保存路徑 175 /// </summary> 176 public string FilePath { get; set; } 177 /// <summary> 178 /// 文件MD5值 179 /// </summary> 180 public string MD5 { get; set; } 181 } 182 }
Controller代碼
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace UploadTest.Controllers { [RoutePrefix("upload")] public class UploadController : Controller { [Route] [HttpGet] public ActionResult Index() { return View(); } [Route] [HttpPost] public ActionResult Upload(string uploadId,int total,int index,string fileName) { var helper = new UploadHelper("D:\\Upload"); var result = helper.Upload(uploadId, total, index, fileName, Request.Files[0]); return Json(result); } } }