如何使用 Java Springboot return 压缩文件流
How to return a zip file stream using Java Springboot
我用的是Springboot,我想生成zip文件然后return到前端
@PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<ZipOutputStream> export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
ZipOutputStream zipOut = customService.generateZipStream();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(zipOut)
}
可以正确生成 zip 文件(在本地目录中)但是当 return 流到前端时我得到 以下错误:
spring.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
然后我检查了 google 并将 return 类型更改为 ResponseEntity<StreamResponseBody>
,但是我应该如何在方法 [=18] 中将 ZipOutputStream
更改为 StreamResponseBody
=],google 中的解决方案是在 body()
方法中创建 zip 输出流,如下所示:
// pseudocode
.body(out -> {
ZipOutputStream zipOut = new ZipOutputStream(out));
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
... balabala
}
我的问题 是如何在这种情况下使用 StreamResponseBody
或 return 的任何替代解决方案zip 流可能有点大。
您可以尝试将其作为字节数组发回:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = customService.generateZipStream();
int count;
byte data[] = new byte[2048];
BufferedInputStream entryStream = new BufferedInputStream(is, 2048);
while ((count = entryStream.read(data, 0, 2048)) != -1) {
zos.write( data, 0, count );
}
entryStream.close();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(bos.toByteArray());
考虑到您需要将 return 类型更改为 ResponseEntity<byte[]>
。
也许你可以returnResponseEntity<Byte[]>
行:126
感谢大家的帮助,我检查了你的答案并通过更新导出逻辑解决了这个问题。
我的解决方案:
将方法重新定义为customService.generateZipStream(ZipOutputStream zipOut)
这样我就可以在controller层用StreamResponseBody
创建一个压缩流然后发送到service层,在service层,我会做export.
预设代码如下:
@PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity< StreamResponseBody > export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.body(outputStream -> {
// Use inner implement and set StreamResponseBody to ZipOutputStream
try(ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
customService.generateZipStream(zipOut);
}
});
}
customService
前导码:
public void generateZipStream(ZipOutputStream zipOut) {
// ... do export here
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
// ... balabala
}
希望有类似问题的可以帮到你
如果你正在使用Spring 3,你可以使用很多swagger注释接口来帮助干净地构建您的 ResponseEntity
在使用 StreamingResponseBody
正确原型化 spring 所期望的格式时。
此处的 body
代码是将 ZipOutputStream
流映射到控制器期望 return 的 StreamingResponseBody
类型的简化方法(.body(out -> {...})
这在我下面的代码中)。
[控制器] 代码如下所示:
@GetMapping(value = "/myZip")
@Operation(
summary = "Retrieves a ZIP file from the system given a proper request ID.",
responses = {
@ApiResponse(
description = "Get ZIP file containing data for the ID.",
responseCode = "200",
content = @Content(schema = @Schema(implementation = StreamingResponseBody.class))),
@ApiResponse(
description = "Unauthenticated",
responseCode = "401",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
@ApiResponse(
description = "Forbidden. Access Denied.",
responseCode = "403",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
})
public ResponseEntity<StreamingResponseBody> myZipBuilder(@RequestParam String id, HttpServletResponse response)
throws IOException {
final String fileName = "MyRequest_" + id + "_" + new SimpleDateFormat("MMddyyyy").format(new Date());
return ResponseEntity.ok()
.header(CONTENT_DISPOSITION,"attachment;filename=\"" + fileName + ".zip\"")
.contentType(MediaType.valueOf("application/zip"))
.body(out -> myZipService.build(id, response.getOutputStream()));
}
您的服务代码 build
方法只需要接收数据所需的任何参数,加上 您的 ServletOutputStream responseOutputStream
参数以允许您构建由该流播种的 ZipOutputStream
对象。
在我下面的小示例中,您可以看到我在 buildDataLists
方法(未显示)中构建了一些 CSV 数据,这只是一个 List.. 然后我将每个的顶级列表项,并使用我的 streamWriteCsvToZip
将它们推送到 ZipOutputStream
对象中。关键是,您构建 ZIP 流时使用控制器提供的 responseOutputStream 作为种子。完全构建 zip 后,请确保将其关闭(在我的例子中 zos.close()
)。然后return将zos
对象传给控制器。
/**
* Get ZIP file containing datafiles for a given request id
*
* @param id of the request
* @param responseOutputStream for streaming the zip results
* @return ZipOutputStream a ZIP file stream for the contents
* @throws AccessDeniedException if user does not have access to this function
* @throws UnauthenticatedException if user is not authenticated
*/
public ZipOutputStream build(String id, ServletOutputStream responseOutputStream) throws IOException {
try {
List<List<String[]>> csvFilesContents = buildDataLists(id);
final ZipOutputStream zos = new ZipOutputStream(responseOutputStream);
streamWriteCsvToZip("control", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("roles", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("accounts", id, zos, csvFilesContents.remove(0));
zos.close(); // finally closing the ZipOutputStream to mark completion of ZIP file
return zos;
} catch (IOException | ClientException ex) {
throw ex;
}
}
这里没有魔法。只需获取您的数据并将其放入 zip 流中即可。就我而言,我正在提取 list/array 数据,将其放入 CSV 中,然后将该 CSV 放入 zip 中(作为使用 zos.putNextEntry(entry);
的条目)。 CSV 和 ZIP 都保存为流,因此在此操作期间不会向文件系统写入任何内容,最终结果可以由控制器流出。确保每次向 zip 输出流写入一个条目时关闭该条目 (zos.closeEntry()
)。
private void streamWriteCsvToZip(String csvName, String id, ZipOutputStream zos, List<String[]> csvFileContents)
throws IOException {
String filename = id + "_" + csvName + ".csv";
ZipEntry entry = new ZipEntry(filename); // create a zip entry and add it to ZipOutputStream
zos.putNextEntry(entry);
CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(zos)); // Directly write bytes to the output stream
csvWriter.writeAll(csvFileContents); // write the contents
csvWriter.flush(); // flush the writer
zos.closeEntry(); // close the entry. Note: not closing the zos just yet as we need to add more files to our ZIP
}
我用的是Springboot,我想生成zip文件然后return到前端
@PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<ZipOutputStream> export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
ZipOutputStream zipOut = customService.generateZipStream();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(zipOut)
}
可以正确生成 zip 文件(在本地目录中)但是当 return 流到前端时我得到 以下错误:
spring.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
然后我检查了 google 并将 return 类型更改为 ResponseEntity<StreamResponseBody>
,但是我应该如何在方法 [=18] 中将 ZipOutputStream
更改为 StreamResponseBody
=],google 中的解决方案是在 body()
方法中创建 zip 输出流,如下所示:
// pseudocode
.body(out -> {
ZipOutputStream zipOut = new ZipOutputStream(out));
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
... balabala
}
我的问题 是如何在这种情况下使用 StreamResponseBody
或 return 的任何替代解决方案zip 流可能有点大。
您可以尝试将其作为字节数组发回:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = customService.generateZipStream();
int count;
byte data[] = new byte[2048];
BufferedInputStream entryStream = new BufferedInputStream(is, 2048);
while ((count = entryStream.read(data, 0, 2048)) != -1) {
zos.write( data, 0, count );
}
entryStream.close();
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.header("Content-Type","application/octet-stream")
.body(bos.toByteArray());
考虑到您需要将 return 类型更改为 ResponseEntity<byte[]>
。
也许你可以returnResponseEntity<Byte[]>
行:126
感谢大家的帮助,我检查了你的答案并通过更新导出逻辑解决了这个问题。
我的解决方案:
将方法重新定义为customService.generateZipStream(ZipOutputStream zipOut)
这样我就可以在controller层用StreamResponseBody
创建一个压缩流然后发送到service层,在service层,我会做export.
预设代码如下:
@PostMapping(value="/export", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity< StreamResponseBody > export() {
// customService.generateZipStream() is a service method that can
//generate zip file using ZipOutputStream and then return this stream
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment;filename=export.zip")
.body(outputStream -> {
// Use inner implement and set StreamResponseBody to ZipOutputStream
try(ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
customService.generateZipStream(zipOut);
}
});
}
customService
前导码:
public void generateZipStream(ZipOutputStream zipOut) {
// ... do export here
zipOut.putEntry(...);
zipOut.write(...);
zipOut.closeEntry();
// ... balabala
}
希望有类似问题的可以帮到你
如果你正在使用Spring 3,你可以使用很多swagger注释接口来帮助干净地构建您的 ResponseEntity
在使用 StreamingResponseBody
正确原型化 spring 所期望的格式时。
此处的 body
代码是将 ZipOutputStream
流映射到控制器期望 return 的 StreamingResponseBody
类型的简化方法(.body(out -> {...})
这在我下面的代码中)。
[控制器] 代码如下所示:
@GetMapping(value = "/myZip")
@Operation(
summary = "Retrieves a ZIP file from the system given a proper request ID.",
responses = {
@ApiResponse(
description = "Get ZIP file containing data for the ID.",
responseCode = "200",
content = @Content(schema = @Schema(implementation = StreamingResponseBody.class))),
@ApiResponse(
description = "Unauthenticated",
responseCode = "401",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
@ApiResponse(
description = "Forbidden. Access Denied.",
responseCode = "403",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
})
public ResponseEntity<StreamingResponseBody> myZipBuilder(@RequestParam String id, HttpServletResponse response)
throws IOException {
final String fileName = "MyRequest_" + id + "_" + new SimpleDateFormat("MMddyyyy").format(new Date());
return ResponseEntity.ok()
.header(CONTENT_DISPOSITION,"attachment;filename=\"" + fileName + ".zip\"")
.contentType(MediaType.valueOf("application/zip"))
.body(out -> myZipService.build(id, response.getOutputStream()));
}
您的服务代码 build
方法只需要接收数据所需的任何参数,加上 您的 ServletOutputStream responseOutputStream
参数以允许您构建由该流播种的 ZipOutputStream
对象。
在我下面的小示例中,您可以看到我在 buildDataLists
方法(未显示)中构建了一些 CSV 数据,这只是一个 ListstreamWriteCsvToZip
将它们推送到 ZipOutputStream
对象中。关键是,您构建 ZIP 流时使用控制器提供的 responseOutputStream 作为种子。完全构建 zip 后,请确保将其关闭(在我的例子中 zos.close()
)。然后return将zos
对象传给控制器。
/**
* Get ZIP file containing datafiles for a given request id
*
* @param id of the request
* @param responseOutputStream for streaming the zip results
* @return ZipOutputStream a ZIP file stream for the contents
* @throws AccessDeniedException if user does not have access to this function
* @throws UnauthenticatedException if user is not authenticated
*/
public ZipOutputStream build(String id, ServletOutputStream responseOutputStream) throws IOException {
try {
List<List<String[]>> csvFilesContents = buildDataLists(id);
final ZipOutputStream zos = new ZipOutputStream(responseOutputStream);
streamWriteCsvToZip("control", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("roles", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("accounts", id, zos, csvFilesContents.remove(0));
zos.close(); // finally closing the ZipOutputStream to mark completion of ZIP file
return zos;
} catch (IOException | ClientException ex) {
throw ex;
}
}
这里没有魔法。只需获取您的数据并将其放入 zip 流中即可。就我而言,我正在提取 list/array 数据,将其放入 CSV 中,然后将该 CSV 放入 zip 中(作为使用 zos.putNextEntry(entry);
的条目)。 CSV 和 ZIP 都保存为流,因此在此操作期间不会向文件系统写入任何内容,最终结果可以由控制器流出。确保每次向 zip 输出流写入一个条目时关闭该条目 (zos.closeEntry()
)。
private void streamWriteCsvToZip(String csvName, String id, ZipOutputStream zos, List<String[]> csvFileContents)
throws IOException {
String filename = id + "_" + csvName + ".csv";
ZipEntry entry = new ZipEntry(filename); // create a zip entry and add it to ZipOutputStream
zos.putNextEntry(entry);
CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(zos)); // Directly write bytes to the output stream
csvWriter.writeAll(csvFileContents); // write the contents
csvWriter.flush(); // flush the writer
zos.closeEntry(); // close the entry. Note: not closing the zos just yet as we need to add more files to our ZIP
}