牛奶味的巧克力 發表於 2025-7-3 08:57:00

SpringMVC流式传输媒体数据

<blockquote>
<p>借助Spring的ResourceHttpRequestHandler可以实现媒体数据的传输,比如在线播放视频、预览图片等。</p>
</blockquote>
<h2 id="目前已知spring-boot传输视频流的方法">目前已知Spring Boot传输视频流的方法</h2>
<ol>
<li>读取整个视频文件,然后把文件流写入HttpServletResponse的OutputStream。<br>
(此方法可行,但是需要消耗较多的服务器资源,且客户端需要下载整个视频才能播放)</li>
<li>使用HTTP的Range实现分片加载,但是需要手动实现,比较麻烦。</li>
<li>使用Spring自带的ResourceHttpRequestHandler是最佳实践。</li>
</ol>
<h2 id="思路">思路</h2>
<p>ResourceHttpRequestHandler是Spring Boot用于加载静态资源的一个类,默认用于从"classpath:/static"等目录读取静态资源,以便前端访问。我们可以继承它自定义一个实现。</p>
<h2 id="使用方法">使用方法</h2>
<p>在项目的config包(推荐)继承ResourceHttpRequestHandler并重写getResource方法,使其返回所需要呈现给前端的资源(org.springframework.core.io.Resource)</p>
<pre><code class="language-java">package com.example.server.config;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.List;

@Component
public class CustomResourceHttpRequestHandler extends ResourceHttpRequestHandler {
    private Resource resource;

    @Override
    protected Resource getResource(@NonNull HttpServletRequest request) {
      return this.resource;
    }

    public void setResource(Path filePath) throws MalformedURLException {
      this.resource = new UrlResource(filePath.toUri());
      setLocations(List.of(this.resource));
    }
}

</code></pre>
<p>控制层注入CustomResourceHttpRequestHandler,并向setResource方法传入文件路径(java.nio.file.Path),设置请求头,最后让customResourceHttpRequestHandler处理请求。</p>
<pre><code class="language-java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.file.Path;
import java.nio.file.Paths;

@Controller
@RequestMapping("/files")
public class FileController {

    @Autowired
    private CustomResourceHttpRequestHandler resourceHttpRequestHandler;

    @GetMapping("/{filename}")
    public ResponseEntity&lt;?&gt; getFile(@PathVariable String filename, HttpServletRequest request, HttpServletResponse response) {
      try {
            // 解析文件路径
            Path filePath = Paths.get("D:/StorageService").resolve(filename).normalize();

            // 检查文件是否存在
            if (!filePath.toFile().exists()) {
                return new ResponseEntity&lt;&gt;(HttpStatus.NOT_FOUND);
            }

            // 设置资源路径
            resourceHttpRequestHandler.setResource(filePath);

            // 设置响应头,inline 会在浏览器中显示或播放文件
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"");

            // 让 CustomResourceHttpRequestHandler 处理请求
            resourceHttpRequestHandler.handleRequest(request, response);
            return new ResponseEntity&lt;&gt;(HttpStatus.OK);
      } catch (Exception e) {
            return new ResponseEntity&lt;&gt;(HttpStatus.INTERNAL_SERVER_ERROR);
      }
    }
}
</code></pre>
<h2 id="注意点">注意点</h2>
<p>控制层返回值需要ResponseEntity类型(org.springframework.http.ResponseEntity),且try-catch不能省略,否则可能会不断抛出异常(AsyncRequestNotUsableException和IOException)但不影响正常使用。</p>
<h2 id="其他方案">其他方案</h2>
<p>如果有条件也可以使用MinIO,或者视频云点播VOD。他们提供了现成的解决方案,通过调用API可以获取视频等文件的直链。</p>
<h2 id="参考资料">参考资料</h2>
<p>springboot+vue播放视频流(无需下载视频,可以拖动进度、倍速播放)</p><br><br>
来源:https://www.cnblogs.com/MagicDoge/p/18962881
頁: [1]
查看完整版本: SpringMVC流式传输媒体数据