天天看點

JS分片上傳大檔案并顯示進度條執行個體

作者:逍遙總遙

上篇文章說明了如何用js在用戶端上傳檔案,功能是實作了,但當上傳大檔案時,一是時間長,像卡死了一樣,二是使用者不知道進度很容易在沒上傳完之前就關掉。今天我們就來看如何在用戶端将大檔案分片上傳,并顯示進度。

先來看一下效果:

JS分片上傳大檔案并顯示進度條執行個體

1、建立一個"js_upBigFile"的項目,添加一個webForm1.aspx,給WebForm1添加如下控件,這裡進度條用一個就可以,我這裡是示範如何用進度條控件progress和用div模拟兩種方式顯示。Form外面的基本資訊這些,都是用來監控效果的控件,實際應用時是不需要的。

<body>
    <form id="form1" runat="server">
        <input type="file" id="f" />
        <br />
        <input type="button" value="up" onclick="up()" />
        <br />
        <!--進度條标簽-->
        <progress value="0" max="100" id="progress" style="height: 20px; width: 100%"></progress>
        <br />
        <!--div模拟進度條-->
        <div id="progressNumber" style="width: 0%; height: 20px; background-color: Red"></div>
        <br />
        <div id="result"></div>
    </form>
    <p>
        基本資訊:<input id="Text1" type="text" /></p>
    <p>
        正在上傳:<input id="Text2" type="text" /></p>
    <p>
        正在處理:<input id="Text3" type="text" /></p>
    <p>
        <textarea id="TextArea1" name="S1"></textarea></p>
    <p>
        <textarea id="TextArea2" name="S2" cols="20" rows="1"></textarea></p>
</body>           

2、在項目目錄下建立兩個檔案夾,一個“1”來存儲臨時檔案,一個“2”來存儲上傳後的檔案。注意不是“bin"目錄。

JS分片上傳大檔案并顯示進度條執行個體

3、添加點選按鈕的js代碼

這個按鈕就是點選執行js代碼的按鈕,<input type="button" value="up" onclick="up()" />

js函數up()的代碼如下:

<script type="text/ecmascript">
    //每個切片檔案的大小 1024 * 1024 就是1M,下面我這裡就是2M
    var bytesPerPiece = 1024 * 1024 * 2;
    var totalPieces;

    pieceover = 0;
    hbindex = 0;
    totalPieces = 0;
    function up() {
        if (document.getElementById("f").value == "") {
            document.getElementById("result").innerHTML = "請選擇檔案";
        }
        else {
            var fileObj = document.getElementById("f").files[0];

            var start = 0;
            var end;
            var index = 0;
            var filesize = fileObj.size;
            var filename = fileObj.name;
            //擷取最後一個.的位置
            var exindex = filename.lastIndexOf(".");
            //擷取擴充名,帶.
            var fileexname = filename.substr(exindex);

            //計算檔案切片總數,也就是一共多少個檔案
            totalPieces = Math.ceil(filesize / bytesPerPiece);

            infostr = "";
            infostr += " 檔案名:" + filename;
            infostr += " 大小:" + filesize;
            infostr += " 總數:" + totalPieces;
            document.getElementById("Text1").value = infostr;

            while (start < filesize) {
                //分片結束大小位置
                end = start + bytesPerPiece;

                if (end > filesize) {
                    end = filesize;
                }

                //建立xhr
                var xhr = new XMLHttpRequest();
                //對應的處理資料的 ashx 檔案 -------------  注意這裡
                var url = "upFile.ashx";
                //得到分片檔案,根據開始位置和結束位置
                var chunk = fileObj.slice(start, end);
                var sliceIndex = fileObj.name + index;

                //FormData對象
                var fd = new FormData();
                //檔案素引,也就是目前是第幾個片檔案
                fd.append("index", index);    
                //将分片檔案做為參數傳到ashx檔案中上傳
                fd.append("file", chunk, filename);
                //加上目前的時間,據稱可以防止檔案緩存,我沒有試過,反正寫上吧
                fd.append("time", new Date().toString());
                xhr.open("POST", url, true);

                xhr.onreadystatechange = function () {
                    //全部上傳完畢以後,彈出菜單
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        alert("分片結束");
                    }
                }
                
                //上傳結束後,注意這裡是上傳一個就會運作一次,
                //也正是這種機制使我們可以監控上傳進度
                xhr.upload.onloadend = function (evt) {
                    pieceover++;
                    document.getElementById("Text2").value = pieceover;
                    
                    var percentComplete = (pieceover / totalPieces * 100).toFixed(4);
                    document.getElementById('progress').value = percentComplete;
                    document.getElementById('progressNumber').style.width = percentComplete + "%";

                    document.getElementById("result").innerHTML = "第1步(共2步):已經完成:" + percentComplete + "%";
                }
								//發送資料
                xhr.send(fd);
                
                //更改下次分片檔案的起始位置
                start = end;
                index++;
            }
        }
    }
</script>           

4、添加一個upFile.ashx一般處理程式

JS分片上傳大檔案并顯示進度條執行個體

代碼如下:

using System;
using System.Web;

namespace WebApplication1
{
    /// <summary>
    /// upFile 的摘要說明
    /// </summary>
    public class upFile : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            //服務端存儲分片檔案的位址
            string rootpath = System.Web.HttpContext.Current.Server.MapPath("~");
            string savePath = rootpath + "\\1\\";

            //得到分片檔案的索引名
            string index = context.Request["index"];
            //得到傳過來的分片檔案
            HttpPostedFile file = context.Request.Files[0];
            //檔案擴充名
            string fileType = System.IO.Path.GetExtension(file.FileName);
            //存到檔案伺服器的檔案名稱 用目前時間命名
            //string fileNewName = DateTime.Now.ToString("yyyyMMddHHmmss_fff") + fileType;
            string fileNewName = index + fileType;

            try
            {
                //上傳
                file.SaveAs(savePath + fileNewName);
                context.Response.Write("上傳成功!");
            }
            catch (Exception ex)
            {
                context.Response.Write("上傳失敗!錯誤資訊:" + ex.Message.ToString());
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}           

此時第一步分片上傳已經結束,運作效果如下:

JS分片上傳大檔案并顯示進度條執行個體

1檔案夾中的内容

JS分片上傳大檔案并顯示進度條執行個體

5、通過上面4步,我們就将檔案分片上傳到伺服器上了,這就完成了第一步,現在我們做第二步,在伺服器上把檔案合并起來。

在js中新加幾個變量

pieceover = 0; //已經合并的檔案總數

hbindex = 0; //目前正在合并的索引号

totalPieces = 0; //總共要合并的檔案總數

将up函數中結束的部分換成如下代碼,再用go()函數開始合并

xhr.onreadystatechange = function () {
                    //全部上傳完畢以後,彈出菜單
                    xhr.onreadystatechange = function () {
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        if (pieceover == totalPieces) {  //如果還沒有開始合并
                            pieceover = 0;  //從第0片開始合并
                            //重置滾動條
                            document.getElementById('progress').value = 0;
                            //開始合并
                            go(0, filename, fileexname);
                        }
                    }
                }           

6、go函數代碼:

function go(index, newfilename, exname) {
        var xhr1 = new XMLHttpRequest();
        var url1 = "HeBin.ashx";    //這次用一個新的處理檔案
        var fd1 = new FormData();

        xhr1.open("POST", url1, true);
        xhr1.onreadystatechange = function () {
            if (xhr1.readyState == 4 && xhr1.status == 200) {
                var result1 = xhr1.responseText;

                if (result1 != "") {
                    if (pieceover < totalPieces) {
                        if (result1 == hbindex) {
                            document.getElementById("TextArea2").value += "Y(result1:" + result1 + ",hbindex:" + hbindex + ")";
                            pieceover++;
                            document.getElementById("Text3").value = pieceover;
                            //var percentComplete = Math.round(pieceover * 100 / totalPieces)
                            var percentComplete = (pieceover / totalPieces * 100).toFixed(4);
                            document.getElementById('progress').value = percentComplete;
                            document.getElementById('progressNumber').style.width = percentComplete + "%";
                            document.getElementById("result").innerHTML = "第2步(共2步):已經完成:" + percentComplete + "%";

                            hbindex++;
                            document.getElementById("TextArea1").value += hbindex;
                            go(hbindex, newfilename, exname);
                            document.getElementById("TextArea1").value += "over,result:" + result1;

                        }
                    }
                    else {
                        document.getElementById("result").innerHTML = "上傳成功!";
                    }
                }
                else
                {
                    if (pieceover >= totalPieces) {
                        document.getElementById("result").innerHTML = "上傳成功!";
                    }
                }
            }
        }
        
        fd1 = new FormData();
        fd1.append("newfilename", newfilename); //合并成的檔案名
        fd1.append("filename", hbindex);   //檔案名(索引)
        fd1.append("exname", exname);      //擴充名
        fd1.append("acttime", new Date().toString());
        xhr1.send(fd1);
    }           

7、再添加一個HeBin.ashx,用來把上傳上去的分片檔案合并,代碼如下

using System;
using System.IO;
using System.Web;

namespace WebApplication1
{
    /// <summary>
    /// HeBin 的摘要說明
    /// </summary>
    public class HeBin : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string NewFileName = context.Request["newfilename"];
            //伺服器路徑
            string rootpath = System.Web.HttpContext.Current.Server.MapPath("~");
            //臨時檔案路徑
            string qiepianpath = rootpath  + "1\\";
            //要處理的檔案名,不帶擴充名,如 0 1
            string nowname = context.Request["filename"];
            //擴充名
            string fileType = context.Request["exname"];
            //要處理的檔案名,帶擴充名,如 0.mp4
            string nownameall = nowname + fileType;
            //要處理的檔案完整路徑
            string thepath = qiepianpath + nownameall;

            //調試用,萬一哪裡出錯了,可以根據這個數字看看到底是哪個檔案錯了
            int time = 0;  
            try
            {
                string filename = NewFileName;
                string SaveFileName = rootpath + "2\\" + filename;

                using (FileStream fs = new FileStream(SaveFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                {
                    using (FileStream readStream = new FileStream(thepath, FileMode.Open, FileAccess.Read))
                    {
                        byte[] buffer = new byte[readStream.Length];
                        int data = 0;
                        
                        //合并檔案
                        while ((data = readStream.Read(buffer, 0, 1024)) > 0)
                        {
                            fs.Position = fs.Length;
                            fs.Write(buffer, 0, data);
                        }
                    }

                    fs.Dispose();
                    File.Delete(thepath);
                    context.Response.Write(nowname);
                }
            }
            catch (Exception x)
            {
                time++;
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}           

運作程式,在合并時能明顯看到檔案正在被合并的效果

JS分片上傳大檔案并顯示進度條執行個體

到這整個程式部分就結束了。

繼續閱讀