Skip to content
This repository has been archived by the owner on Aug 22, 2022. It is now read-only.

如何实现秒传与断点续传 #142

Open
2betop opened this issue Apr 10, 2014 · 96 comments
Open

如何实现秒传与断点续传 #142

2betop opened this issue Apr 10, 2014 · 96 comments

Comments

@2betop
Copy link
Contributor

2betop commented Apr 10, 2014

因为这是小众需求,所以默认没有做在webuploader里面,而只是提供hook接口,让用户很简单的扩展此功能。

那么,都有哪些重要的hook接口呢?

  • before-send-file 此hook在文件发送之前执行
  • before-file 此hook在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行。
  • after-send-file 此hook在文件所有分片都上传完后,且服务端没有错误返回后执行。
  • ...

对于秒传来说,其实就是文件上传前,把内容读取出来,算出md5值,然后通过ajax与服务端验证进行验证, 然后根据结果选择继续上传还是掉过上传。

像这个操作里面有两个都是异步操作,文件内容blob读取和ajax请求。所以这个handler必须是异步的,怎样告诉组件此handler是异步的呢?只需要在hanlder里面返回一个promise对象就可以了,这样webuploader就会等待此过程,监听此promise的完成事件,自动继续。

以下是此思路的简单实现。

Uploader.register({
    'before-send-file': 'preupload'
}, {
    preupload: function( file ) {
        var me = this,
            owner = this.owner,
            server = me.options.server,
            deferred = WebUploader.Deferred();

        owner.md5File( file.source )

            // 如果读取出错了,则通过reject告诉webuploader文件上传出错。
            .fail(function() {
                deferred.reject();
            })

            // md5值计算完成
            .then(function( md5 ) {

                // 与服务安验证
                $.ajax(server, {
                    dataType: 'json',
                    data: {
                        md5: ret
                    },
                    success: function( response ) {

                        // 如果验证已经上传过
                        if ( response.exist ) {
                            owner.skipFile( file );

                            console.log('文件重复,已跳过');
                        }

                        // 介绍此promise, webuploader接着往下走。
                        deferred.resolve();
                    }
                });
            });

        return deferred.promise();
    }
});

关于断点续传

其实就是秒传分片,跟秒传整个文件是一个思路。关于md5验证这块,可以ajax请求验证,也可以在文件秒传验证的时候,把已经成功的分片md5列表拿到,这样分片验证的时候就只需要本地验证就行了,减少请求数。

具体实现和思路请查看这里#139

@2betop
Copy link
Contributor Author

2betop commented Apr 10, 2014

代码中 md5File 的调用说明,请查看这: http://fex.baidu.com/webuploader/doc/index.html#WebUploader_Uploader_md5File

@litterGuy
Copy link

md5Blob 在ie下无法使用 有没有什么好的解决办法?

@2betop
Copy link
Contributor Author

2betop commented Apr 23, 2014

恩,ie下确实不能用,只能扩展flash在那个里面实现。

@litterGuy
Copy link

filereader 已经实现在swf内了 想问下 有没有可以直接在外面用js调用读取文件的方法(不会改flash啊)

@2betop
Copy link
Contributor Author

2betop commented Apr 23, 2014

fileReader确实可以把文件内容读取出来,但是把内容读取出来再交给JS来md5,效率肯定会很慢的。最好还是flash直接把md5值算好了把结果交给js。

以后如果这块需求比较我可以考虑加上此功能。

ps:文库那边发现可以编译c/c++版本的md5代码给flash调用,md5速度比html5的速度快出很多。但是文件读取速度html5又比flash快,所以对于一个文件读取+md5运算总体时间花费基本上差不多。

@anota
Copy link

anota commented May 5, 2014

md5Blob 找不到Deferred方法 是依赖了什么库么

@2betop
Copy link
Contributor Author

2betop commented May 5, 2014

我直接使用的jQuery的Deferred,你没引入吗?

@anota
Copy link

anota commented May 5, 2014

好吧 哪应该写错了吧 好像是$.deferred()吧。

@2betop
Copy link
Contributor Author

2betop commented May 5, 2014

对,已更新https://gist.github.com/2betop/10399507

@cloudsiu
Copy link

cloudsiu commented May 7, 2014

请问在IE如何获取到文件的md5码? md5Blob 在IE下报错! 找不到FileReader 对象

@2betop
Copy link
Contributor Author

2betop commented May 7, 2014

IE里面只能用flash实现,可以自己扩展flash实现md5功能,目前没有此功能。

@cloudsiu
Copy link

cloudsiu commented May 7, 2014

也就是说IE环境下没法实现断点续传功能? 还是说有其他方法 因为本人不懂Flash!
还有刚才在使用Hook before-file是 返回的file对象访问source时返回的是undefind 无法给分块产生MD5

@2betop
Copy link
Contributor Author

2betop commented May 7, 2014

对,目前 IE 下无法实现断点续传,除非把 flash 版本的 md5 计算模块扩展到 webuploader 中去。

@cloudsiu
Copy link

cloudsiu commented May 7, 2014

那请问往后的版本会考虑拓展这个功能吗? 被IE无法支持断点续传功能困扰很久了!麻烦大神能指点迷津!

This was referenced May 15, 2014
@yuanhaibo
Copy link

md5 用这个
https://github.com/satazor/SparkMD5

@cloudsiu
Copy link

cloudsiu commented Jun 9, 2014

请问我在使用上述的方法进行MD5码的获取时,浏览器依旧被阻塞住了! 请问有何办法可以解决此问题!

@2betop
Copy link
Contributor Author

2betop commented Jul 1, 2014

自 0.1.4 后 已添加 md5 模块,包括 flash 和 html5 两个实现。

@PerterPon
Copy link

看这个issue的意思,断点续传也是分多个请求实现的么?

@2betop
Copy link
Contributor Author

2betop commented Jul 16, 2014

恩,基于 chunk 上传的。

@PerterPon
Copy link

好吧,目前有办法能在一个请求里面实现么?仅针对chrome而且,不考虑兼容新的话

@2betop
Copy link
Contributor Author

2betop commented Jul 16, 2014

不 chunk 的话,其实后端很难知道已经接收了多少的。像 php 都是等你的上传文件全部存入临时文件后才执行到脚本里面来。你每次接受文件,要不就是进度为0,要不进度为 100%。

@2betop
Copy link
Contributor Author

2betop commented Jul 16, 2014

还是我理解错你的意思了,你是不想每次 chunk 上传文件的时候都做 ajax 验证?还是压根就不想 chunk?
如果只是担心 不想 chunk的时候多发一次 ajax 验证请求的话,其实第一次就可以把当前文件所有已成功的分块 sign 取到,分块验证的时候本地验证就行了。

@wwg88888888
Copy link

@2betop md5 能停止扫描码?2G的文件,选错了,这停止都停止不掉

@mercurychs
Copy link

你这个妙传文件的时候,按照你的思路,先算md5,然后与服务端对比,但是如果服务端没有的话,因为你的流数据已经读过一次,不能重复再读,你怎么把数据再传到服务器。小文件可以在算md5的时候,这样数据变成byte[]数组是可以得,但是大文件肯定不行,不知道你这个大文件妙传能支持多大。或者还是有别的解决方案。

@litingtingting
Copy link

我是利用那个hash值来进行比对,算md5太慢了, 我自己改了三处代码 , 就能实现前端的断点续传的支持。 但现在有个新的问题,就是服务器端给我明明是有数据的, 但在uploadSuccess监听的事件中有时候却取不到服务端的值。为什么呢 ? 感觉像是在上传完成前就执行了uploadSuccess这个事件。

@WillZeroman
Copy link

分片上传有两个问题:
1)md5校验时太慢了
2)后台多线程同步问题很复杂,怎么让多个分片按顺序写到同一文件中?

@litingtingting
Copy link

@lw394407679 webuploader是利用多线程上传, 而且上传时是没有顺序的, 所以我先将分块的文件放在一个独立的临时目录中, 当上传完成后,再进行合并。 而不是写到同一文件中(除非你将线程数改为1),

@Hibear
Copy link

Hibear commented Aug 14, 2015

@lw394407679 你没遇到分片上传点击暂停又继续上传,会丢失分片的情况吗?

@lvmn
Copy link

lvmn commented Aug 26, 2015

flash分片上传按理应该内存不会暴增因此我分为多个小片上传,为何不会释放,是不是flash问题?

@luoyehanfei
Copy link

关于断点续传与秒传的总结:
1、首先说说pupload这个插件,用过它的都知道,其实webuploader的功能与它有惊人的相似之处,但是pupload是不带md5验证的,得自己去计算。这几天在研究的过程中发现,pupload在HTML5下利用FileRead读取md5时是没有文件大小限制的,但是在flash下,flash实际上是调用的Moxie.swf接口。

这里着重说说flash的断点续传与秒传(同时也希望开发团队能够参考参考):

当文件大小在100M以下时:读取文件流然后利用spark-md5去计算出MD5是没有问题的。

那么,当文件在100M以上,甚至1G,10G,100G的时候怎么办呢?就算是HTML5模式下去计算这样的文件耗时也非常严重,有时候无法忍受了。

于是我想着能不能读取文件的开始字节+结束字节然后组合成 一个伪造的md5去进行验证呢?

可以想象一下,假设文件有351M,我以5M的区间去读取 0-5M的数据,然后346-351的数据,最终组合成一个MD5,这样在(99.9%)常规情况下,算出来的md5值也是唯一的,基本不会出现重复的。

于是我想:既然可以利用这种方式:那么应该在HTML5与Flash 下都能算出md5了,因为这样实际上我只读取了10M的内容。甚至这个值可以设置成1M一个区间,那么2M的内容算MD5,不会超过20ms。

但是:最后经过实现发现,pupload在html5时这样读取是没有任何问题,可以读取并算出md5值的,但是在flash下,超过100M文件时在读取的时候还是报错了。(这个我查阅资料说是flash fileread instance单个实例只支持100M的文件,所以读取会报错)

昨天我正在纠结这个问题的时候偶然发现国产大作 webuploader这款上传插件,于是信心满满的又开始测试了,webuploader自带md5计算方法

uploader.on('fileQueued', function (file) {
uploader.md5File(file, 0, 5242880).progress(function (percentage) {
$("#thelist").append("

进度:" + percentage + "
")
}).then(function (val) {
alert('md5 result:' + val);
});
$("#thelist").append('
' + '

' + file.name + '

' + '

等待上传...

' + '
');
});

利用 uploader.md5File(file,start,end)方法,我们轻易可以计算出区间的md5,也可以计算完整的md5。

但是.....................在flash模式下(用ie8),又发现了相同的问题,在文件超过100m时同样计算不出md5值,并且假设文件是50g 直接 uploader.md5File(file) 与 uploader.md5File(file,0,"1mb") 所计算的耗时是一样的。

现在初步推测原因是flash的fileread instance 造成的,并且flash在读取区间数据时应该还是先把整个文件加载到内存,从内存中取得的,所以计算整个文件的md5与取一个区间是没有区别的,但是flash这块不了解,也不敢轻易改造源码。

希望webuploader开发团队能在这块上有个崭新的突破,那么做到真正的全兼容 断点续传、秒传也就不远了。

@litingtingting
Copy link

我感觉对文件内容做md5加密 ,不如对文件名,文件大小,文件修改时间等文件信息合并做一个hash值,这种情况发生冲突的概念也是相当小的。我做断点续传的秒传思路与原作者不同, 看了源代码:代码没有对每一块分块上传完成后的事件,若某个分块没有实际保存上,就坑了, 并且每一次分块上传都得去做ajax验证,而且skipfIle这个函数是跳过整个文件, 我做了些改造,能实现在整个文件开始上传前,我去获取已传上传完成的chunk, 然后从缺的分块开始上传,只要是上传过的分块就直接跳过。

@luoyehanfei
Copy link

楼上这种做法早在几年前用pupload试过,不适用,做出来毫无意义,打个比方用户A上传了一份SQL SERVER安装包,他电脑的时间,文件名等可能和 B用户电脑上的不相同,当B用户又上传一份SQL SERVER安装包时,你无法确定已经穿过了,做不了秒传,最多只能做针对“同一份文件”的断点续传。

@litingtingting
Copy link

不是毫无意义,也有一种情况用户在文件的内容后追加了一些内容, 用md5做加密, 得到的值还是不同的,实际上他们相似度可能99.99%, 所以就取决于 对“同一份文件”的定义了, 我这边的应用场景更多的是针对某一个用户的上传,他上次上传了55%,这次要接着传,我说的思路更多是“断”点“续”传,不是秒传。

@zz13761743
Copy link

分片上传功能已实现,但是暂停上传后再次触发上传方法时,上传进度又从0开始上传了

@Davis-Wong
Copy link

uploader.on( 'startUpload', function( file ) {
uploader.md5File( file )
.then(function(val) {
console.log('md5 result:', val);
$md5 = val;
});
});
为什么会报错呢?加入列队后fileQueued能正常执行,但是startUpload状态确保错 求大师讲解

@AngieJames
Copy link

@beyond290239 @sky20054122
我遇到个这么问题,断点续传做分片上传的时候,我设置了4个线程一起上传,有的用户上传失败,很小一部分用户存在这个问题,偶发问题,查log是分4个线程后,分片文件总数小于四个,并发时有的上传请求就丢了,没有请求到server,导致上传失败。自己本地自测很多遍没有重现,怎么解?
有人遇到过类似的问题么,还有就是当分片文件总数小于线程数时会有无效的请求。即空参数的请求。

@litingtingting
Copy link

我遇到的情况是 有分块丢失的情况, 但不清楚是因为没有发起请求,还是服务端的问题。 我也是自己在本地自测测了很多次没有复现,所以后来我在一个文件上传成功之后 ,去 上传服务器 检测一下,判断是否真正上传成功。

@wangmeijian
Copy link

用uploader.stop(file)暂停单个文件上传后,用uploader.upload(file)无法继续上传,是怎么回事?

@baiyunchen
Copy link

baiyunchen commented Apr 15, 2016

14M的文件,用谷歌/火狐分片上传,好卡啊~整个上传完成差不多要五六分钟,是我哪里没有写对么?

<script type="text/javascript">
    var GUID = WebUploader.Base.guid();//一个GUID
    var uploader = WebUploader.create({
        swf: '/Scripts/Plugins/webuploader-0.1.5/Uploader.swf',
        server: '@Url.Action("Upload")',
        pick: '#picker',
        resize: false,
        chunked: true,//开始分片上传
        chunkSize: 2048000,//每一片的大小
        formData: {
            guid: GUID //自定义参数,待会儿解释
        }
    });
    var $list = $("#thelist");
    uploader.on('fileQueued', function (file) {
        $list.append('<div id="' + file.id + '" class="item">' +
            '<h4 class="info">' + file.name + '</h4>' +
            '<p class="state">等待上传...</p>' +
        '</div>');
    });
    uploader.on('uploadSuccess', function (file, response) {
        $.post('@Url.Action("Merge")', { guid: GUID, fileName: file.name }, function (data) {
            $list.text('已上传');
        });
    });
    uploader.on('uploadProgress', function (file, percentage) {
        console.log('Percentage:', percentage);
        $("#progress-bar").width(percentage * 100 + "%");
    });
    $("#ctlBtn").click(function () {
        uploader.upload();
    });
</script>

@nikorliu
Copy link

nikorliu commented Jun 7, 2016

@luoyehanfei
确实,现在需要兼容IE7/8/9支持,只有flash的方案了,但是flash计算md5确实太慢了。看NTKO的大附件上传非常的快,不知道是用了什么方案

@2betop @sky20054122 有其它办法解决吗?

@annezhong
Copy link

annezhong commented Jun 21, 2016

IE7/8/9 计算MD5还是不兼容吗?
或者是我没找对方法?

@jaanio
Copy link

jaanio commented Jul 22, 2016

怎么在外部添加个文件到上传队列呢?

@jaanio
Copy link

jaanio commented Jul 22, 2016

@2betop 怎么在外部添加个文件到上传队列呢?

@JongShau
Copy link

当我切换到IE9下时,选择文件按钮失效,我在webuploader.js 源码中调用flash的位置alert了一下,发现alert没反应,而在IE10和IE11下则没问题,因此确定flash根本没有加载,对于此问题楼主有没有解决之道

@wangjunjx8868
Copy link

@2betop
uploader.register({
'before-send' : 'checkchunk'
}, {
checkchunk : function(block) {

用了上面的HOOK的before-send上传前分段检测MD5,
下面的方法的进度条显示percentage一直是0
uploader.on('uploadProgress', function (file, percentage) {
console.log('Percentage:', percentage);
$("#progress-bar").width(percentage * 100 + "%");
});

@liuweitao2
Copy link

为什么我owner.md5File(file)后返回是undefined呢?

@diskrooms
Copy link

有一个问题 服务器如何得知这个文件的md5 难道把上传目录的所有文件全部md5 一遍吗 如果有多个上传目录呢

@LSL1618
Copy link

LSL1618 commented Jan 15, 2019

@diskrooms ,肯定是前端计算所有文件MD5值,然后发送到后端,与后端计算的MD5值匹配。要比较必须要有来源和目标。

@gkm1987
Copy link

gkm1987 commented Jan 21, 2019

var fileT = uploader.getFile(parentT)
uploader.stop(fileT)

Cannot read property 'file' of undefined
at HTMLSpanElement.filePause (uploadPage.html:203) 指向stop方法

@LSL1618
Copy link

LSL1618 commented Jan 22, 2019

@gkm1987 ,先确认var fileT = uploader.getFile(parentT)这一句有没有问题。

@Wigithub1201
Copy link

this.uploader.retry(continueFile);

我这里断网之后再连接调用upload方法或者retry并不续传,为什么?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests