背景
项目需要实现在下载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 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();
}