浏览器监听文件下载实现
之前在做类似下载功能的时候,很少会考虑那些优化问题。直接一个超链接了事。
然后。。。我遇到了两个坑。
坑1,A项目中的导出按钮,测试妹纸在火狐浏览器上死活点不出来,而我自己的火狐上是正常的,最后我发现,是迅雷下载插件惹的祸T。T
坑2,B项目中,某运营连续点了10次导出按钮,然后。。。网站变得很卡。
对于坑1,我们可以用iframe去实现下载。
$('#J-download').on('click',function(){
var iframe=$('<iframe />').attr('src', url).attr('id','iframe_download_report').hide().appendTo('body');
});
而对于坑2,
首先我需要做连续点击的限制:
利用setTimeout设置一个触发延时,1秒以内的连续点击都只算做一次。但是事实上很少会有这样的恶意点击,这种控制就很显得很鸡肋。
而最重要的一点就是做到“waiting”效果,点击下载按钮后就禁用该按钮,同时按钮文本显示“下载中”,等到浏览器开始下载,再启用该按钮,重置按钮文本。
所以问题的关键就是如何监听到文件下载。
最开始的思路想到了iframe的onload事件,iframe加载html页面,确实会触发onload事件,但是下载却不会。又想到了异步下载,也被我否决。第二天开会的时候,我问到这个问题的解决办法,一位后端提到他们已有解决方案就是使用cookie,豁然开朗。想到之前百度时在stack overflow上被我匆匆一瞥忽略的cookie和token,原来答案很早就有了冏rz
思路大致是这样:
前端生成一个唯一的token,以get方式随url传给后端。后端将token写进cookie中,而前端通过定时器获取,然后核对前端生成的token和通过cookie获取的token值是否一致。
生成唯一的token,这个可以用new Date()结合随机数做:
_setFormToken:function(){ return ""+(Math.random()*1024|0)+new Date().getTime(); }
禁用启用链接,因为超链接不支持disabled属性,我们可以操作它的href属性~~
其他的就很简单了,只要检测到token一致,就启用链接,然后移除iframe。如果没有获取到token或者token不一致,就继续setTimeout。
_iframeDownLoad:function(){ var timer,_this=this,flag=0; $(document).on('click','.J_DownLog',function(){ var downloadToken=_this._setFormToken(), url=this.href+"&downloadToken="+downloadToken, triggerDelay = 1000, btn=$(this); _this._disableLink(btn,"导出中..."); clearTimeout(timer); timer=setTimeout(function() { function checkToken(){ sertoken=_this._getCookie( "downloadToken" ); if(sertoken==downloadToken){ clearTimeout( downloadTimer ); _this._expireCookie( "downloadToken" ); frame.remove(); _this._enableLink(btn,"导出日志"); flag=0; }else{ checkToken(); } } if(!flag){ flag=1; var frame=$('<iframe />').attr('src', url).attr('id','iframe_download_report').hide().appendTo('body'); var downloadTimer=setTimeout(checkToken,1000); } }, triggerDelay); return false; }); }
-----------------后续------------------
2015.04.28
模拟了下低网速的情况,发现浏览器获取token与弹窗下载对话框之间有时间差,如果每次都是点击导出时添加新的iframe/导出后移除iframe,就会产生iframe过早移除的情况,这样下载对话框就弹不出来了。所以,需要修改为第一次生成iframe,之后都是修改iframe的地址。