WebUploader組件實際介紹:
- 官網:http://fex.baidu.com/webuploader/doc/index.html
- 組件優勢及優化總結:http://itindex.net/detail/49267-webuploader-%E6%96%87%E4%BB%B6-%E4%B8%8A%E4%BC%A0
- 組件暫停功能的問題:https://github.com/fex-team/webuploader/issues/488
- 斷點續傳問題:https://github.com/fex-team/webuploader/issues/142
- 具體思路可以先參考鏈接:http://www.sxrczx.com/pages/blog.kazaff.me/uxgb1423648892626.html
以下內容於你對上述組件有了大致了解的基礎上,結合C#實現大文件的上傳、斷點續傳、分塊等功能,展開說明的:
設計思路:在每次文件上傳前,獲取該文件的MD5值作為該文件的唯一標識符,然后對該文件進行分塊處理(此處每塊設置5M大小,詳見2),按塊分發請求(此處設置為3個線程發請求,詳見2)文件開始上傳前觸發,一個文件只會觸發一次 uploadStart事件,該事件會驗證之前該文件是否已上傳部分,返回已上傳部分的塊數編號列表,然后在下一個事件before-send(在文件分片處理之后,上傳之前會觸發)判斷哪些塊數已存在,上傳不存在的文件塊,至此基本上傳流程已完成。
/// <summary> /// 文件上傳消息類 /// </summary> public class UploaderResult { public UploaderResult() { this.IsOver = false; this.Chunk = 0; this.Chunks = 0; this.FileExtist = false; this.ChunkNum = new List<int>(); } /// <summary> /// 文件名稱、當上傳完成時將返回最終的文件名 /// </summary> public string Name { get; set; } /// <summary> /// 文件是否全部上傳完成 /// </summary> public bool IsOver { get; set; } /// <summary> /// 消息 /// </summary> public string Message { get; set; } /// <summary> /// 如果為分塊上傳、返回當前的分塊索引 /// </summary> public int Chunk { get; set; } /// <summary> /// 總的分塊大小 /// </summary> public int Chunks { get; set; } /// <summary> /// 文件的MD5碼 /// </summary> public string Md5 { get; set; } /// <summary> /// 上傳的文件是否已經存在於服務器 /// </summary> public bool FileExtist { get; set; } /// <summary> /// 服務器已經存在的區塊序號 /// </summary> public List<int> ChunkNum { get; set; } /// <summary> /// 文件擴展名 /// </summary> public string FileExtension { get; set; } /// <summary> /// 文件路徑 /// </summary> public string FilePath { get; set; } /// <summary> /// 文件大小 /// </summary> public int FileSize { get; set; } }
/// <summary>
/// 斷點續傳檢測、MD5檢測 秒傳亦可以在這個方法塊中實現,看需求
/// </summary>
public static UploaderResult ProcessCheck(HttpRequestBase request, string savepath = null
, Func<HttpPostedFileBase, string> setfilename = null, Func<string, bool> md5check = null)
{
UploaderResult obj = new UploaderResult();
string tempFilePath = savepath + "temp\\" + request["md5"] + "\\";
//文件大小
long size = request.Form["size"] == null ? 0 : Convert.ToInt64(request.Form["size"]);
//文件分塊大小
long chunksize = request.Form["chunksize"] == null ? 0 : Convert.ToInt64(request.Form["chunksize"]);
//文件區塊總數
int chunks = chunksize != 0 ? Convert.ToInt32(size / chunksize) : 1;
int j = 0;
for (int i = 0; i <= chunks; i++)
{
if (File.Exists(tempFilePath + i.ToString()))
{
obj.ChunkNum.Add(i);//服務器已經存在的區塊編號
j++;
}
}
obj.Message = string.Format("服務器已經存在區塊數量{0},總區塊數量{1},占比{2}%", j
, chunks + 1, chunks != 0 && j != 0 ? Convert.ToDouble(Convert.ToDouble(j) / Convert.ToDouble(chunks)) * 100 : 0);
return obj;
}
/// <summary> /// 文件上傳、保存 /// </summary> /// <param name="request"></param> /// <param name="savepath"></param> /// <returns></returns> public static UploaderResult UploadSingleProcess(HttpRequestBase request, string savepath = null) { UploaderResult obj = new UploaderResult(); if (request.Files.Count == 0) { obj.Message = "請求中不包含文件流信息"; return obj; } if (request["size"] == null) { obj.Message = "文件大小為空"; return obj; } string tempFilePath = savepath + "temp\\" + request["md5"] + "\\"; string saveFilePath = savepath + "files\\"; if (!Directory.Exists(saveFilePath)) { Directory.CreateDirectory(saveFilePath); } //判斷是否分片,若文件大小不足分片,則直接保存 if (request.Form.AllKeys.Any(m => m == "chunk")) { //分片時創建臨時文件目錄 if (!Directory.Exists(tempFilePath)) { Directory.CreateDirectory(tempFilePath); } //取得chunk和chunks int chunk = Convert.ToInt32(request.Form["chunk"]); int chunks = Convert.ToInt32(request.Form["chunks"]); HttpPostedFileBase file = request.Files[0]; //根據GUID創建用該GUID命名的臨時文件 file.SaveAs(tempFilePath + chunk.ToString()); //判斷是否所有的分塊都已經上傳完畢 string[] fileArr = Directory.GetFiles(tempFilePath); if (fileArr.Length == chunks) { obj.IsOver = true; } } else { request.Files[0].SaveAs(saveFilePath + request.Files[0].FileName); obj.IsOver = true; } return obj; }
/// <summary> /// 文件塊合並 /// </summary> /// <returns></returns> public ActionResult MergeFiles() { string ext = Request.Form["fileExt"]; string fileName = Request.Form["fileName"]; string chunkNum = Request.Form["chunkNum"]; string md5 = Request.Form["md5"]; string tempFilePath = this.RootFolder + "\\temp\\" + md5 + "\\"; string saveFilePath = this.RootFolder + "\\files\\" + fileName; string[] fileArr = Directory.GetFiles(tempFilePath); try { lock (tempFilePath) { if (Convert.ToInt32(chunkNum) == fileArr.Length) { if (System.IO.File.Exists(saveFilePath)) { System.IO.File.Delete(saveFilePath); } FileStream addStream = new FileStream(saveFilePath, FileMode.Append, FileAccess.Write); BinaryWriter AddWriter = new BinaryWriter(addStream); for (int i = 0; i < fileArr.Length; i++) { //以小文件所對應的文件名稱和打開模式來初始化FileStream文件流,起讀取分割作用 FileStream TempStream = new FileStream(tempFilePath+i, FileMode.Open); //用FileStream文件流來初始化BinaryReader文件閱讀器,也起讀取分割文件作用 BinaryReader TempReader = new BinaryReader(TempStream); //讀取分割文件中的數據,並生成合並后文件 AddWriter.Write(TempReader.ReadBytes((int)TempStream.Length)); //關閉BinaryReader文件閱讀器 TempReader.Close(); TempStream.Close(); } AddWriter.Close(); addStream.Close(); AddWriter.Dispose(); addStream.Dispose(); Directory.Delete(tempFilePath, true); } //驗證文件的MD5,確定文件的完整性 前端文件MD5值與后端合並的文件MD5值不相等 //if (UploaderHelper.GetMD5HashFromFile(saveFilePath) == md5) //{ // int i = 1; //} return Json("{hasError:\"false\"}"); } } catch (Exception ex) { return Json("{hasError:\"true\"}"); } }
后台代碼,還需要整理優化,當然如果有問題,大家可以一塊學習談論。
