最近在研究文件上傳,里面的門道還是挺多的,網上大多數文章比較雜亂,代碼都是片段,對於新手小白來說難度較高,所以在此詳細寫一下今天看到的一個demo,關於文件分片上傳的。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f" crossorigin="anonymous"></script> </head> <body> <input type="file" id="file6" multiple><button type="button" class="btnFile6">分片上傳</button><div class="result"></div> </body> </html> <script> $(".btnFile6").click(function () { var upload = function (file, skip) { var formData = new FormData();//初始化一個FormData對象 var blockSize = 1000000;//每塊的大小 var nextSize = Math.min((skip + 1) * blockSize, file.size);//讀取到結束位置 var fileData = file.slice(skip * blockSize, nextSize);//截取 部分文件 塊 formData.append("file", fileData);//將 部分文件 塞入FormData formData.append("fileName", file.name);//保存文件名字 $.ajax({ url: "Handler.ashx", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理發送的數據 contentType: false, // 告訴jQuery不要去設置Content-Type請求頭 success: function (responseText) { $(".result").html("已經上傳了" + (skip + 1) + "塊文件"); if (file.size <= nextSize) {//如果上傳完成,則跳出繼續上傳 alert("上傳完成"); return; } upload(file, ++skip);//遞歸調用 } }); }; var file = $("#file6")[0].files[0]; upload(file, 0); }); </script>
后端一般處理程序:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; namespace WebApplication1 { /// <summary> /// Handler 的摘要說明 /// </summary> public class Handler : IHttpHandler { public void ProcessRequest(HttpContext context) { //保存文件到指定目錄 var filePath = @"D:\penglong\study\WebApplication1\WebApplication1\uploads\" + context.Request.Form["fileName"]; //創建一個追加(FileMode.Append)方式的文件流 using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs)) { //讀取文件流 BinaryReader br = new BinaryReader(context.Request.Files[0].InputStream); //將文件留轉成字節數組 byte[] bytes = br.ReadBytes((int)context.Request.Files[0].InputStream.Length); //將字節數組追加到文件 bw.Write(bytes); } } } public bool IsReusable { get { return false; } } } }
瀏覽器監聽的請求過程如圖:
目前代碼里設置的是1M一片,2.25MB的文件被分為了3片,因此請求的時候請求了三次,這樣,我們就可以上傳大文件了。(webconfig可以設置最大文件為2G,當上傳的文件超過2G或者我們不希望更改webconfig配置時,則可以使用這個進行分片上傳,網上大多數分片插件的原理也是如此,通過構造formdata對象進行多次上傳,然后在后端進行文件的合並操作)。
參考鏈接:https://www.cnblogs.com/fjzhang/p/7227401.html?utm_source=itdadao&utm_medium=referral
補:
從demo上講,上面的案例基本沒有問題,但是實際使用的場景中,會存在這樣一個問題,由於文件是存在磁盤上的,當用戶再次上傳同名文件時,文件流會被繼續追加到這個文件里,而不是另起一個文件,或者做覆蓋操作,因為FileMode.Append模式下會檢測當前路徑是否存在這個文件,存在則append。
那么該如何解決這個問題呢,一開始我想到的是通過size判斷,如果傳入文件的size,不等於讀取到的文件流對象的大小,則說明文件還沒有傳輸完畢,則可以繼續append。但是當等於時,是需要新建文件的,所以就得重命名了,windows系統再重命名的時候會在原有文件名后面帶上副本啊之類的名稱,我這里為了簡潔,直接在前端讀取了一個系統時間作為唯一標識,來混入文件名,這樣后台保存的文件就不會存在文件流並入一個文件的問題了。
代碼如圖:
這里有一點需要注意,為什么我不在后端去生成這個時間標識,而是在前端生成呢?原因是這里是進行分片上傳的,會多次請求后端接口,如果在后端生成唯一標識,那么在上傳文件的方法里會生成多個文件,這樣是不對的,所以這里在前端做了參數取值,每次點擊的時候生成一個唯一標識,即便分N次上傳,名稱也是同一個,文件流就能正常合並在一個文件里了。
當然,關於這個同名文件上傳改名的操作,后端應該也能完成,但是感覺過於復雜,這里就沒有嘗試。有興趣的園友可以一起探討思路。暫時就補充到這里了。