将POI SXSSFWorkbook 实时流式传输到servlet输出流

3
我们正在构建一个Spring boot REST终端点,用于生成一个大的XLS文件(可能包含约1百万行),并提供其下载。目前的解决方案使用Apache POI库的SXSSF API来创建工作簿; 之后,我们将工作簿写入输出流,将流收集到字节数组中,然后提供此字节数组进行下载。
如何对工作簿的内容进行流处理,以便在添加更多行时,不会将整个文件保存在内存中?
当前解决方案的代码:
@RequestMapping(path = "/download/xls", method = RequestMethod.GET, produces = org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<InputStreamResource> downloadXls(HttpServletResponse response, XlsRequest request) throws FileNotFoundException, InternalServerErrorException {

        byte[] data = downloadIssuesAsExcel(response, request);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Description", "File Transfer");
        headers.add("Content-Disposition", "attachment; filename=justAFile.xlsx");
        headers.add("Content-Transfer-Encoding", "binary");
        headers.add("Connection", "Keep-Alive");
        headers.setContentType(
                org.springframework.http.MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
        InputStreamResource isr = new InputStreamResource(new ByteArrayInputStream(data));
        return ResponseEntity.ok().contentLength(data.length).headers(headers).body(isr);
    }

    public byte[] downloadIssuesAsExcel(HttpServletResponse response, XlsRequest request)
            throws InternalServerErrorException {
        try {
            SXSSFWorkbook workbook = createExcel(request, response);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            workbook.write(stream);
            workbook.dispose();
            workbook.close();
            stream.close();
            return stream.toByteArray();
        } catch (Exception e) {
            throw new InternalServerErrorException("IO exception while downloading XLS file", e);
        }
    }

我也试图将工作簿内容直接写在 response.getOutputStream() 中,但是文件以某种方式损坏了。

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        response.setHeader("Content-Description", "File Transfer");
        response.setHeader("Content-Disposition", "attachment; filename=" + issueDataService.getExcelName(request));
        response.setHeader("Content-Transfer-Encoding", "binary");
        response.setHeader("Connection", "Keep-Alive");

        SXSSFWorkbook workbook = createExcel(request, response);
        workbook.write(response.getOutputStream());
        workbook.dispose();
        workbook.close();

这个回答有帮助吗 https://dev59.com/_XDYa4cB1Zd3GeqPA36B#15800625? - Smile
我已经使用了直接写入到servlet输出流的方法,它运行良好。请问您能否展示一下response.getOutputStream()的代码?可能有多种原因,例如是否指定了内容长度? - edwgiz
嗨@Smile,谢谢你的想法。如果我们决定先将整个文件保存在磁盘上,这将是高效的方法。我仍然希望找到一种直接从内存中流式传输的方法。 - chirina
嗨@edwgiz,我实际上没有添加content-length头,因为我认为它意味着首先要访问整个文件。我使用代码片段更新了我的问题,用于“response.getOutputStream()”。您是否发现与您的版本有所不同的内容,可能会破坏文件? - chirina
1个回答

1

我刚刚使用了你的代码作为模板,并创建了一个能够正常工作的控制器。

@RestController
public class XlsxController {

    @RequestMapping(path = "/download/xls", method = RequestMethod.GET, produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    public void downloadXls(HttpServletResponse r) throws IOException {
        r.setHeader("Content-Description", "File Transfer");
        r.setHeader("Content-Disposition", "attachment; filename=justAFile.xlsx");
        r.setHeader("Content-Transfer-Encoding", "binary");
        r.setHeader("Connection", "Keep-Alive");

        try (SXSSFWorkbook w = getWorkbook()) {
            w.write(r.getOutputStream());
        }
    }


这个工作簿有100万行,重量超过40 Mb。
org.springframework:spring-webmvc:5.2.2.RELEASE
org.apache.poi:poi-ooxml:4.1.1

没错,这个方法奏效了。实际上我在创建工作簿的时候遇到了一些问题,然后当尝试向输出流写入内容时这些问题又随机地出现了。感谢@edwgiz确认了解决方案! - chirina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接