# Spring Boot + MinIO + MySQL + Vue 实现视频管理与流式播放

在视频回放、监控分析等系统中,视频通常不能简单地当作普通文件下载。用户需要在网页中直接播放、拖动进度条、快进后退、从上次位置继续播放,并且不同角色还要看到不同范围的视频。

本文介绍一套基于 Spring Boot + MinIO + MySQL + Vue 的视频管理方案,实现:

  • 视频上传、删除、分页查看
  • 后端从 MinIO 读取视频流
  • 支持浏览器原生进度条拖动
  • 支持断点续播
  • 支持指定时间戳跳转
  • 支持前进 10 秒、后退 10 秒
  • 按角色和部门控制视频可见范围

一、整体架构

上传/列表/删除/进度
video Range 请求
携带用户角色 Header
视频元数据/播放进度
对象上传/分段读取
Vue 前端
Spring Boot API
Vite 视频代理
MySQL
MinIO

这里 MySQL 不保存视频本体,只保存视频元数据和播放进度;视频文件本体存储在 MinIO 中。

二、为什么拖动进度条依赖 HTTP Range

浏览器 <video> 播放视频时,如果用户拖动进度条,浏览器不会重新下载完整视频,而是发送类似这样的请求:

http 复制代码
Range: bytes=10485760-20971519

后端必须返回:

http 复制代码
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 10485760-20971519/73400320
Content-Length: 10485760
Content-Type: video/mp4

只要后端正确支持 Range,浏览器原生播放器就能自然支持拖动、快进、后退和时间跳转。

三、数据表设计

视频元数据表建议命名为 train_record_video

字段 说明
id 视频 ID
train_record_id 关联训练记录 ID
original_name 原始文件名
bucket_name MinIO bucket
object_name MinIO 对象路径
content_type 视频类型
file_size 文件大小
duration_seconds 视频时长
owner_user_id 上传用户 ID
owner_dept_id 上传用户部门 ID
owner_dept_path 上传用户部门路径
create_time 创建时间

播放进度表 train_record_video_progress

字段 说明
video_id 视频 ID
user_id 当前播放用户 ID
current_time_seconds 当前播放秒数
duration_seconds 视频总时长
last_played_at 最近播放时间

四、后端核心接口

text 复制代码
POST   /minio/video/upload
GET    /minio/video
GET    /minio/video/{id}
GET    /minio/video/{id}/stream
DELETE /minio/video/{id}
GET    /minio/video/{id}/progress
PUT    /minio/video/{id}/progress

其中最关键的是播放接口:

java 复制代码
@GetMapping("/{id}/stream")
public void stream(@PathVariable String id,
                   HttpServletRequest request,
                   HttpServletResponse response) {
    trainRecordVideoService.stream(id, request, response);
}

服务层根据 Range 读取 MinIO 指定字节段:

java 复制代码
InputStream inputStream = minioClient.getObject(
    GetObjectArgs.builder()
        .bucket(video.getBucketName())
        .object(video.getObjectName())
        .offset(rangeStart)
        .length(contentLength)
        .build()
);

然后返回 206 Partial Content

java 复制代码
response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + totalSize);
response.setHeader("Content-Length", String.valueOf(contentLength));
response.setContentType(video.getContentType());

五、权限控制

视频权限不根据用户名判断,而是根据角色和部门判断:
video_admin_user
video_manager_user
video_normal_user
当前用户
角色
查看全部视频
查看本部门及下级部门视频
仅查看自己上传的视频

查询列表时动态拼接条件:

text 复制代码
admin:
  不加 owner 过滤

manager:
  owner_dept_path like 当前部门路径%

normal:
  owner_user_id = 当前用户ID

详情、播放流、删除、播放进度接口也必须做同样的权限校验,避免用户绕过列表直接访问视频 ID。

六、Vue 前端播放

前端使用原生 <video>

html 复制代码
<video
  controls
  preload="metadata"
  :src="streamUrl"
  @loadedmetadata="onLoadedMetadata"
  @timeupdate="onTimeUpdate"
/>

给定时间戳自动跳转:

js 复制代码
videoEl.value.currentTime = timestampSeconds

前进 10 秒:

js 复制代码
videoEl.value.currentTime += 10

后退 10 秒:

js 复制代码
videoEl.value.currentTime = Math.max(0, videoEl.value.currentTime - 10)

保存断点续播:

js 复制代码
await fetch(`/api/minio/video/${videoId}/progress`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    currentTimeSeconds: videoEl.value.currentTime,
    durationSeconds: videoEl.value.duration
  })
})

再次打开视频时读取进度:

js 复制代码
const progress = await fetch(`/api/minio/video/${videoId}/progress`).then(r => r.json())
videoEl.value.currentTime = progress.currentTimeSeconds || 0

七、前端播放代理的必要性

浏览器原生 <video> 标签不能自定义 X-User-IdX-RolesX-Dept-Id 这类 Header。

所以在本地联调时,可以通过 Vite 增加一个代理:

text 复制代码
/video-stream/{id}

由代理把 <video>Range 请求转发给后端,同时补充用户、角色、部门 Header。
MinIO Spring Boot Vite 代理 Vue video MinIO Spring Boot Vite 代理 Vue video GET /video-stream/{id} Range: bytes=0-1023 GET /minio/video/{id}/stream Range + 用户角色Header getObject offset/length 视频字节流 206 Partial Content 206 Partial Content

八、上传大文件配置

视频文件通常较大,需要调整 Spring Boot multipart 限制:

yaml 复制代码
spring:
  servlet:
    multipart:
      max-file-size: 2048MB
      max-request-size: 2048MB

前端代理也建议设置较长超时,避免大文件上传时连接被提前断开。

九、测试重点

上传后先测试列表接口:

bash 复制代码
GET /minio/video?page=0&size=20

再测试 Range:

bash 复制代码
curl -i \
  -H "Range: bytes=0-1023" \
  -H "X-User-Id: user-001" \
  -H "X-Roles: video_normal_user" \
  http://localhost:8099/minio/video/{id}/stream

期望结果:

text 复制代码
HTTP/1.1 206
Accept-Ranges: bytes
Content-Range: bytes 0-1023/视频总大小
Content-Length: 1024

只要这里正确,网页播放器拖动进度条、指定时间跳转、快进后退就有了可靠基础。

十、总结

这套方案的关键不是"把视频文件返回给前端",而是:

  1. 视频本体存 MinIO,元数据和播放进度存 MySQL。
  2. 播放接口必须完整支持 HTTP Range。
  3. 前端使用原生 <video> 控制时间跳转和快进后退。
  4. 断点续播由前端上报当前秒数,后端按用户和视频保存。
  5. 权限必须覆盖列表、详情、播放流、删除和进度接口。

如果视频源主要是 MP4,还建议上传前或上传后确保 MP4 的 moov atom 位于文件头部,例如使用 ffmpeg -movflags +faststart,这样浏览器首帧加载和拖动体验会更稳定。

相关推荐
小李云雾2 小时前
Pinia:Vue3 全局状态管理从入门到精通
前端·javascript·vue.js
Database_Cool_2 小时前
数据仓库弹性扩缩容实践:阿里云 AnalyticDB MySQL 按需付费方案详解
数据仓库·mysql·阿里云
流星白龙2 小时前
【MySQL高阶】27.事务(2)-锁
android·mysql·adb
数据库小学妹2 小时前
MySQL并行复制原理与调优实战:LOGICAL_CLOCK到WRITESET_SESSION全链路优化
数据库·经验分享·mysql·性能优化·dba
风吹夏回2 小时前
Vue3 + Element Plus 完整使用指南
前端·javascript·vue.js·element
承渊政道2 小时前
【MySQL数据库学习】MySQL基本查询(上)
linux·数据库·学习·mysql·bash·数据库开发·数据库系统
学计算机的计算基2 小时前
MySQL 性能调优面试复习:Explain、索引、慢查询、缓存和架构优化
java·数据库·笔记·mysql
李少兄2 小时前
Spring Boot Test 启动类自动发现机制解析与工程实践
java·spring boot·后端
刃神太酷啦2 小时前
MySQL 库表操作 +数据类型+ 基础概念全梳理----《Hello MySQL!》(2)
java·c语言·数据库·c++·vscode·mysql·adb
骄马之死10 小时前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端