行业知识
Java返回包含文件流和其他Json数据的响应对象
2026-10-26 17:02:43
来源:互联网转载
文章目录
- 项目场景
- 问题描述
- 解决方案
- 前端接收(base64转换为文件)
项目场景
需求:
- 前端上传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 void exportExcel(HttpServletResponse response, String filename, String sheetName, Class head, List 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@AllArgsConstructorpublic 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 OutputStream exportExcelOutputStream(HttpServletResponse response, String filename, String sheetName, Class head, 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(); // 写入当前工作表的数据 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 OutputStream exportBatchExcelInputStream(HttpServletResponse response, String filename, List sheetNames, 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// 导出 Excel 数据到 OutputStreamOutputStream 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转换为文件)
- 前端接收数据
/** * 将 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' });}- 文件前端下载导出
/** * 下载当前的 Excel 文件到本地。 */downloadExcel() { // 调试信息 // console.log(sheet.getAllSheets()) // exportExcel(sheet.getAllSheets(), "导出数据"); // console.log("excelData", this.excelData) // 使用 FileSaver 库保存文件到本地 FileSaver.saveAs(this.excelData, '导出数据.xlsx');}调用函数
// 假设当前的 Excel的base64数据为 excelData,需要下载的文件名为 filenamelet file = getExcelFile(excelData, filename);downloadExcel(file);
- 其他数据:将其他数据写入返回的响应对象中。
- 可以将生成的 InputStream 中的数据写入到一个临时文件中,然后返回该文件的下载链接给前端。前端可以通过这个链接来下载文件。
- Excel文件:
- 其他数据:将其他数据写入返回的响应对象中。
- 将生成的outputStream 转换为字节数组并返回。可以读取 outputStream 中的数据并将其存储到字节数组中,然后将字节数组返回给 前端。前端可以使用这个字节数组来下载文件或进行其他操作。
- Excel文件:
- 将设置为HTTP头部的值,前端通过读取header中对应的值达到数据传输的效果。
- 其他数据(少量数据):
- 将数据导出到Excel文件,并将其作为HTTP servlet响应发送。
- Excel文件:
-
-
- 前端上传Excel文件,导入数据库,后端校验不通过直接返回并下载带有失败信息的Excel文件,例如:返回一个Excel文件以及失败的条数、成功的条数。