You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot控制器返回多个byte[]方案求助(PDF发票展示)

问题:SpringBoot控制器返回多PDF发票(inline展示Zip包)

问题背景

我需要在SpringBoot控制器中实现getAllInvoices方法,返回多个发票的PDF数据(byte[]类型)。

最初的尝试(不可行)

Service层代码:

public List<byte[]> getAllInvoices() {
    Iterable<Invoice> invoices = repos.findAll();
    List<byte[]> invoicesData = new ArrayList<>();

    for (Invoice invoice : invoices) {
        byte[] invoiceData = ImageUtil.decompressImage(invoice.getInvoiceData());
        invoicesData.add(invoiceData);
    }
    return invoicesData;
}

控制器层代码:

@GetMapping
public ResponseEntity<List<byte[]>> getAllInvoices() {
    List<byte[]> invoicesPdfBytes = service.getAllInvoices();

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_PDF);
    headers.setContentDispositionFormData("inline", "invoice.pdf");

    return ResponseEntity.ok()
            .headers(headers)
            .body(invoicesPdfBytes);
}

这个方案明显不可行:返回List<byte[]>无法被客户端正确解析为多文件,最终所有数据会被合并到单一的invoice.pdf中,无法区分不同发票。

尝试Zip打包后的问题

感谢Alex和AndrewL的建议,我改用Zip打包的方式实现,要求接口的contentDisposition设为inline(已有GET "/download/{order_id}"接口用attachment实现单发票下载),用于展示所有PDF发票。但现在Postman返回乱码原始数据(如??ff/bel??ffx????m??/fmx?bel??),当前代码如下:

@GetMapping
public ResponseEntity<Object> getAllInvoices() throws IOException {

    List<byte[]> invoicesData = service.getAllInvoices();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);

    for (int i = 0; i < invoicesData.size(); i++) {
        ZipEntry zipEntry = new ZipEntry("invoice-insight" + (i + 1) + ".pdf");
        zipEntry.setSize(invoicesData.get(i).length);
        zipOutputStream.putNextEntry(zipEntry);
        zipOutputStream.write(invoicesData.get(i));
        zipOutputStream.closeEntry();
    }

    zipOutputStream.close();
    ByteArrayResource resource = new ByteArrayResource(byteArrayOutputStream.toByteArray());

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    headers.setContentDispositionFormData("inline", "invoices.zip");

    return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}

请问该如何正确实现需求?


解决方案

1. 核心问题分析

Postman返回乱码、Zip文件无法正常解析的原因:

  • 响应MediaType不准确,客户端无法识别为Zip文件
  • 流操作未完成,导致Zip文件损坏
  • 文件名未做编码处理,可能引发乱码
  • 未明确设置Content-Length,客户端无法准确处理资源大小

2. 修正后的控制器代码

@GetMapping
public ResponseEntity<ByteArrayResource> getAllInvoices() throws IOException {
    List<byte[]> invoicesData = service.getAllInvoices();
    
    // 使用try-with-resources自动关闭流,避免资源泄漏
    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {

        for (int i = 0; i < invoicesData.size(); i++) {
            byte[] pdfBytes = invoicesData.get(i);
            ZipEntry zipEntry = new ZipEntry(String.format("invoice-insight-%d.pdf", i + 1));
            zipEntry.setSize(pdfBytes.length);
            zipOutputStream.putNextEntry(zipEntry);
            zipOutputStream.write(pdfBytes);
            zipOutputStream.closeEntry();
        }
        
        zipOutputStream.finish(); // 确保Zip流完成所有写入操作,避免文件损坏
        byte[] zipBytes = byteArrayOutputStream.toByteArray();
        ByteArrayResource resource = new ByteArrayResource(zipBytes);

        HttpHeaders headers = new HttpHeaders();
        // 使用更准确的APPLICATION_ZIP类型,替代通用的OCTET_STREAM
        headers.setContentType(MediaType.APPLICATION_ZIP);
        // 构建ContentDisposition,确保inline生效且文件名UTF-8编码
        headers.setContentDisposition(ContentDisposition.inline()
                .filename("invoices.zip", StandardCharsets.UTF_8)
                .build());
        headers.setContentLength(zipBytes.length); // 明确资源大小

        return ResponseEntity.ok()
                .headers(headers)
                .body(resource);
    }
}

3. 关键调整说明

  • try-with-resources语法:自动管理流的生命周期,确保流按顺序关闭,避免资源泄漏
  • MediaType.APPLICATION_ZIP:让客户端明确识别这是Zip文件,而非二进制流
  • ContentDisposition构建:通过官方API设置inline,同时对文件名做UTF-8编码,避免中文乱码
  • zipOutputStream.finish():强制完成Zip流的所有写入操作,防止生成损坏的Zip文件
  • Content-Length设置:帮助客户端准确处理资源,避免解析异常

4. 关于inline展示的补充

设置inline后,浏览器会尝试直接预览Zip文件(部分现代浏览器支持直接查看Zip内的PDF,或依赖插件);若浏览器不支持该功能,会自动触发下载,这是符合预期的浏览器行为,满足需求。


内容的提问来源于stack exchange,提问作者mjCode

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:44:59