js实现文件切片上传 断点续传

2018-04-02 06:24:13断点续传, javascriptjs

思路很简单,拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕。

以下文字没有完整的代码,只有基础理论,伸手党绕道。

读取文件

var input = document.querySelector('input');
input.addEventListener('change', function() {
    var file = this.files[0];
});

文件唯一性

var md5code = md5(file);

文件切割

var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
    //每10M切割一段,这里只做一个切割演示,实际切割需要循环切割,
    var slice = e.target.result.slice(0, 10*1024*1024);
});

h5上传一个(一片)文件

var formdata = new FormData();
formdata.append('0', slice);
//这里是有一个坑的,部分设备无法获取文件名称,和文件类型,这个在最后给出解决方案
formdata.append('filename', file.filename);
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
    //xhr.responseText
});
xhr.open('POST', '');
xhr.send(formdata);
xhr.addEventListener('progress', updateProgress);
xhr.upload.addEventListener('progress', updateProgress);

function updateProgress(event) {
    if (event.lengthComputable) {
        //进度条
    }
}

无法获取文件类型的设备解决方案

首先在:http://www.garykessler.net/library/file_sigs.html查找对应文件的头信息
这里只给出了常见的图片和视频的文件类型判断

function checkFileType(type, file, back) {
    /**
     * type png jpg mp4 ...
     * file input.change=> this.files[0]
     * back callback(boolean)
     */
    // http://www.garykessler.net/library/file_sigs.html
    var args = arguments;
    if (args.length != 3) {
        back(0);
    }
    var type = args[0]; // type = '(png|jpg)' , 'png'
    var file = args[1];
    var back = typeof args[2] == 'function' ? args[2] : function() {};
    if (file.type == '') {
        // 如果系统无法获取文件类型,则读取二进制流,对二进制进行解析文件类型
        var imgType = [
            'ff d8 ff', //jpg
            '89 50 4e', //png

            '0 0 0 14 66 74 79 70 69 73 6F 6D', //mp4
            '0 0 0 18 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 4D 53 4E 56', //mp4
            '0 0 0 0 66 74 79 70 69 73 6F 6D', //mp4

            '0 0 0 18 66 74 79 70 6D 70 34 32', //m4v
            '0 0 0 0 66 74 79 70 6D 70 34 32', //m4v

            '0 0 0 14 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 6D 6F 6F 76', //mov

            '4F 67 67 53 0 02', //ogg
            '1A 45 DF A3', //ogg

            '52 49 46 46 x x x x 41 56 49 20', //avi (RIFF fileSize fileType LIST)(52 49 46 46,DC 6C 57 09,41 56 49 20,4C 49 53 54)
        ];
        var typeName = [
            'jpg',
            'png',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'm4v',
            'm4v',
            'mov',
            'mov',
            'mov',
            'ogg',
            'ogg',
            'avi',
        ];
        var sliceSize = /png|jpg|jpeg/.test(type) ? 3 : 12;
        var reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.addEventListener("load", function(e) {
            var slice = e.target.result.slice(0, sliceSize);
            reader = null;
            if (slice && slice.byteLength == sliceSize) {
                var view = new Uint8Array(slice);
                var arr = [];
                view.forEach(function(v) {
                    arr.push(v.toString(16));
                });
                view = null;
                var idx = arr.join(' ').indexOf(imgType);
                if (idx > -1) {
                    back(typeName[idx]);
                } else {
                    arr = arr.map(function(v) {
                        if (i > 3 && i < 8) {
                            return 'x';
                        }
                        return v;
                    });
                    var idx = arr.join(' ').indexOf(imgType);
                    if (idx > -1) {
                        back(typeName[idx]);
                    } else {
                        back(false);
                    }

                }
            } else {
                back(false);
            }

        });
    } else {
        var type = file.name.match(/\.(\w+)$/)[1];
        back(type);
    }
}

调用方法
checkFileType('(mov|mp4|avi)',file,function(fileType){
    // fileType = mp4,
    // 如果file的类型不在枚举之列,则返回false
});

上面上传文件的一步,可以改成:

formdata.append('filename', md5code+'.'+fileType);

总结:有了切割上传,有了文件唯一标识信息(文件md5)断点续传只不过是后台的一个小小的判断逻辑而已。
##未来,前端,大有可为


有些小伙伴不是太清楚后台的小小的判断是怎么做的:
这里贴一张图给大家参考,自己手画,有点丑,将就下。

clipboard.png

后续我再画一张完整的上传流程图给更新上来。

热门评论:

  • ttwx0011 ttwx0011 2017-11-14 9:42 回复:

    赞,请问计算文件唯一性是用的什么MD5文件

    • jsoncode jsoncode 2017-11-14 10:07 回复:ttwx0011
      md5.js

  • 向阳 向阳 2018-4-2 5:49 回复:

    您好,您的文章很棒,前台文件切片我可以理解,但是后台您说断点续传只是一个小小的逻辑判断,我不太懂,您能解答一下吗

    • jsoncode jsoncode 2018-4-2 6:11 回复:向阳
      你好,很感谢你的认可。后台小小的逻辑判断,指的就是,根据前端传给后台的md5值,到服务器磁盘查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片),取到之后根据上传切片的数量,返回数据告诉前端开始从第几节上传。这样就节省了前面已上传的切片重复上传的问题。

    • jsoncode jsoncode 2018-4-2 6:24 回复:向阳
      文章已更新,我加了一张说明图,你参考下

    • 向阳 向阳 2018-4-2 6:28 回复:向阳

      @jsoncode 恩,也就是说每次上传切片之前需要先请求接口已经上传到第几个切片了是吗


  • xianshenglu xianshenglu 2018-8-4 6:10 回复:

    亲测直接切割 this.files[0] 就可以了,切割 arrayBuffer 对象上传反而会错误,因为 arrayBuffer 对象会被转成字符串,传 Blob 对象就没问题

    • jsoncode jsoncode 2018-8-4 7:36 回复:xianshenglu
      这个方法只能切割字符串,你说的那种流传输,要么切割后转base64要么切割前转

    • xianshenglu xianshenglu 2018-8-4 7:40 回复:xianshenglu

      @jsoncode 直接用 this.files[0].slice 就可以了,传的是 Blob 对象,后台可以合并


  • 吴疆 吴疆 2018-11-20 11:57 回复:

    请问后台怎么接收这个分片的arraybuffer?

    • jsoncode jsoncode 2018-11-21 1:31 回复:吴疆
      我是前端,后端的事交给后台 好了

    • 吴疆 吴疆 2019-1-22 9:42 回复:吴疆

      OK,我已经解决了


  • valorayan valorayan 2019-6-21 9:32 回复:

    后台能判读已经传过的总片数,如果文件切分了10片,点击暂停,后台接收到5片分片,但是这5片的分片索引不一定就是0,1,2,3,4,由于网速和http传输的影响,上传过的分片索引也有可能是0,1,2,4,5,这种问题怎么解决


  • jsoncode jsoncode 2019-6-28 4:09 回复:
    根据服务器缓存的文件列表进行对比就行了

  • Best_Yu Best_Yu 2019-7-31 11:43 回复:

    请问一下您是怎么实现手动停止上传,然后再去进行断点续传的,Ajax 的请求貌似不能中断吧,小白请教。

    • jsoncode jsoncode 2019-8-2 2:48 回复:Best_Yu
      我文章里说了,分片上传,分片上面意思你知道把,就是前一片上传完成,再调用ajax继续上传下一片。那么再上传下一片时,你先判断一下是否被暂停就行了。如果是已经在上传过程中的那一片,也可以通过ajax的abort()方法来实现停止。这个停止表示终止本次请求。

  • Best_Yu Best_Yu 2019-8-2 2:54 回复:

    我试试。谢谢


  • 许GZIom 许GZIom 2020-9-18 10:52 回复:

    www.html5upload.com

    1)支持断点续传
    2)支持直接拖拉文件到浏览器中,更新上传队列
    3)支持上传文件夹,并保持目录结构
    4)服务器端以文件的md5值作为文件名存储文件
    5)支持与服务器端文件进行完整文件md5校验
    6)支持跨域上传
    7)客户端无需安装任何插件,HTML5upload以jQuery插件形式进行调用
    8)适用于任何服务器端平台(PHP,c#, Python, Ruby on Rails, Java, Node.js, 等...)
    9)阿里云版,支持Web直传oss
    10)Electron版,支持保持未上传完成对文件队列。

    • jsoncode jsoncode 2020-9-20 12:54 回复:许GZIom
      同学,你回复这么多解决方案是什么意思呢?我写的不如他们的好用,还是既然有现成的了,我就不用这么费劲在这讲解了?我没看懂你的意思。我只做个人技术免费分享。如果你有好的建议请直接说。


  • Aries Aries 2021-10-28 9:38 回复:

    为什么我iphone上的mp4上传以后会变成mov呢?

    • jsoncode jsoncode 2021-11-1 11:04 回复:Aries
      苹果设备拍摄出的视频,就是mov格式的,不是mp4,如果显示名字是mp4,那也仅仅是名字是mp4,实际编码格式仍然是mov.

  • L、  华? L、 华? 2021-12-13 9:52 回复:

    简洁明了,写太多太啰嗦反倒看不懂,赞