如何在Quarkus中用Uni和AsyncFile实现MinIO大文件预览?
在Quarkus中用Uni和Reactive流实现MinIO大视频文件的流式预览
要实现不落地临时文件的MinIO大视频预览,核心是利用Quarkus的Reactive特性,直接将MinIO的输入流转为响应流,同时支持HTTP范围请求(视频拖动需要)。以下是完整实现方案:
1. 添加必要依赖
确保pom.xml中包含Quarkus MinIO、Reactive REST和Vertx扩展:
<dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-minio</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-reactive</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-vertx</artifactId> </dependency> </dependencies>
2. 配置MinIO连接
在application.properties中填入你的MinIO服务信息:
quarkus.minio.endpoint=http://your-minio-host:9000 quarkus.minio.access-key=your-access-key quarkus.minio.secret-key=your-secret-key quarkus.minio.bucket=your-target-bucket quarkus.minio.ssl=false # 如果用HTTPS则设为true
3. 实现流式预览接口
这个接口会处理Range请求,从MinIO读取指定字节范围的数据流,直接返回给客户端,完全不需要本地存储:
import io.minio.MinioClient; import io.minio.GetObjectArgs; import io.minio.StatObjectArgs; import io.minio.StatObjectResponse; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; @Path("/preview") public class MinioVideoPreviewResource { @Inject MinioClient minioClient; @Inject io.vertx.mutiny.core.Vertx vertx; // 可从配置中读取,此处为示例写死 private static final String TARGET_BUCKET = "your-target-bucket"; @GET @Path("/video/{objectName}") public Uni<Response> streamVideo(@Context HttpHeaders headers, @PathParam("objectName") String objectName) { // 先获取文件元数据(大小、类型) return Uni.createFrom().completionStage(() -> minioClient.statObject(StatObjectArgs.builder() .bucket(TARGET_BUCKET) .object(objectName) .build())) .onItem().transformToUni(stat -> { long totalSize = stat.size(); String contentType = stat.contentType() != null ? stat.contentType() : "video/mp4"; // 解析Range请求头,支持视频进度拖动 String rangeHeader = headers.getHeaderString(HttpHeaders.RANGE); long startByte = 0; long endByte = totalSize - 1; if (rangeHeader != null) { String[] rangeParts = rangeHeader.replace("bytes=", "").split("-"); startByte = Long.parseLong(rangeParts[0]); if (rangeParts.length > 1 && !rangeParts[1].isEmpty()) { endByte = Long.parseLong(rangeParts[1]); } endByte = Math.min(endByte, totalSize - 1); } // 从MinIO获取指定范围的输入流 return Uni.createFrom().completionStage(() -> minioClient.getObject(GetObjectArgs.builder() .bucket(TARGET_BUCKET) .object(objectName) .offset(startByte) .length(endByte - startByte + 1) .build())) .onItem().transform(inputStream -> { // 将输入流转为Reactive Multi,实现流式传输 ReadableByteChannel channel = Channels.newChannel(inputStream); Multi<ByteBuffer> dataStream = Multi.createFrom().generator( () -> channel, (ch, emitter) -> { ByteBuffer buffer = ByteBuffer.allocate(8192); // 8KB缓冲区,可按需调整 try { int readBytes = ch.read(buffer); if (readBytes == -1) { emitter.complete(); return null; } buffer.flip(); emitter.emit(buffer); buffer.clear(); return ch; } catch (Exception e) { emitter.fail(e); return null; } } ); // 构建响应 Response.ResponseBuilder responseBuilder; if (rangeHeader != null) { // 部分内容响应,符合HTTP Range规范 responseBuilder = Response.status(Response.Status.PARTIAL_CONTENT) .header(HttpHeaders.CONTENT_RANGE, String.format("bytes %d-%d/%d", startByte, endByte, totalSize)); } else { responseBuilder = Response.ok(); } return responseBuilder .header(HttpHeaders.ACCEPT_RANGES, "bytes") .header(HttpHeaders.CONTENT_LENGTH, endByte - startByte + 1) .header(HttpHeaders.CONTENT_TYPE, contentType) .entity(dataStream) // 直接传入Multi,Reactive REST自动处理流式输出 .build(); }); }) // 异常处理:文件不存在返回404 .onFailure().recoverWithItem(e -> Response.status(Response.Status.NOT_FOUND).entity("视频文件不存在").build()); } }
关键说明
- 无本地存储:直接从MinIO获取输入流,转成Reactive流后返回,全程不落地文件。
- Range请求支持:解析HTTP Range头,返回指定字节范围的内容,让浏览器可以支持视频进度拖动。
- 低内存占用:用8KB缓冲区分批读取数据,不会把整个大文件加载到内存。
- 异步非阻塞:基于Uni和Multi实现全异步处理,符合Quarkus的Reactive架构。
内容的提问来源于stack exchange,提问作者Kambei
相关产品推荐
相关产品推荐

