Java返回包含文件流和其他Json数据的响应对象

项目场景

需求:

  • 前端上传Excel文件,导入数据库,后端校验不通过直接返回并下载带有失败信息的Excel文件,例如:返回一个Excel文件以及失败的条数、成功的条数。

问题描述

后端需求

  • 解析Excel,并导入数据库,返回数据导入成功和失败的条数。

  • 将导入失败的数据,写入Excel文件中返回的前端。

前端需求:

  • 根据后端返回的响应对象,展示成功以及失败数据的条数。

  • 根据后端返回的响应对象中的文件流,下载Excel文件。


解决方案

三种解决方案:

第一种方案(取巧方式):

  • Excel文件:
    • 将数据导出到Excel文件,并将其作为HTTP servlet响应发送。
/**
 * 方法用于将数据导出到Excel文件,并将其作为HTTP servlet响应发送。
 *
 * @param response   HttpServletResponse对象,用于编写响应
 * @param filename   要下载的Excel文件的名称
 * @param sheetName  工作表的名称
 * @param head       Excel文件的标题类(通常是数据对象的类)
 * @param data       要导出到Excel文件的数据列表
 * @throws IOException 如果发生IO错误
 */
public static <T> void exportExcel(HttpServletResponse response, String filename, String sheetName,
                                    Class<T> head, List<T> data) throws IOException {
    // 对文件名进行编码以处理中文文件名
    String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
    response.setHeader("Content-Disposition", "attachment; filename=" + encodedFilename);
    // 设置响应的内容类型为Excel
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=GBK");
    // 使用EasyExcel库将数据写入Excel文件
    EasyExcel.write(response.getOutputStream(), head)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动适配列宽
            .sheet(sheetName).doWrite(data);
}
  • 其他数据(少量数据):
    • 将设置为HTTP头部的值,前端通过读取header中对应的值达到数据传输的效果。

注意:
虽然response.setHeader()可以用来设置HTTP响应头,但通常情况下,它用于设置元数据而不是传递大量数据。
HTTP头部通常用于传递元数据,如内容类型、内容长度、缓存控制等。
如果尝试传递大量数据,特别是文本数据,可能会遇到一些限制,因为HTTP头部的大小通常是有限制的。
使用response.setHeader()来传递数据,可以将数据转换为字符串,并且确保数据量不会太大以超出HTTP头部的大小限制。

response.setHeader("success_count", String.valueOf(importList.size()));
response.setHeader("error_count", String.valueOf(exceptList.size()));

响应结果:

文件流:
在这里插入图片描述

其他信息:
在这里插入图片描述

第二种方案:
创建一个响应对象(包含:字节数组(base64) 和 其他属性)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseObject {
    /**
     *  成功数量
     */
    private Integer success_count;
    /**
     *  失败数量
     */
    private Integer fail_count;
    /**
     *  导出文件字节数组
     */
    private byte[] excelStream;
}
  • Excel文件:
    • 将生成的outputStream 转换为字节数组并返回。可以读取 outputStream 中的数据并将其存储到字节数组中,然后将字节数组返回给 前端。前端可以使用这个字节数组来下载文件或进行其他操作。
/**
 * 处理输入流的实用工具类。
 */
public class ExcelUtils {

        /**
     * 将输出流转换为字节数组。
     *
     * @param outputStream 要转换的输出流
     * @return 包含从输出流中读取的数据的字节数组
     * @throws IOException 如果在从输出流中读取数据时发生 I/O 错误
     */
    public static byte[] outputStreamToByteArray(OutputStream outputStream) throws IOException {
        try {
            // 检查传入的输出流是否是 ByteArrayOutputStream 的实例
            if (!(outputStream instanceof ByteArrayOutputStream)) {
                throw new IllegalArgumentException("Output stream is not a ByteArrayOutputStream");
            }

            // 将输出流转换为 ByteArrayOutputStream
            ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) outputStream;

            // 获取 ByteArrayOutputStream 中的字节数组
            byte[] byteArray = byteArrayOutputStream.toByteArray();

            return byteArray;
        } finally {
            closeStream(outputStream);
        }
    }

    /**
     * 关闭给定的输出流。
     *
     * @param outputStream 要关闭的输出流
     */
    private static void closeStream(OutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                // 记录异常或根据情况进行处理
                e.printStackTrace();
            }
        }
    }
}
/**
     * @description: TODO  生成单个工作表的 Excel 文件并返回输出流
     * @author: LLong
     * @param response HttpServletResponse 对象
     * @param filename 导出文件名
     * @param sheetName 工作表名称
     * @param head Excel 表头类
     * @param data Excel 数据列表
     * @return OutputStream 包含 Excel 数据的输出流
     * @throws IOException
     */
    public static <T> OutputStream exportExcelOutputStream(HttpServletResponse response, String filename, String sheetName,
                                              Class<T> head, List<T> data) throws IOException {
        // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition", "attachment; filename=" + encodedFilename);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=GBK");

        // 创建 ByteArrayOutputStream
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        
        // 写入当前工作表的数据
        EasyExcel.write(outputStream, head)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
                .sheet(sheetName).doWrite(data);

        // 返回包含 Excel 数据的输出流
        return outputStream;
    }


    /**
     * @description: TODO 生成多个工作表的 Excel 文件并返回输出流
     * @author: LLong
     * @param response HttpServletResponse 对象
     * @param filename 导出文件名
     * @param sheetNames 工作表名称列表
     * @param head Excel 表头类列表
     * @param data Excel 数据列表
     * @return OutputStream 包含 Excel 数据的输出流
     * @throws IOException
     */
    public static <T> OutputStream exportBatchExcelInputStream(HttpServletResponse response, String filename, List<String> sheetNames,
                                                         List<Class<?>> head, List<List<?>> data) throws IOException {
        // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition", "attachment; filename=" + encodedFilename);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=GBK");

        // 创建 ByteArrayOutputStream
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 创建 ExcelWriter
        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();

        // 循环写入每个工作表
        for (int i = 0; i < sheetNames.size(); i++) {
            // 获取当前工作表的数据和表头 (数据类型不同)
            List<?> dataItem =  data.get(i);
            Class<?> headItem = head.get(i);

            // 创建工作表
            WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetNames.get(i))
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .head(headItem)
                    .build();

            // 写入当前工作表的数据
            excelWriter.write(dataItem, writeSheet);
        }
        // 关闭 ExcelWriter
        excelWriter.finish();
        // 返回包含 Excel 数据的输出流
        return outputStream;
    }
// 导出 Excel 数据到 OutputStream
OutputStream outputStream = ExcelUtils.exportExcelOutputStream(response, "导入数据.xlsx", "导入文件", EvaluateLogImportExcelVo.class, exceptList);

// 将 OutputStream 转换为字节数组
byte[] excelByteArray = null;
if (outputStream != null) {
    excelByteArray = ExcelUtils.outputStreamToByteArray(outputStream);
}

// 将字节数组设置到 responseObject 中
responseObject.setExcelStream(excelByteArray);
  • 其他数据:将其他数据写入返回的响应对象中。
// 设置成功数量和失败数量
responseObject.setSuccessCount(importList.size()); // 设置成功数量
responseObject.setFailCount(exceptList.size()); // 设置失败数量

响应结果:
在这里插入图片描述

第三种方案:
创建一个响应对象(包含:字节数组 和 其他属性)

  • Excel文件:
    • 可以将生成的 InputStream 中的数据写入到一个临时文件中,然后返回该文件的下载链接给前端。前端可以通过这个链接来下载文件。
/**
 * 将输入流写入临时文件并返回文件下载链接。
 *
 * @param inputStream 要写入临时文件的输入流
 * @return 写入的临时文件的路径
 * @throws IOException 如果在写入临时文件时发生 I/O 错误
 */
public static String writeInputStreamToTempFile(InputStream inputStream) throws IOException {
    // 创建临时文件
    Path tempFile = Files.createTempFile("temp-file-", ".tmp");

    // 写入 InputStream 数据到临时文件
    try (FileOutputStream outputStream = new FileOutputStream(tempFile.toFile())) {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    }

    // 返回临时文件的路径
    return tempFile.toString();
}
  • 其他数据:将其他数据写入返回的响应对象中。

该方法更适用于大文件,而且前端处理起来更加方便。但是会存在临时文件,会对服务器造成一定的压力,需要定期删除等问题。

响应结果:
在这里插入图片描述

前端接收(base64转换为文件)

  1. 前端接收数据
/**
 * 将 Base64 编码的字节数组转换为 Excel 文件格式,并返回 File 对象。
 *
 * @param {string} data Base64 编码的字节数组
 * @param {string} filename 下载的文件名
 * @returns {File} Excel 文件的 File 对象
 */
export function getExcelFile(data, filename) {
  // 解码 Base64 字符串
  let bstr = window.atob(data);
  let n = bstr.length;
 
  // 创建 Uint8Array 以保存字节数据
  let u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  
  // 创建 File 对象,并指定文件类型为 Excel
  return new File([u8arr], filename, { type: 'application/vnd.ms-excel' });
}

  1. 文件前端下载导出
/**
 * 下载当前的 Excel 文件到本地。
 */
downloadExcel() {
  // 调试信息
  // console.log(sheet.getAllSheets())
  // exportExcel(sheet.getAllSheets(), "导出数据");
  // console.log("excelData", this.excelData)
  
  // 使用 FileSaver 库保存文件到本地
  FileSaver.saveAs(this.excelData, '导出数据.xlsx');
}

调用函数

// 假设当前的 Excel的base64数据为 excelData,需要下载的文件名为 filename
let file = getExcelFile(excelData, filename);
downloadExcel(file);
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要将Java List转换为文件返回前端,可以按照以下步骤操作: 1. 将List转换为字符串形式 将List转换为字符串形式可能涉及到JSON序列化或其他方式的序列化。这取决于你的List中的对象类型和你想要的返回格式。例如,如果你的List中包含的是字符串或数字类型,你可以使用以下代码将其转换为JSON格式的字符串: ``` List<String> stringList = new ArrayList<>(); // add elements to the list Gson gson = new Gson(); String jsonString = gson.toJson(stringList); ``` 2. 将字符串转换为文件 现在你有了List的字符串表示形式,接下来需要将其转换为文件。你可以使用Java的ByteArrayInputStream类来实现这一点。以下是一个将字符串转换为文件的示例代码: ``` InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes()); ``` 3. 返回文件前端 现在你有了文件,可以将其返回前端。具体的实现方式取决于你使用的后端框架和你返回文件的方式。以下是一个使用Spring框架将文件返回前端的示例代码: ``` @GetMapping("/list-to-file") public ResponseEntity<InputStreamResource> getListAsFile() { List<String> stringList = new ArrayList<>(); // add elements to the list Gson gson = new Gson(); String jsonString = gson.toJson(stringList); InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes()); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(inputStreamResource); } ``` 在上面的代码中,我们使用了Spring框架的ResponseEntity类来包装文件并设置响应头的类型。文件的MIME类型设置为APPLICATION_OCTET_STREAM,表示它是二进制数据。在实际应用中,根据需要设置正确的MIME类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值