我想使用 spring webflux 创建一个端点,在其中创建一个包含多个文件的 zip 文件。我也根据一些数据库查询动态创建这些文件。由于这个 zip 文件可能很大,我想立即将处理后的数据流式传输到客户端,而不是构建整个 zip 文件,然后返回它。我该如何实现这一目标?对于 HttpServletResponse 的注入,有多种 MVC 解释,但 Spring Webflux 中没有这样的东西。这是我到目前为止所拥有的,但它只是下载了一个“response.html”文件,即使我将其修改为“response.zip”,该文件也已损坏。
@GetMapping(path = "/test", produces = "application/octet-stream")
public Mono<ResponseEntity<FileSystemResource>> test(
@ApiIgnore ServerHttpRequest request,
@ApiIgnore ServerHttpResponse httpResponse
) {
try {
String str = "Hello";
byte[] strToBytes = str.getBytes();
FileSystemResource zipFile = new FileSystemResource("result.zip");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile.getFile()));
ZipEntry zipEntry = new ZipEntry("1/Hello.txt");
out.putNextEntry(zipEntry);
out.write(strToBytes);
out.close();
return Mono.just(ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=result.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(zipFile));
} catch (Exception e) {
e.printStackTrace();
return Mono.empty();
}
}
```
您可以创建一个通量发射器,该发射器在发射器完成之前保持活动状态。
下面是使用
Resource
的示例方法,但这可以轻松地从外部源更新为 inputStream
或 Flux<DataBuffer
。
需要注意的是,要避免
public class ZipDataBuffer {
final DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
final DefaultDataBuffer buffer = factory.allocateBuffer(1024);
final ZipOutputStream zos = new ZipOutputStream(buffer.asOutputStream());
public static Flux<DataBuffer> toFlux(Resource... resources) {
var zipDataBuffer = new ZipDataBuffer();
return Flux.create(emitter -> Flux.fromArray(resources)
.flatMap(zipDataBuffer::write)
.doFinally(s -> {
emitter.next(zipDataBuffer.finish());
emitter.complete();
})
.subscribe(emitter::next));
}
public Mono<DataBuffer> write(Resource resource) {
var dataBufferFlux = DataBufferUtils.read(resource, factory, 1024);
return DataBufferUtils.join(dataBufferFlux)
.doOnNext(dataBuffer -> write(resource.getFilename(), dataBuffer))
.thenReturn(read());
}
@SneakyThrows
private void write(String filename, DataBuffer dataBuffer) {
try (var is = dataBuffer.asInputStream()) {
var zipEntry = new ZipEntry(filename);
zos.putNextEntry(zipEntry);
zos.write(is.readAllBytes());
zos.closeEntry();
}
}
private DataBuffer read() {
return buffer.split(buffer.writableByteCount());
}
@SneakyThrows
public DataBuffer finish() {
zos.finish();
zos.close();
return read();
}
}