前后端分离 AJAX 文件下载解决方案
【场景描述】
微服务架构中,使用前后端分离设计,用户点击导出下载Excel列表,通过AJAX与后端SpringMVC交互获取下载文件,请求需要携带Authorization认证信息,并且需要考虑到IE浏览器的兼容问题
【解决方案】
Ajax下载文件的这种方式本来就是禁止的。出于安全因素的考虑,javascript是不能够保存文件到本地的,
所以ajax考虑到了这点,只是接受json,text,html,xml格式的返回值,二进制的返回格式就会抛出这个异常。ajax请求只是个“字符型”的请求,即请求的内容是以文本类型存放的。
文件的下载是以二进制形式进行的,虽然可以读取到返回的response,但只是读取而已,是无法执行的,js是无法调用到浏览器的下载处理机制和程序。
如果非要使用ajax的话,只能通过返回值得到生成的文件相关url。一个对文件生成流的处理url,这样操作来实现文件下载且页面无刷新。
后端返回二进制文件,前端通过 blob 数据格式接收,并通过构建a链接URL的方式实现文件的下载
【前端代码】
1:注意考虑IE浏览器的XMLHttpRequest兼容性问题
2:设置响应接收的类型为blob
3: 请求类型这里设置为json,是因为我在传输FormData时后端一直获取失败于是使用这种方式获取参数
4:AJAX请求头中添加Authorization认证Token
5:创建Blob接收后端返回的文件流对象
6:通过返回值得到生成的文件相关url
7:IE浏览器使用link.click();没反应,需要使用window.navigator.msSaveBlob(blob, downloadFileName);构建
function excelExport(url, params) {
let xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
//IE
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open('post', url);
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8')
xhr.responseType = 'blob';
xhr.setRequestHeader("Authorization", "Bearer " + localStorage.getItem("access_token"))
xhr.send(JSON.stringify(params));
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const blob = new Blob([xhr.response])
let urlBlob = window.URL.createObjectURL(blob)
//创建一个a标签元素,设置下载属性,点击下载
let link = document.createElement('a')
link.href = urlBlob
link.style.display = 'none'
//取出下载文件名
const fileName = xhr.getResponseHeader('fileName')
const downloadFileName = decodeURIComponent(fileName)
link.setAttribute('download', downloadFileName);
//IE浏览器
if (window.navigator.msSaveBlob) {
try {
window.navigator.msSaveBlob(blob, downloadFileName);
} catch (e) {
console.log(e);
}
} else {
link.click();
}
}
}
}
参考的前端调用示例
function ebotMonitorExport() {
let exportUrl = "/api-m/XXXX/XXXX/export";
let searchVo = {
code: $("#code").val(),
name: $("#name").val()
};
excelExport(exportUrl, searchVo);
}
【后端代码】
后端就是一般的导出逻辑,返回文件流,唯一注意的就是需要返回的文件名称放在响应流头中
setResponseHeader(response, fileName);
@PostMapping("/export")
public void export(@RequestBody ExportVO vo, HttpServletResponse response) {
service.export(vo, response);
}
//根据条件查询数据
List<XXX> list = mapper.selectByExample(vo);
//构建Excel头
String[] title = {"XXX", "XXX", "XXX"};
String[] fieldNames = {"code", "name", "createTime"};
String filename = "数据列表" + DateUtil.getNowDateStr() + ".xls";
String sheetName = "数据列表";
ExcelUtil.writeExcel(list, title, fieldNames, "createTime", sheetName, response, filename);
public static void writeExcel(List list, String[] title, String[] fieldNames, String toDateField, String sheetName,
HttpServletResponse response, String fileName) {
//创建HSSFWorkbook
HSSFWorkbook wb = getWorkbookDetail(list, title, fieldNames, toDateField, sheetName, null, null);
try {
fileName = URLEncoder.encode(fileName, "UTF8");
} catch (UnsupportedEncodingException e) {
log.error(LogHandle.error(String.format("出现异常: %s", e.getMessage())).toString());
}
setResponseHeader(response, fileName);
try (OutputStream os = response.getOutputStream()) {
wb.write(os);
} catch (IOException e) {
log.error(LogHandle.error(String.format("出现异常: %s", e.getMessage())).toString());
}
}
下一篇: 二分查找及其扩展