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

如何在REST Controller中动态下载Excel文件(不依赖HttpResponse)

问题描述

我们正将基于会话的应用迁移至REST API。当前应用通过将生成的Excel工作簿写入HttpResponse的OutputStream来动态下载小型Excel文件,现有实现代码如下,请问如何在不使用HttpResponse对象、不保存文件至临时路径的REST API中实现该动态下载功能?

现有实现代码

@RequestMapping(value = "exportRules", method = GET)
public void exportRuleResults(final HttpServletRequest request, final HttpServletResponse response) 
{

    try {
            
    final SXSSFWorkbook workbook = new SXSSFWorkbook(100);
    // summary report
    final Sheet sheet = workbook.createSheet("Rules Details");
            
                        //Headers (First row of report)
            final int rows = writeRulesHeaders(workbook, sheet);
            int pageNumber = 0;
            final ExportReturnVals ret = new ExportReturnVals(1, true);
            ret.rowIndex = rows;
            ret.totalnumberOfRecords = 1;

            while (ret.hasMoreRows) {
                writeRows(pageNumber, sheet, ret, workbook);
                pageNumber++;
            }
            OutputStream os=getOutputStream(response, ret);
            workbook.write(os);
            response.flushBuffer();
            workbook.dispose();
            os.close();
        } catch (final IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
}

private OutputStream getOutputStream(final HttpServletResponse response, final ExcelExportReturnVals ret) throws IOException {

    final OutputStream os = response.getOutputStream();
    OutputStream outputStream;

    if (ret.totalnumberOfRecords > MAX_RECORD_NONZIP) {
        response.setContentType("application/zip");
        response.addHeader("Content-Disposition", "attachment; filename=\"" + "RuleDetails" + ".zip\"");
        outputStream = new ZipOutputStream(new BufferedOutputStream(os));
        ((ZipOutputStream) outputStream).putNextEntry(new ZipEntry("RuleDetails" + ".xlsx"));
    }else {
    response.setContentType("application/xlsx");
    response.addHeader("Content-Disposition", "attachment; filename=\"" + "RuleDetails" + ".xlsx\"");
    outputStream = new BufferedOutputStream(os);
    }
    return outputStream;
}
解决方案

在Spring REST API中,可通过返回ResponseEntity<Resource>实现无临时文件、不依赖HttpResponse的动态下载。核心逻辑是将Excel/压缩包内容写入内存流,再封装为资源对象配合响应头完成下载。

改造步骤

  1. 替换HttpResponse输出流为ByteArrayOutputStream,在内存中暂存文件内容
  2. 保留原有的记录数阈值判断,分别处理普通Excel和压缩包场景
  3. 将内存中的字节数据封装为ByteArrayResource,搭配正确的响应头返回

改造后代码示例

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;

@RequestMapping(value = "exportRules")
@GetMapping
public ResponseEntity<ByteArrayResource> exportRuleResults() {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    SXSSFWorkbook workbook = null;
    try {
        workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("Rules Details");

        // 写入表头
        int rows = writeRulesHeaders(workbook, sheet);
        int pageNumber = 0;
        ExportReturnVals ret = new ExportReturnVals(1, true);
        ret.rowIndex = rows;
        ret.totalnumberOfRecords = 1;

        // 写入数据行
        while (ret.hasMoreRows) {
            writeRows(pageNumber, sheet, ret, workbook);
            pageNumber++;
        }

        HttpHeaders headers = new HttpHeaders();
        if (ret.totalnumberOfRecords > MAX_RECORD_NONZIP) {
            // 处理压缩包场景
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", "RuleDetails.zip");
            
            try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(outputStream))) {
                zipOut.putNextEntry(new ZipEntry("RuleDetails.xlsx"));
                workbook.write(zipOut);
                zipOut.closeEntry();
            }
        } else {
            // 处理普通Excel场景(使用标准MIME类型)
            headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
            headers.setContentDispositionFormData("attachment", "RuleDetails.xlsx");
            
            workbook.write(outputStream);
        }

        workbook.dispose();
        ByteArrayResource resource = new ByteArrayResource(outputStream.toByteArray());

        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
    } catch (Exception e) {
        throw new RuntimeException("导出Excel失败", e);
    } finally {
        try {
            if (workbook != null) workbook.close();
            outputStream.close();
        } catch (Exception ex) {
            // 忽略资源关闭异常
        }
    }
}

关键说明

  • 内存流暂存:用ByteArrayOutputStream替代HttpResponse输出流,全程在内存中处理文件内容,无需临时文件
  • 资源封装:ByteArrayResource是Spring提供的资源类,可直接适配ResponseEntity的返回要求
  • 标准响应头:
    • 普通Excel使用标准MIME类型application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,替代原代码的非标准application/xlsx
    • 通过setContentDispositionFormData设置下载文件名,自动处理编码问题
  • 资源释放:通过try-with-resources和finally块确保工作簿、流资源被正确关闭,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 01:12:34