将响应下载为Excel文件

时间:2016-07-27 03:28:22

标签: angularjs spring servlets spring-boot apache-poi

文件未在浏览器下载。我正在准备文件并将其写入输出的响应流。

Rest API就在那里:

@RequestMapping(value = "/export-companies",
        method = {RequestMethod.GET, RequestMethod.HEAD})
    @Timed
    public void downloadCompanies(HttpServletResponse response) throws URISyntaxException {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet("Sample sheet");

        Map<String, Object[]> data = new HashMap<String, Object[]>();
        data.put("1", new Object[] {"Emp No.", "Name", "Salary"});
        data.put("2", new Object[] {1d, "John", 1500000d});
        data.put("3", new Object[] {2d, "Sam", 800000d});
        data.put("4", new Object[] {3d, "Dean", 700000d});

        Set<String> keyset = data.keySet();
        int rownum = 0;
        for (String key : keyset) {
            Row row = sheet.createRow(rownum++);
            Object [] objArr = data.get(key);
            int cellnum = 0;
            for (Object obj : objArr) {
                Cell cell = row.createCell(cellnum++);
                if(obj instanceof Date)
                    cell.setCellValue((Date)obj);
                else if(obj instanceof Boolean)
                    cell.setCellValue((Boolean)obj);
                else if(obj instanceof String)
                    cell.setCellValue((String)obj);
                else if(obj instanceof Double)
                    cell.setCellValue((Double)obj);
            }
        }

        try {
            ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
            workbook.write(outByteStream);
            byte [] outArray = outByteStream.toByteArray();
            response.setContentType("application/ms-excel");
            response.setContentLength(outArray.length);
            response.setHeader("Expires:", "0"); // eliminates browser caching
            response.setHeader("Content-Disposition", "attachment; filename=template.xls");
            OutputStream outStream = response.getOutputStream();
            outStream.write(outArray);
            outStream.flush();
            workbook.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

从前端(使用Angular JS):

(function() {
    'use strict';

    angular
        .module('MyApp')
        .factory('CompanyExportService', CompanyExportService);

    CompanyExportService.$inject = ['$resource'];

    function CompanyExportService ($resource) {
        var service = $resource('api/export-companies', {}, {
            'get': {
                method: 'GET',
                isArray: false
            }
        });

        return service;
    }
})();

文件内容作为不可读格式响应。但是文件不会在浏览器下载。

3 个答案:

答案 0 :(得分:5)

Angular将仅接收文件内容的字符序列。您需要从这些字符创建一个文件,并在前端启动浏览器下载。

你可以这样做 -

var blob = new Blob([data], 
                    {type: 'application/vnd.openxmlformat-officedocument.spreadsheetml.sheet;'});
saveAs(blob, fileName);

其中data是您从API收到的回复。 saveAs函数是FileSaver.js库的一部分。虽然您可以看看如何手动执行此操作,但为什么要重新发明轮子?

答案 1 :(得分:3)

使用XHR下载文件存在问题。只要您只执行GET请求,就会有更简单的方法来触发浏览器下载文件。

使用JavaScript本机方法window.open(url)。 它适用于所有浏览器,包括IE9。

在下面的代码中,我使用$window,这是Angular的本机窗口对象的代理。

您的代码示例可能如下:

(function() {
'use strict';

angular
    .module('MyApp')
    .factory('CompanyExportService', CompanyExportService);

CompanyExportService.$inject = ['$window'];

function CompanyExportService ($window) {
    var exportUrl = 'api/export-companies';

    return {
        download: download
    }

    function download() {
        $window.open(exportUrl);
    }
}
})();

请注意,此操作超出了Angular的范围,您无法在错误处理或等待文件下载之前做很多事情。如果你想生成巨大的Excel文件或者你的API很慢,可能会有问题。

有关详细信息,请参阅问题:Spring - download response as a file

更新

我已将window.location.href替换为window.open(),这似乎是下载文件的最佳选择。

如果您的API会抛出错误页面而不是文件,window.location.href将替换当前页面(从而失去其状态)。但是,$window.open()会在新选项卡中打开此错误,而不会丢失当前的应用程序状态。

答案 2 :(得分:3)

您可以在新标签中下载文件。现代浏览器在下载完成后自动关闭它们。

通过打开新窗口,您可以参考它,当下载完成后, $(':checkbox').each(function (k, v) { $(this).onchange = null;// $(this).off() // works but I do not know how to reactivate onchange event }) 将设置为true。

不幸的是,你需要在时间间隔内不时检查这个参数...

window.closed

经过测试并适用于Chrome v52,FF v48和IE 11