如何使用Spring Webflux实现GridFS中MP4视频的流式返回
问题原因
返回乱码的核心是没有明确指定响应的媒体类型为video/mp4,浏览器默认将二进制视频流按文本格式解析渲染,就会出现满页乱码的情况,同时缺少必要的响应头配置也会导致浏览器无法正常识别为可播放的视频资源。
调整方案
方案1:修改原有逻辑适配要求
1. 控制器层调整
在@GetMapping注解中新增produces属性,明确声明接口返回MP4视频格式,避免Spring默认序列化处理:
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import reactor.core.publisher.Flux; @GetMapping(value = "/videos/{name}", produces = MediaType.VIDEO_MP4_VALUE) public Flux<Void> getVideo(@PathVariable String name, ServerWebExchange exchange){ return gridStorageService.read(name, exchange); }
2. 业务逻辑层调整
写入响应前先配置正确的响应头,同时补充文件不存在的异常处理:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public Flux<Void> read(String id, ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); return this.reactiveGridFsTemplate.findOne(Query.query(Criteria.where("filename").is(id))) // 处理文件不存在的场景 .switchIfEmpty(Mono.defer(() -> { response.setStatusCode(HttpStatus.NOT_FOUND); return Mono.empty(); })) .flatMap(reactiveGridFsTemplate::getResource) .flatMapMany(resource -> { HttpHeaders headers = response.getHeaders(); // 明确返回内容为MP4视频 headers.setContentType(MediaType.VIDEO_MP4); // 配置为inline让浏览器直接播放,替换为attachment可触发下载 headers.setContentDispositionFormData("inline", id + ".mp4"); // 支持范围请求,允许用户拖动视频进度条 headers.add(HttpHeaders.ACCEPT_RANGES, "bytes"); try { // 可选配置:设置文件总长度 headers.setContentLength(resource.contentLength()); } catch (Exception e) { // 忽略长度获取异常,不影响流式返回 } return response.writeWith(resource.getDownloadStream()); }); }
方案2:更简洁的实现方式
不需要手动操作ServerWebExchange,直接返回ReactiveGridFsResource即可,Spring Webflux会自动处理流式写入、响应头配置和范围请求支持,代码更简洁不易出错:
import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.data.mongodb.gridfs.ReactiveGridFsResource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import reactor.core.publisher.Mono; @GetMapping(value = "/videos/{name}", produces = MediaType.VIDEO_MP4_VALUE) public Mono<ResponseEntity<ReactiveGridFsResource>> getVideo(@PathVariable String name) { return reactiveGridFsTemplate.findOne(Query.query(Criteria.where("filename").is(name))) .flatMap(reactiveGridFsTemplate::getResource) .map(resource -> ResponseEntity.ok() .contentType(MediaType.VIDEO_MP4) .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + name + ".mp4\"") .body(resource)) .defaultIfEmpty(ResponseEntity.notFound().build()); }
内容的提问来源于stack exchange,提问作者Przemysław Świat
相关产品推荐
相关产品推荐

