XMLHttpRequest实现异步下载

背景

项目需要实现在下载Excel时,实时显示文件下载的进度条,不能直接调用浏览器下载。因此超链接下载,或者调用window.location.ref等方法无法满足要求,因此只有采用异步的方式。

为什么要用XMLHttpRequest

jQuery的AJAX是可以实现文件的异步上传,但无法实现异步下载,因此我们需要用到JS的XMLHttpRequest对象来实现.

XMLHttpRequest实现异步下载,我们需要将xhr的响应类型设置为blog(responseType = "blob"),在xhr的load事件中处理响应,并实现文件的保存。

下载的JS代码

jQuery(function() {
    jQuery("#DI_0027_EV02").click(function() {
        var xhr = new XMLHttpRequest();
        var url =  contextPath + DI_GUI_0027_02 + "?eventId=DI_0027_EV02&dataType="+dataType+"&deleteMode=02";
        xhr.open("GET",url);
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xhr.responseType = "blob";
        xhr.addEventListener("loadstart", function(ev) {
            // 开始下载事件:下载进度条的显示
            jQuery('div.progress-bar').css('width',"0%").find("span").text("0/0");
            jQuery('#progressModal').modal('toggle');
        });
        xhr.addEventListener("progress", function(ev) {
            // 下载中事件:计算下载进度
            var max   = ev.total;
            var value = ev.loaded;
            var width = value/max*100;
            jQuery('div.progress-bar').css('width',width+"%").find("span").text(value+"/"+max);
        });
        xhr.addEventListener("load", function(ev) {
            // 下载完成事件:处理下载文件
            processRequest(xhr);
        });
         xhr.addEventListener("loadend", function(ev) {
            // 结束下载事件:下载进度条的关闭
            jQuery('#progressModal').modal('toggle');
        });
        xhr.addEventListener("error", function(ev) {
            jQuery('#progressModal').modal('hide');
            common.showMessage(ev.error.message,false);
        });
        xhr.addEventListener("abort", function(ev) {
            jQuery('#progressModal').modal('hide');
            common.showMessage(ev.error.message,false);
        });
        xhr.send();
    });
});

通过响应的头信息获取下载的文件格式和文件名,这里我们下载的是Excel表格,根据需要更改。

处理响应内容代码

function processRequest(xhr){
    if (xhr.status == 200) { 
        var response = xhr.response;  
        var contentType = xhr.getResponseHeader("Content-Type");
        if(contentType.split(";")[0] == "application/json"){
            var reader = new FileReader();
            reader.readAsText(response);
            reader.onload = function (oFREvent) {
                common.showMessage(JSON.parse(reader.result).error.message,false);
            };
        } else if (contentType.split(";")[0] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"){
            var fileName =  xhr.getResponseHeader("content-disposition").split("UTF-8''")[1];
            saveFile(response, decodeURI(fileName))
        }
    }else{
        jQuery('#progressModal').modal('hide');
        var response = xhr.response;  
        var contentType = xhr.getResponseHeader("Content-Type")
        if(contentType.split(";")[0] == "application/json"){
            var reader = new FileReader();
            reader.readAsText(response);
            reader.onload = function (oFREvent) {
                common.showMessage(JSON.parse(reader.result).error.message,false);
            };
        }
    }
}

拿到文件格式和文件内容后,我们需要根据浏览器来实现文件的保存,这个部分花了很多时间研究。因为各个浏览器不同,因此我们需要根据不同的浏览器实现文件的保存。以下代码在Firefox,Chrome,IE和Edge中测试成功。

文件保存代码

function saveFile(blob, fileName){
    var b = getBrowser();
    if(b =="Chrome"){
        var link = document.createElement('a');
        var file = new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
        link.href = window.URL.createObjectURL(file);
        link.download = fileName;
        link.click(); 
    } else if(b =="Firefox"){
        var file = new File([blob], fileName, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
        var url = URL.createObjectURL(file);
        //window.location.href = url;
        parent.location.href = url;
    } else if(b=="IE"){
        var file = new Blob([blob], { type: 'application/force-download' });
        window.navigator.msSaveBlob(file, fileName);
    }
}

判断浏览器类型

function getBrowser() {  
    var ua = window.navigator.userAgent;  
    //var isIE = window.ActiveXObject != undefined && ua.indexOf("MSIE") != -1;  
    var isIE = !!window.ActiveXObject || "ActiveXObject" in window;
    var isFirefox = ua.indexOf("Firefox") != -1;  
    var isOpera = window.opr != undefined;  
    var isChrome = ua.indexOf("Chrome") && window.chrome;  
    var isSafari = ua.indexOf("Safari") != -1 && ua.indexOf("Version") != -1;  
    if (isIE) {  
        return "IE";  
    } else if (isFirefox) {  
        return "Firefox";  
    } else if (isOpera) {  
        return "Opera";  
    } else if (isChrome) {  
        return "Chrome";  
    } else if (isSafari) {  
        return "Safari";  
    } else {  
        return "Unkown";  
    }  
} 

到此XMLHttpRequest异步下载前台代码已全部实现。
项目用的是Java Web,框架是Spring MVC和Mybatis。

后台代码

@RequestMapping(PageUrlConstants.VP_GUI_0008_SCRATCH_EXPORT)
    public void downloadFile(HttpServletRequest request, HttpServletResponse response) throws Exception {
        init(request);
        String vnfTaskId = request.getParameter(PARAM_VNF_TASK_ID);
        String definitionBodyId = request.getParameter(PARAM_DEFINITION_BODY_ID);
        OutputStream out = null;
        // 1) 定義体・コマンドテーブルを検索する。
        Map<String, Object> result = definitionBodyDetailInputService.downloadDefinitionBodyDetailInput(vnfTaskId, definitionBodyId);
        // 2) 処理1)で取得した定義体・コマンド.データ"をxmlファイルに出力する。
        String definitionBodyName = "";
        String data = "";
        if (null != result) {
            definitionBodyName = (String) result.get(KEY_DEFINITION_BODY_NAME);
            data = (String) result.get(KEY_DATA);
        }

        // ファイル名: <1)で取得した 定義体マスタ.定義体名>_<YYYYMMDDhhmmss>.xml
        //String dateFormat = "YYYYMMDDhhmmss";
        //artf212696
        DateFormat df = new SimpleDateFormat(DatetimeConstants.DATE_FORMAT_STYLE_C);
        Date currentDate = new Date();
        String dateString = df.format(currentDate);
        String fileName = definitionBodyName + "_" + dateString + ".xml";
        if (null == data) {
            data = " ";
        }
        // 3) ファイルを、Windows端末にダウンロードするためのダウンロードダイアログを表示する。
        InputStream ins = new ByteArrayInputStream(data.getBytes());
        //artf214128
        String userAgent = request.getHeader("User-Agent");
        byte[] bytes = userAgent.contains("MSIE") ? fileName.getBytes() : fileName.getBytes("UTF-8");
        fileName = new String(bytes, "ISO-8859-1"); 
        response.setHeader("Content-disposition",String.format("attachment; filename=\"%s\"", fileName));
        //response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, ENCODE_UTF_8));
        out = response.getOutputStream();
        byte[] b = new byte[1024];
        int len = -1;
        while ((len = ins.read(b)) != -1) {
            out.write(b, 0, len);
        }
        out.flush();
        out.close();
        ins.close();
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 本文详细介绍了 XMLHttpRequest 相关知识,涉及内容: AJAX、XMLHTTP、XMLHttpReq...
    semlinker阅读 13,602评论 2 18
  • Ajax和XMLHttpRequest 我们通常将Ajax等同于XMLHttpRequest,但细究起来它们两个是...
    changxiaonan阅读 2,217评论 0 2
  • AJAX 原生js操作ajax 1.创建XMLHttpRequest对象 var xhr = new XMLHtt...
    碧玉含香阅读 3,170评论 0 7
  • 看到标题时,有些同学可能会想:“我已经用xhr成功地发过很多个Ajax请求了,对它的基本操作已经算挺熟练了。” 我...
    前端渣渣阅读 5,752评论 1 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139