webuploader-異步切片上傳(暫不支持斷點續傳)及 下載方法!C#/.NET


十年河東,十年河西,莫欺少年窮

學無止境,精益求精

進入正題:

關於webuploader,參考網址:https://fex.baidu.com/webuploader/

本篇博客范例下載地址:https://download.csdn.net/download/wolongbb/11958864

WebUploader是由Baidu WebFE(FEX)團隊開發的一個簡單的以HTML5為主,FLASH為輔的現代文件上傳組件。在現代的瀏覽器里面能充分發揮HTML5的優勢,同時又不摒棄主流IE瀏覽器,沿用原來的FLASH運行時,兼容IE6+,iOS 6+, android 4+。兩套運行時,同樣的調用方式,可供用戶任意選用。采用大文件分片並發上傳,極大的提高了文件上傳效率。

作為一個上傳插件,我們首先要做的是下載資源包,方便項目引用。

關於下載資源包,參考網址上有下載鏈接,可自行下載!

下面構造項目:

1、引入相關文件:

    <meta name="viewport" content="width=device-width" />
    <title>大文件上傳測試</title>
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <link href="/Content/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
    <script src="/Content/webuploader-0.1.5/webuploader.js"></script>

2、參數說明:

3、前端HTML如下:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>大文件上傳測試</title>
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <link href="/Content/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
    <script src="/Content/webuploader-0.1.5/webuploader.js"></script>
    <script type="text/javascript">
    $(function () {
        var GUID = WebUploader.Base.guid();//一個GUID
        var uploader = WebUploader.create({
            // {Boolean} [可選] [默認值:false] 設置為 true 后,不需要手動調用上傳,有文件選擇即開始上傳。
            auto: true,
            swf: '/Content/webuploader-0.1.5/Uploader.swf',
            server: '@Url.Action("Upload")',
            pick: '#picker',
            resize: false,
            chunked: true,//開始分片上傳
            chunkSize: 2048000,//每一片的大小
            formData: {
                guid: GUID //自定義參數,待會兒解釋
            }
        });
        uploader.on('fileQueued', function (file) {
            $("#uploader .filename").html("文件名:" + file.name);
            $("#uploader .state").html('等待上傳');
        });
        uploader.on('uploadSuccess', function (file, response) {
            $.post('@Url.Action("Merge")', { guid: GUID, fileName: file.name }, function (data) {
                //alert("上傳成功!")
            });
        });
        uploader.on('uploadProgress', function (file, percentage) {
            $("#uploader .progress-bar").width(percentage * 100 + '%');
            $(".sr-only").html('上傳進度:'+(percentage * 100).toFixed(2) + '%')

        });
        uploader.on('uploadSuccess', function () {
            $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active').removeClass('progress-bar-info').addClass('progress-bar-success');
            $("#uploader .state").html("上傳成功...");

        });
        uploader.on('uploadError', function () {
            $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active').removeClass('progress-bar-info').addClass('progress-bar-danger');
            $("#uploader .state").html("上傳失敗...");
        });

        $("#ctlBtn").click(function () {
            uploader.upload();
            $("#ctlBtn").text("上傳");
            $('#ctlBtn').attr('disabled', 'disabled');
            $("#uploader .progress-bar").addClass('progress-bar-striped').addClass('active');
            $("#uploader .state").html("上傳中...");
        });
        $('#pause').click(function () {
            uploader.stop(true);
            $('#ctlBtn').removeAttr('disabled');
            $("#ctlBtn").text("繼續上傳");
            $("#uploader .state").html("暫停中...");
            $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active');
        });
    });

        function downLoadFile() {
            window.open("/Home/Download")
        }

    </script>
</head>
<body>
    <div id="uploader" class="wu-example">
        <!--用來存放文件信息-->
        <div class="filename"></div>
        <div class="state"></div>
        <div class="progress">
            <div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
                <span class="sr-only"></span>
            </div>
        </div>
        <div class="btns">
            <div id="picker">選擇文件</div>
        </div>

        <div class="btns" style="margin-top:25px;" onclick="downLoadFile()">
            <div id="picker" class="webuploader-container"><div class="webuploader-pick">下載文件</div><div id="rt_rt_1deem09edokba40iet6gmkem2" style="position: absolute; top: 0px; left: 0px; width: 94px; height: 41px; overflow: hidden; bottom: auto; right: auto;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple"><label style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);"></label></div></div>
        </div>
    </div>
</body>
</html>
View Code

4、后端代碼如下:

namespace WebUploader.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }
        /// <summary>
        /// 分切片上傳-異步
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public ActionResult Upload()
        {
            string fileName = Request["name"];
            int index = Convert.ToInt32(Request["chunk"]);//當前分塊序號
            var guid = Request["guid"];//前端傳來的GUID號
            var dir = Server.MapPath("~/Upload");//文件上傳目錄
            dir = Path.Combine(dir, guid);//臨時保存分塊的目錄
            if (!System.IO.Directory.Exists(dir))
                System.IO.Directory.CreateDirectory(dir);
            string filePath = Path.Combine(dir, index.ToString());//分塊文件名為索引名,更嚴謹一些可以加上是否存在的判斷,防止多線程時並發沖突
            var data = Request.Files["file"];//表單中取得分塊文件
            if (data != null)//為null可能是暫停的那一瞬間
            {
                data.SaveAs(filePath);//報錯
            }
            return Json(new { erron = 0 });//Demo,隨便返回了個值,請勿參考
        }

        /// <summary>
        /// 合並切片
        /// </summary>
        /// <returns></returns>
        public ActionResult Merge()
        {
            var guid = Request["guid"];//GUID
            var uploadDir = Server.MapPath("~/Upload");//Upload 文件夾
            var dir = Path.Combine(uploadDir, guid);//臨時文件夾
            var fileName = Request["fileName"];//文件名
            var files = System.IO.Directory.GetFiles(dir);//獲得下面的所有文件
            var finalPath = Path.Combine(uploadDir, fileName);//最終的文件名(demo中保存的是它上傳時候的文件名,實際操作肯定不能這樣)
            var fs = new FileStream(finalPath, FileMode.Create);
            foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))//排一下序,保證從0-N Write
            {
                var bytes = System.IO.File.ReadAllBytes(part);
                fs.Write(bytes, 0, bytes.Length);
                bytes = null;
                System.IO.File.Delete(part);//刪除分塊
            }
            fs.Close();
            System.IO.Directory.Delete(dir);//刪除文件夾
            return Json(new { error = 0 });//隨便返回個值,實際中根據需要返回
        }

        #region 文件下載處理
        /// <summary>
          /// 下載文件,支持大文件、續傳、速度限制。支持續傳的響應頭Accept-Ranges、ETag,請求頭Range 。
          /// Accept-Ranges:響應頭,向客戶端指明,此進程支持可恢復下載.實現后台智能傳輸服務(BITS),值為:bytes;
          /// ETag:響應頭,用於對客戶端的初始(200)響應,以及來自客戶端的恢復請求,
          /// 必須為每個文件提供一個唯一的ETag值(可由文件名和文件最后被修改的日期組成),這使客戶端軟件能夠驗證它們已經下載的字節塊是否仍然是最新的。
          /// Range:續傳的起始位置,即已經下載到客戶端的字節數,值如:bytes=1474560- 。
          /// 另外:UrlEncode編碼后會把文件名中的空格轉換中+(+轉換為%2b),但是瀏覽器是不能理解加號為空格的,所以在瀏覽器下載得到的文件,空格就變成了加號;
         /// 解決辦法:UrlEncode 之后, 將 "+" 替換成 "%20",因為瀏覽器將%20轉換為空格
         /// </summary>
        /// <param name="httpContext">當前請求的HttpContext</param>
         /// <param name="filePath">下載文件的物理路徑,含路徑、文件名</param>
         /// <param name="speed">下載速度:每秒允許下載的字節數</param>
         /// <returns>true下載成功,false下載失敗</returns>
        public static bool DownloadFile(HttpContext httpContext, string filePath, long speed)
        {
            bool ret = true;
            try
            {
                #region 驗證:HttpMethod,請求的文件是否存在
                switch (httpContext.Request.HttpMethod.ToUpper())
                { //目前只支持GET和HEAD方法
                    case "GET":
                    case "HEAD":
                        break;
                    default:
                        httpContext.Response.StatusCode = 501;
                        return false;
                }
                if (!System.IO.File.Exists(filePath))
                {
                    httpContext.Response.StatusCode = 404;
                    return false;
                }
                #endregion

                #region 定義局部變量
                long startBytes = 0;
                int packSize = 1024 * 10; //分塊讀取,每塊10K bytes
                string fileName = Path.GetFileName(filePath);
                FileStream myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                BinaryReader br = new BinaryReader(myFile);
                long fileLength = myFile.Length;

                int sleep = (int)Math.Ceiling(1000.0 * packSize / speed);//毫秒數:讀取下一數據塊的時間間隔
                string lastUpdateTiemStr = System.IO.File.GetLastWriteTimeUtc(filePath).ToString("r");
                string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr;//便於恢復下載時提取請求頭;
                #endregion

                //--驗證:文件是否太大,是否是續傳,且在上次被請求的日期之后是否被修改過--------------

                try
                {
                    //-------添加重要響應頭、解析請求頭、相關驗證-------------------

                    #region -------向客戶端發送數據塊-------------------
                    br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
                    int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//分塊下載,剩余部分可分成的塊數
                    for (int i = 0; i < maxCount && httpContext.Response.IsClientConnected; i++)
                    {//客戶端中斷連接,則暫停
                        httpContext.Response.BinaryWrite(br.ReadBytes(packSize));
                        httpContext.Response.Flush();
                        if (sleep > 1) Thread.Sleep(sleep);
                    }
                    #endregion
                }
                catch
                {
                    ret = false;
                }
                finally
                {
                    br.Close();
                    myFile.Close();
                }
            }
            catch
            {
                ret = false;
            }
            return ret;
        }
        #endregion

        public ActionResult Download()
        {
            string filePath = Server.MapPath("~/Upload/TortoiseSVN-1.8.11.26392-x64.zip");
            string fileName = "TortoiseSVN-1.8.11.26392-x64.zip";
            return new FileResult(filePath, fileName);
        }
    }

    /// <summary>
    /// 該類繼承了ActionResult,通過重寫ExecuteResult方法,進行文件的下載
    /// </summary>
    public class FileResult : ActionResult
    {
        private readonly string _filePath;//文件路徑
        private readonly string _fileName;//文件名稱
        public FileResult(string filePath, string fileName)
        {
            _filePath = filePath;
            _fileName = fileName;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            string fileName = _fileName;
            HttpResponseBase response = context.HttpContext.Response;
            if (System.IO.File.Exists(_filePath))
            {
                FileStream fs = null;
                byte[] fileBuffer = new byte[1024];//每次讀取1024字節大小的數據
                try
                {
                    using (fs = System.IO.File.OpenRead(_filePath))
                    {
                        long totalLength = fs.Length;
                        response.ContentType = "application/octet-stream";
                        response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName));
                        while (totalLength > 0 && response.IsClientConnected)//持續傳輸文件
                        {
                            int length = fs.Read(fileBuffer, 0, fileBuffer.Length);//每次讀取1024個字節長度的內容
                            fs.Flush();
                            response.OutputStream.Write(fileBuffer, 0, length);//寫入到響應的輸出流
                            response.Flush();//刷新響應
                            totalLength = totalLength - length;
                        }
                        response.Close();//文件傳輸完畢,關閉相應流
                    }
                }
                catch (Exception ex)
                {
                    response.Write(ex.Message);
                }
                finally
                {
                    if (fs != null)
                        fs.Close();//最后記得關閉文件流
                }
            }
        }
    }
}
View Code

5、說明如下

上傳時,會將大附件切成切片並存在一個臨時文件夾中,待所有切片上傳成功后,會調用Merge()方法合成文件!隨后刪除切片文件夾!

上述代碼中包含一個下載附件的代碼!

時間有限,沒時間寫博客,太忙了!

關於下載的方法,優化如下:

場景:如果遇到打包壓縮后再下載,則下載完成后有可能需要刪除壓縮后的文件,現優化如下:

關於壓縮文件可參考:C# 壓縮文件

2019年7月17日 20點26分

增加是否可以刪除下載的文件,如下:

    public class downLoadFileResult : ActionResult
    {
        private readonly string _filePath;//文件路徑
        private readonly string _fileName;//文件名稱
        private readonly bool _isDeleted;//下載完成后,是否可刪除
        /// <summary>
        /// 構造刪除
        /// </summary>
        /// <param name="filePath">路徑</param>
        /// <param name="fileName">文件名</param>
        /// <param name="isDeleted">下載完成后,是否可刪除</param>
        public downLoadFileResult(string filePath, string fileName,bool isDeleted)
        {
            _filePath = filePath;
            _fileName = fileName;
            _isDeleted = isDeleted;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            string fileName = _fileName;
            HttpResponseBase response = context.HttpContext.Response;
            
            if (System.IO.File.Exists(_filePath))
            {
                FileStream fs = null;
                byte[] fileBuffer = new byte[1024];//每次讀取1024字節大小的數據
                try
                {
                    using (fs = System.IO.File.OpenRead(_filePath))
                    {
                        long totalLength = fs.Length;
                        response.ContentType = "application/octet-stream";
                        response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName));
                        while (totalLength > 0 && response.IsClientConnected)//持續傳輸文件
                        {
                            int length = fs.Read(fileBuffer, 0, fileBuffer.Length);//每次讀取1024個字節長度的內容
                            fs.Flush();
                            response.OutputStream.Write(fileBuffer, 0, length);//寫入到響應的輸出流
                            response.Flush();//刷新響應
                            totalLength = totalLength - length;
                        }
                        response.Close();//文件傳輸完畢,關閉相應流
                    }
                }
                catch (Exception ex)
                {
                    response.Write(ex.Message);
                }
                finally
                {

                    if (fs != null)
                    {
                        fs.Close();//最后記得關閉文件流
                        if (_isDeleted)
                        {
                            System.IO.File.Delete(_filePath);
                        }
                    }
                }
            }
        }
    }
View Code

如果附件比較大,例如超過一個GB,則需要分塊下載

        /// <summary>
        ///  Response分塊下載,輸出硬盤文件,提供下載 支持大文件、續傳、速度限制、資源占用小
        /// </summary>
        /// <param name="fileName">客戶端保存的文件名</param>
        /// <param name="filePath">客戶端保存的文件路徑(包括文件名)</param>
        /// <returns></returns>
        /// <summary>
        /// 使用OutputStream.Write分塊下載文件  
        /// </summary>
        /// <param name="filePath"></param>
        public void PostDownFile(string path, string name)
        {
            string fileName = name;
            LogHelper.WriteTextLog("讀取文件", path, DateTime.Now);
            path = EncodeHelper.DefaultDecrypt(path);
            LogHelper.WriteTextLog("讀取文件成功", path, DateTime.Now);
            var ef = CfDocument.GetByCondition("Url='" + path + "'").FirstOrDefault();
            //防止帶有#號文件名
            if (ef != null && !string.IsNullOrEmpty(ef.Documentname))
            {
                fileName = ef.Documentname;
            }
            var filePath = HttpContext.Current.Server.MapPath("~" + path);
            if (!File.Exists(filePath))
            {
                return;
            }
            FileInfo info = new FileInfo(filePath);
            //指定塊大小   
            long chunkSize = 4096;
            //建立一個4K的緩沖區   
            byte[] buffer = new byte[chunkSize];
            //剩余的字節數   
            long dataToRead = 0;
            FileStream stream = null;
            try
            {
                //打開文件   
                stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);

                dataToRead = stream.Length;

                //添加Http頭   
                HttpContext.Current.Response.Charset = "UTF-8";
                HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("UTF-8");
                HttpContext.Current.Response.ContentType = "application/octet-stream";
                HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpContext.Current.Server.UrlEncode(fileName));
                while (dataToRead > 0)
                {
                    if (HttpContext.Current.Response.IsClientConnected)
                    {
                        int length = stream.Read(buffer, 0, Convert.ToInt32(chunkSize));
                        HttpContext.Current.Response.OutputStream.Write(buffer, 0, length);
                        HttpContext.Current.Response.Flush();
                        HttpContext.Current.Response.Clear();
                        dataToRead -= length;
                    }
                    else
                    {
                        //防止client失去連接   
                        dataToRead = -1;
                    }
                }
            }
            catch (Exception ex)
            {
                HttpContext.Current.Response.Write("Error:" + ex.Message);
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
                HttpContext.Current.Response.Close();
            }

        }
View Code

只需將上述方法寫在webApi中

@陳卧龍的博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM