天天看點

C#/.net批量大檔案分塊上傳的實作

        在寫網站時,經常會用到檔案上傳這個。一般的圖檔上傳什麼的直接用fileupload倒也簡單。但是遇到大檔案上傳就頭大了。以前我也是使用别人的一個控件,但是總覺得不怎麼好,也不是開源,就還是想自己寫一個。

實作過程也還是挺簡單的,但是僅支援html5,低版本的浏覽器就不支援了。

這裡用的是html5的formdata來擷取檔案,并用slice分割後,逐個上傳。

JS:

function ReadFile(files) {
        var len = files.length;
        var per;
        var uploadLength = new Array(len);
        for (var file_index = 0; file_index < len; file_index++) {
            $('.list' + file_index + ' .pro .probar').width(0);
            var file = files[file_index];
            var size = file.size;
            var shardSize = 2097152;//以2M為一塊,這個可以自定義
            var shardCount = Math.ceil(size / shardSize);
            var tryTimes = 0;
            uploadLength[file_index] = 0;//已完成的檔案長度
            for (var bock_index = 0; bock_index < shardCount; bock_index++) {
                if (tryTimes >= 3) {
                    //失敗三次跳出循環避免死循環,顯示失敗資訊
                    tryTimes = 0;
                    $('.list' + file_index + ' .pro .probar').addClass("displayN");
                    $('.list' + file_index + ' .pro').append("<div>上傳失敗</div>");
                    break;
                }
                
                var start = bock_index * shardSize;
                var end = Math.min(size, start + shardSize);
                var formdata = new FormData();
                formdata.append("data", file.slice(start, end));
                formdata.append("name", file.name);
                formdata.append("total", shardCount);
                formdata.append("index", bock_index);
                formdata.append("file_index", file_index);
                formdata.append("file_length", size);
                formdata.append("shardSize", shardSize);
                $.ajax({
                    url: "Upload",
                    type: "post",
                    data: formdata,
                    async: true,
                    processData: false,//不對form進行處理
                    contentType: false,//指定為false才能形成正确的Content-Type
                    success: function (data, textStatus) {
                        var findex = data.findex;
                        if (data.Error == 0) {
                            uploadLength[findex] += data.successlength;
                            var thislength = data.file_length;
                            per = Math.ceil(uploadLength[findex] / thislength * 100) + "%";
                            $('.list' + findex + ' .pro .probar').width(per);
                            if (per != '100%')
                                $('.list' + findex + ' .w100 .spprocess').text(per);
                            else {
                                $('.list' + findex + ' .w100 .spprocess').text('完成');
                            }
                        }
                        else if (data.Error == 1) {
                            //發生錯誤,重新上傳
                            bock_index--;
                            tryTimes++;
                        } else {
                            tryTimes = 3;
                            $('.list' + findex + ' .pro .probar').addClass("displayN");
                            $('.list' + findex + ' .pro').append("<div>上傳失敗</div>");
                        }
                        
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        //發生錯誤,重新上傳
                        bock_index--;
                        tryTimes++;
                    }
                });
            }
        }
    }
           

        這裡确實有幾個需要注意的地方。

         首選要明确這個是異步上傳,它與同步上傳的差别還是非常大的。認真看代碼的可以發現,裡面一些變量是傳入背景,又從背景擷取。這麼做的一個目的,是保持變量的值。比如:

        在for循環裡的幾個變量如file_index和bock_index,你在浏覽器中斷點ajax的success檢視就會發現,它們并非是從0-n的依次變化,由于是異步上傳,for并不會等待ajax的操作結束,ajax向背景送出後,for就繼續往下走,往往等ajax收到背景傳回的資訊後,for循環已經結束,這裡候在success裡擷取的bock_index或者file_index都是最後一個。隻有送出背景後,再由背景發回來,才能夠取到正确的資訊。如果是同步上傳,這些就是不必要的了。或者說我們不需要顯示上傳進度也不需要這麼做。

error裡面的意思是發生錯誤後重新上傳,這個在同步上傳中有用,但異步操作的話,我覺得也是沒什麼用的。可以删去。

.net:

public ActionResult Upload()
        {
            string name = Request.Form["name"];
            int total = Int32.Parse(Request.Form["total"]);
            int index = Int32.Parse(Request.Form["index"]);
            System.Web.HttpPostedFileBase data = Request.Files["data"];
            
            long file_length = long.Parse(Request.Form["file_length"]);
            int shardSize = Int32.Parse(Request.Form["shardSize"]);
            string dir = Server.MapPath("~/upload/");
            string file_name = null;
            FileStream fs = null;
            int bock_length = 0;
            try
            {
                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                //塊檔案的存放
                //string bock_name = Path.Combine(dir, name + "_" + index);
                //data.SaveAs(bock_name);
                //實際檔案的路徑
                file_name = Path.Combine(dir, name);
                //寫入檔案中
                if (!System.IO.File.Exists(file_name))
                {
                    //建構與要上傳檔案大小相同的檔案
                    fs = new FileStream(file_name, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                    fs.SetLength(file_length);
                    fs.Close();
                }
                Stream st = data.InputStream;
           
<span style="white-space: pre;">		</span>long getLength = st.Length;
           
byte[] bytesBock = new byte[getLength];
                st.Read(bytesBock, 0, (int)getLength);
                fs = new FileStream(file_name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                int offset = index * shardSize;//寫入檔案偏移量
                //bytesBock = System.IO.File.ReadAllBytes(bock_name);
                bock_length = bytesBock.Length;
                //System.IO.File.Delete(bock_name);
                //寫入位元組流
                fs.Seek(offset, SeekOrigin.Begin);//移動到要寫入的位置
                fs.Write(bytesBock, 0, bock_length);
                fs.Close();
            }
            catch (Exception e)
            {
                fs.Close();
                if (file_name != null)
                    WriteLog(file_name, index, "save" + e.ToString());
                else WriteLog("noname", index, e.ToString());
                return Json(new { Error = 1, findex = Request.Form["file_index"] });
            }

            return Json(new { Error = 0, findex = Request.Form["file_index"],index=index, successlength = bock_length, file_length = file_length });
        }
           

        這個類似于迅雷的下載下傳,先建立一個與要上傳的檔案大小的檔案,然後再往裡面相應的位置寫入資料。這裡的關鍵是offset,這個偏量。

這裡比較關鍵的還是Write(byte[] array, int offset, int count)這個方法,offset這個偏移量,我一開始時也搞成了要寫入檔案的偏移量,後來查了才知道,原來是待入的byte[]的偏移量。

        一些不懂的方法可以去查查。

繼續閱讀