zoukankan      html  css  js  c++  java
  • 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=" 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;  94px; height: 41px; overflow: hidden; bottom: auto; right: auto;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple"><label style="opacity: 0;  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中

    @陈卧龙的博客

  • 相关阅读:
    又玩起了“数独”
    WebService应用:音乐站图片上传
    大家都来DIY自己的Blog啦
    CSS导圆角,不过这个代码没有怎么看懂,与一般的HTML是不同
    网站PR值
    CommunityServer2.0何去何从?
    网络最经典命令行
    炎热八月,小心"落雪"
    Topology activation failed. Each partition must have at least one index component from the previous topology in the new topology, in the same host.
    SharePoint 2013服务器场设计的一些链接
  • 原文地址:https://www.cnblogs.com/chenwolong/p/webuploader.html
Copyright © 2011-2022 走看看