SpringMVC文件“点击下载”功能小结

一、需求

是业务成就了技术,是事业成就了人,而不是相反。  ——《大型网站技术架构:核心原理与案例分析》李智慧著

  在项目过程中遇到一个需求:用户需要导出一些数据,并且该数据以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的文件(图片、文档等)访问小结》
  最后,说一点个人感想吧,一两年前我是真的很反感前端任何东西的,见不得任何前端的代码,渐渐地我发现前端还是蛮好玩的,每一个后台开发人员必须懂一点前端,这样进行前后端分离的项目开发时才更好协作。

原创文章 18 获赞 25 访问量 9780

猜你喜欢

转载自blog.csdn.net/BlackButton_CC/article/details/93381822