一、需求
是业务成就了技术,是事业成就了人,而不是相反。 ——《大型网站技术架构:核心原理与案例分析》李智慧著
在项目过程中遇到一个需求:用户需要导出一些数据,并且该数据以Excel表进行组织,并在浏览器中进行推送。前面半截儿倒是好弄,直接用Apache POI这个组件就可以,本人就卡在后半截如何在浏览器中推送下载,弹出常见的下载框,如下图。
在本博客中不涉及上面需求的代码,而是新的通用的需求:如何把硬盘中的文件推送给前端进行下载?
二、实现
先说说后台吧,后台大体思路就是把获得文件后,把其写入到返回流中,并利用HttpServletResponse进行流推送。文件路径如下图:
后台代码:
/**
* 文件下载主函数
* @param response
* @throws IOException
*/
@RequestMapping(value = "fileDownload", method = RequestMethod.GET)
public void fileDownload(HttpServletResponse response) throws IOException {
//自己定义文件名和文件路径
String fileName = "样例文件.xls";
String filePath = "E://test/";
String absolutePath = filePath + fileName ;
//读取文件
FileInputStream fis = null;
try {
setResponseHeader(response, fileName);
fis = new FileInputStream(new File(absolutePath));
//获得HttpServletResponse输出流
OutputStream os = response.getOutputStream();
//缓存字节数组
byte[] b = new byte[2048];
int length;
//把输入流中的文件分段读入缓存字节数组,再把字节数组中的数据写到输出流。
while ((length = fis.read(b)) > 0) {
os.write(b, 0, length);
}
os.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fis != null) {
fis.close();
}
}
}
/**
* 设置响应HTTP报文头
* @param response HTTP响应
* @param fileName 文件名
*/
public void setResponseHeader(HttpServletResponse response, String fileName) {
try {
fileName = URLEncoder.encode(fileName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-Disposition","attachment;filename=" + fileName);
}
前端这块的思路就是模拟Ajax的请求方式,然后构造虚拟的<a>标签模拟用户点击。代码如下:
var url = "/fileDownload";
//模拟Ajax方式
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "blob";
req.onreadystatechange = function () {
//后台返回200
if (req.readyState === 4 && req.status === 200) {
//获取HTTP报文头的文件名
var name = req.getResponseHeader("Content-disposition");
var fileNameInUtf8 = name.substring(20,name.length);
//必须解码,因为传过来的是汉字的UTF-8编码,在这里卡了好久,晕!
var filename = decodeURIComponent(fileNameInUtf8);
if (typeof window.chrome !== 'undefined') {
// Chrome version
var link = document.createElement('a');
link.href = window.URL.createObjectURL(req.response);
link.download = filename;
link.click();
} else if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE version
var blob = new Blob([req.response], { type: 'application/force-download' });
window.navigator.msSaveBlob(blob, filename);
} else {
// Firefox version
var file = new File([req.response], filename, { type: 'application/force-download' });
window.open(URL.createObjectURL(file));
}
}
};
req.send();
三、总结
先说说遇到的坑,在这里不能用Ajax进行请求,因为jQuery的Ajax回调已经把response的数据傻瓜式的以字符串的方式解析,所以解析不了文件流。当然文件上传是可以用Ajax的。除了这种方式,查询了资料,发现还有个叫file-saver的js插件,大家可以尝试尝试。
其次,还是SpringBoot好用啊,可以直接让用户访问到外部静态文件,包括图片、PDF和Excel等文件,大体思路可以参考我写过的一篇博文《SpringBoot 2.0的文件(图片、文档等)访问小结》。
最后,说一点个人感想吧,一两年前我是真的很反感前端任何东西的,见不得任何前端的代码,渐渐地我发现前端还是蛮好玩的,每一个后台开发人员必须懂一点前端,这样进行前后端分离的项目开发时才更好协作。