JQ+asp.net实现文件上传的断点续传功能

一、功能原理

断点续传,顾名思义就是将文件分割成一段段的过程,然后一段一段的传。

以前文件无法分割,但随着HTML5新特性的引入,类似普通字符串、数组的分割,我们可以可以使用slice方法来分割文件。

所以断点续传的最基本实现也就是:前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。

同时,将传送的进度记录记录到浏览器的缓存中。每次传送数据都更新浏览器的缓存

二、实现过程

  1、前端代码

           <table id="tbResult" class="table table-normal">
                <thead>
                    <tr>
                        <td>序号</td>
                        <td>地图名称</td>
                        <td>地图版本</td>
                        <td>地图类型</td>
                        <td>类型名称</td>
                        <td>创建时间</td>
                        <td style="width:250px;">操作</td>
                    </tr>
                </thead>
                <tbody>
                    <tr index="0">
                    </tr>
                </tbody>
            </table>

  2、计算文件的大小

 // 计算文件大小
       var size = file.size > 1024
                file.size / 1024 > 1024
                ? file.size / (1024 * 1024) > 1024
                ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
                : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
                : (file.size / 1024).toFixed(2) + 'KB'
                : (file.size).toFixed(2) + 'B';

  3、选择文件后显示文件的信息,在模版中替换一下数据

            var fileList = "";
            var uploadVal = '开始上传';
            var files = document.getElementById('myFile').files;
            fileCount = files.length;
            if (files) {
                for (var i = 0, j = files.length; i < j; ++i) {
                    var file = this.files[i];
                    // 计算文件大小
                    var size = file.size > 1024
                        ? file.size / 1024 > 1024
                            ? file.size / (1024 * 1024) > 1024
                                ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
                                : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
                            : (file.size / 1024).toFixed(2) + 'KB'
                        : (file.size).toFixed(2) + 'B';
                    // 初始通过本地记录,判断该文件是否曾经上传过
                    var percent = window.localStorage.getItem(file.name + '_p');
                    if (percent && percent !== '100.0') {
                        uploadVal = "继续上传"
                    }
                    fileList += "<tr><td>" + file.name + "</td><td>" + file.type + "</td><td>" + size + "</td><td class='upload-progress'>" + percent + "</td><td><div class='upload-item-btn' data-name='" + file.name + "' data-size='" + file.size + "' data-state='default' style='display:none'>" + uploadVal + "</div></td></tr>";
                }
            }
            $("#fileList").append(fileList);

  4、不过,在显示文件信息的时候,可能这个文件之前之前已经上传过了,为了断点续传,需要判断并在界面上做出提示通过查询本地看是否有相应的数据(这里的做法是当本地记录的是已经上传100%时,就直接是重新上传而不是继续上传了) 

            // 初始通过本地记录,判断该文件是否曾经上传过
                    var percent = window.localStorage.getItem(file.name + '_p');
                    if (percent && percent !== '100.0') {
                        uploadVal = "继续上传"
                    }

  5、显示文件信息列表

   

 

 

      6、点击开始上传,可以上传相应的文件

          var $this = $(this);
                var fileName = $this.attr('data-name');
                var totalSize = $this.attr('data-size');
                var eachSize = 1024 * 1024;
                var chunks = Math.ceil(totalSize / eachSize);
                var $progress = $this.closest('tr').find('.upload-progress')

  7、接下来是分段过程

            // 上传之前查询是否以及上传过分片
                    var chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
                    chunk = parseInt(chunk, 10);
                    // 判断是否为末分片
                    var isLastChunk = (chunk == (chunks - 1) ? 1 : 0);
                    // 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传
                    if (times === 'first' && isLastChunk === 1 && totalSize > eachSize) {
                        window.localStorage.setItem(fileName + '_chunk', 0);
                        chunk = 0;
                        isLastChunk = 0;
                    }
                    // 设置分片的开始结尾
                    var blobFrom = chunk * eachSize, // 分段开始
                        blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段结尾
                        percent = (100 * blobTo / totalSize).toFixed(1), // 已上传的百分比
                        timeout = 5000, // 超时时间
                        fd = new FormData($('#myForm')[0]);


                    fd.append('json', JSON.stringify(record)); // 文件名
                    fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件(实际上传递的就是这个文件)
                    fd.append('fileName', fileName); // 文件名
                    //fd.append('totalSize', totalSize); // 文件总大小
                    fd.append('isLastChunk', isLastChunk); // 是否为末段
                    //fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上传)
                    fd.append('chunks', chunks); // 总片段
                    fd.append('chunk', chunk); // 当前片段

  8、AJAX上传  

$.ajax({
                        url: serviceBaseUrl + URL.ADD_MapManage,
                        type: 'POST',
                        data: fd,
                        async: true,  
                        //cache: false,  
                        contentType: false,
                        processData: false
                    }).then(function (res) {
                        // 已经上传完毕
                        window.localStorage.setItem(fileName + '_p', percent);
                        if (chunk === (chunks - 1)) {

                            $progress.text('上传完毕');
                            if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {
                                $('#upload-all-btn').val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
                            }
                            uploadCount++;
                            if (uploadCount == fileCount) {
                                swal({
                                    title: "操作成功!",
                                    type: "success",
                                    text: "2秒后自动关闭。",
                                    timer: 2000,
                                    showConfirmButton: true
                                });
                                closeEdit();
                            }
                        } else {
                            // 记录已经上传的分片
                            window.localStorage.setItem(fileName + '_chunk', ++chunk);
                            $progress.text(percent + '%');
                            startUpload();
                        }
                    })

  9、完成的js逻辑如下

  

    //附件选择
        $('body').on('change', '#myFile', function (e) {
            var fileList = "";
            var uploadVal = '开始上传';
            var files = document.getElementById('myFile').files;
            fileCount = files.length;
            if (files) {
                for (var i = 0, j = files.length; i < j; ++i) {
                    var file = this.files[i];
                    // 计算文件大小
                    var size = file.size > 1024
                        ? file.size / 1024 > 1024
                            ? file.size / (1024 * 1024) > 1024
                                ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
                                : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
                            : (file.size / 1024).toFixed(2) + 'KB'
                        : (file.size).toFixed(2) + 'B';
                    // 初始通过本地记录,判断该文件是否曾经上传过
                    var percent = window.localStorage.getItem(file.name + '_p');
                    if (percent && percent !== '100.0') {
                        uploadVal = "继续上传"
                    }
                    fileList += "<tr><td>" + file.name + "</td><td>" + file.type + "</td><td>" + size + "</td><td class='upload-progress'>" + percent + "</td><td><div class='upload-item-btn' data-name='" + file.name + "' data-size='" + file.size + "' data-state='default' style='display:none'>" + uploadVal + "</div></td></tr>";
                }
            }
            $("#fileList").append(fileList);
        })

     //附件全部上传
        $('body').on('click', '#btnSaveAll', function (e) {
            // 未选择文件
            if (!$('#myFile').val()) {
                //$('#myFile').focus();
                var fd = new FormData($('#myForm')[0]);
                fd.append('json', JSON.stringify(record)); // 文件名
                $.ajax({
                    url: serviceBaseUrl + URL.ADD_MapManage,
                    type: 'POST',
                    data: fd,
                    //async: false,  
                    //cache: false,  
                    contentType: false,
                    processData: false
                }).then(function (res) {
                    swal({
                        title: "操作成功!",
                        type: "success",
                        text: "2秒后自动关闭。",
                        timer: 2000,
                        showConfirmButton: true
                    });
                    closeEdit();
                })
            }
            // 模拟点击其他可上传的文件
            else {
                $('#upload-list .upload-item-btn').each(function () {
                    $(this).click();
                    uploadCount = 0;
                });
            }
        })
    
    
     //文件单个上传功能
        $('body').on('click', '.upload-item-btn', function () {
            if (inputValidator !== undefined) {
                inputValidator.checkValidity();
            }
            if ($(".validContainer input.invalid").length == 0) {
                var $this = $(this);
                var fileName = $this.attr('data-name');
                var totalSize = $this.attr('data-size');//文件的总大小
                var eachSize = 1024 * 1024;//每次上传1M的数据
                var chunks = Math.ceil(totalSize / eachSize);//一共多少片段
                var $progress = $this.closest('tr').find('.upload-progress')

                //var fileCount=document.getElementById('myFile').files;
                // 第一次点击上传
                startUpload('first');
                // 上传操作 times: 第几次
                function startUpload(times) {
                    // 上传之前查询是否以及上传过分片
                    var chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
                    chunk = parseInt(chunk, 10);
                    // 判断是否为末分片
                    var isLastChunk = (chunk == (chunks - 1) ? 1 : 0);
                    // 如果第一次上传就为末分片,即文件已经上传完成,则重新覆盖上传
                    if (times === 'first' && isLastChunk === 1 && totalSize > eachSize) {
                        window.localStorage.setItem(fileName + '_chunk', 0);
                        chunk = 0;
                        isLastChunk = 0;
                    }
                    // 设置分片的开始结尾
                    var blobFrom = chunk * eachSize, // 分段开始
                        blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段结尾
                        percent = (100 * blobTo / totalSize).toFixed(1), // 已上传的百分比
                        timeout = 5000, // 超时时间
                        fd = new FormData($('#myForm')[0]);


                    fd.append('json', JSON.stringify(record)); // 文件名
                    fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件(实际上传递的就是这个文件)
                    fd.append('fileName', fileName); // 文件名
                    //fd.append('totalSize', totalSize); // 文件总大小
                    fd.append('isLastChunk', isLastChunk); // 是否为末段
                    //fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上传)
                    fd.append('chunks', chunks); // 总片段
                    fd.append('chunk', chunk); // 当前片段
                    //$progress.text(percent + '%');
                    $.ajax({
                        url: serviceBaseUrl + URL.ADD_MapManage,
                        type: 'POST',
                        data: fd,
                        async: true,  
                        //cache: false,  
                        contentType: false,
                        processData: false
                    }).then(function (res) {
                        // 已经上传完毕
                        window.localStorage.setItem(fileName + '_p', percent);
                        if (chunk === (chunks - 1)) {

                            $progress.text('上传完毕');
                            if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {
                                $('#upload-all-btn').val('已经上传').prop('disabled', true).css('cursor', 'not-allowed');
                            }
                            uploadCount++;
                            if (uploadCount == fileCount) {
                                swal({
                                    title: "操作成功!",
                                    type: "success",
                                    text: "2秒后自动关闭。",
                                    timer: 2000,
                                    showConfirmButton: true
                                });
                                closeEdit();
                            }
                        } else {
                            // 记录已经上传的分片
                            window.localStorage.setItem(fileName + '_chunk', ++chunk);
                            $progress.text(percent + '%');
                            startUpload();
                        }
                    })
                }
            }
        })
 

三、后端实现

  

      try
            {
                HttpPostedFile file = fileCollection[0];
                //二进制数组
                byte[] fileBytes = null;
                fileBytes = new byte[file.ContentLength];
                //创建Stream对象,并指向上传文件
                Stream fileStream = file.InputStream;
                //从当前流中读取字节,读入字节数组中
                fileStream.Read(fileBytes, 0, file.ContentLength);
                //全路径(路劲+文件名)
                string timePath = DateTime.Now.ToString("yyyy") + DateTime.Now.ToString("MM");
                string fullPath = path + "Files\\Drones\\" + timePath + "\\" + fileName + "_" + chunk;
                //保存到磁盘
                var fullAllPath = Path.GetDirectoryName(fullPath);
                //如果没有此文件夹,则新建
                if (!Directory.Exists(fullAllPath))
                {
                    Directory.CreateDirectory(fullAllPath);
                }
                //创建文件,返回一个 FileStream,它提供对 path 中指定的文件的读/写访问。
                using (FileStream stream = File.Create(fullPath))
                {
                    //将字节数组写入流
                    stream.Write(fileBytes, 0, fileBytes.Length);
                    stream.Close();
                }
                //最后的片段需要将文件进行合并
                if (isLastChunk == "1")
                {
                    List<string> list = new List<string>();
                    for (int i = 0; i < Convert.ToInt32(chunks); i++)
                    {
                        //获取所有片段文件的位置
                        list.Add(path + "Files\\Drones\\" + timePath + "\\" + fileName + "_" + i);
                    }
                    int coutSize = 0;
                    //文件合并
                    using (FileStream fileNew = new FileStream(path + "Files\\Drones\\" + timePath + "\\" + fileName, FileMode.Create, FileAccess.Write))
                    {
                        int count = -1;

                        for (int i = 0; i < list.Count; i++)
                        {
                            using (FileStream readStream = new FileStream(list[i], FileMode.Open, FileAccess.Read))
                            {
                                byte[] buffer = new byte[readStream.Length];
                                coutSize += buffer.Length;
                                while ((count = readStream.Read(buffer, 0, buffer.Length)) > 0)
                                {
                                    fileNew.Write(buffer, 0, count);
                                }
                            }
                        }
                    }
                    //每次航线新增完毕都需要做查询是否有相同ID的数据,如果有就不做添加
                    var entity = JsonConvert.DeserializeObject<T_DRO_MAPMANAGEEntity>(josn);
                    int retCount = mDAL.GetCount($" ID='{entity.ID}'").Result;
                    if (retCount == 0)
                    {
                        //entity.ID = Guid.NewGuid().ToString();
                        entity.CHUANGJIAN_SJ = DateTime.Now;
                        entity.STA = "A";
                        var result = mDAL.AddData(entity, addId: true);
                    }
                    else
                    {
                        entity.STA = "U";
                        var result = mDAL.UpdateData(entity);
                    }
                    //添加附件实体
                    T_DRO_MAPFILESEntity mapfile = new T_DRO_MAPFILESEntity();
                    mapfile.ID = Guid.NewGuid().ToString();
                    mapfile.FILEPATH = "Files\\Drones\\" + timePath + "\\" + fileName;
                    mapfile.FILESIZE = coutSize.ToString();
                    mapfile.CREATETIME = DateTime.Now;
                    mapfile.PK_MAP_ID = entity.ID;
                    mapfile.SUFFIX = System.IO.Path.GetExtension(fileName);
                    mapfile.FILENAME = fileName;
                    //mapfile.State = "已完成";
                    var ret = fDAL.AddData(mapfile, addId: true);


                    //删除片段文件
                    for (int i = 0; i < list.Count; i++)
                    {
                        //获取所有片段文件的位置
                        if (File.Exists(list[i]))
                        {
                            File.Delete(list[i]);
                        }
                    }

                    //fDAL
                    return new SerializeJson<int>(Enum.ResultType.succeed, entity.ID, 1).ToString();
                }
                return "";
            }
            catch (Exception ex)
            {
                return new SerializeJson<int>(Enum.ResultType.failed, ex.Message, -1).ToString();
            }

四、最后的结果如图所

  

 

posted @ 2021-06-23 14:57  桎梏110  阅读(93)  评论(0编辑  收藏  举报
Live2D