Java毕设实战:SpringBoot+Vue+FFmpeg构建视频点播平台全流程
每年到毕设季,Java方向的选题清单里总会出现几个"XX视频平台"之类的题目,"光影视频平台"就是其中很有代表性的一种。这东西说起来不算多难,但真上手之后你会发现,一个完整的视频点播系统牵扯到的技术面远比想象中宽:Java后端、Vue前端、SpringBoot接口、MySQL数据库只是基础盘,真正让人掉头发的是视频处理、流媒体播放、前后端联调、最后部署上线这一连串问题。这篇就把我完整做下来的经验捋一遍,从架构设计到数据库建表,从FFmpeg转码到Vue播放m3u8,再从服务器部署到答辩准备,给准备做类似题目的同学一条能直接跟着走的路。
标题里写了"程序+数据库+报告+部署教程+答辩指导",说明这不是一个只跑通Demo就完事的项目,而是要经得起答辩老师追问、能够展示完整工程能力的作品。我的经验是先别急着写代码,把整体思路理清楚比什么都重要。
1. 这个"光影视频平台"到底要做什么:项目定位与全套技术栈
1.1 功能需求先想明白,代码才不会白写
很多人拿到题目第一反应是"做一个能播放视频的网站",这个理解太粗糙了。答辩时老师问你"系统有哪些角色、每个角色能干什么",如果你说不清楚,分数直接掉一档。我当时把需求拆成了两层:
普通用户端 要做的事:注册登录、浏览视频列表、按分类筛选、关键词搜索、点进详情页看视频信息、播放视频、点赞收藏、发表评论。这些功能基本覆盖了"用户能正常使用"的全部路径。
管理端 要做的事:管理员登录、视频上传、视频信息编辑、视频上下架、分类管理、用户管理。这里最核心的是视频上传,因为视频不是简单传个文件就完了,还要触发后端的转码处理、封面生成、信息登记。
除了功能,还要定好非功能需求,哪怕只是写在报告里:用户密码不能明文存、接口要有登录校验、视频文件不能谁拿到链接都能下、系统要能支撑一定量的并发访问。这些点不是空话,后面每一条都会落到具体技术上。
1.2 技术选型:为什么是Java+Vue+SpringBoot,而不是别的
做毕设或者课设,技术选型的核心原则是: 用你讲得清楚的技术,而不是用听起来高级但你答不上来的技术 。选Java+Vue+SpringBoot这套组合,原因是它足够主流、资料足够多、出问题能找到答案,同时叠加起来又能撑起一个完整项目的体量。
- Java + SpringBoot :SpringBoot基于Spring框架,通过自动配置和内嵌Tomcat把开发部署的门槛降下来了,很适合快速构建后端服务。选它而不是原生SpringMVC,是因为你不需要写一堆XML配置,能让精力集中在业务逻辑上。
- Vue :渐进式框架,组件化开发,数据双向绑定让页面交互写起来很顺手。Vue2和Vue3都有大量现成组件库,拿来就能用。
- MySQL :中小型系统的万金油数据库,存储关系型数据(用户、视频信息、评论)非常合适。
- FFmpeg :视频处理工具,用来把用户上传的MP4转成HLS流(m3u8+ts切片),这是播放器能够流畅播放、拖动进度条的关键,后面详细讲。
- Redis :不一定强制要求,但我建议加上,用来缓存播放量和登录Token,答辩时这是加分项。
整个项目用前后端分离架构:Vue负责页面渲染和用户交互,通过HTTP请求调用SpringBoot提供的RESTful接口,SpringBoot负责业务逻辑和数据持久化,MySQL落库,FFmpeg作为外部工具处理视频。各层职责清晰,出了问题也方便排查。
1.3 项目结构规划:后端分包和前端页面清单
开工前先把工程结构规划好。后端我习惯这样分包:
com.guangying.video
├── controller // 接口层
├── service // 业务逻辑层
├── mapper // 数据访问层
├── entity // 数据库实体
├── dto // 请求/响应对象
├── config // 配置类(跨域、拦截器、异步任务)
├── common // 通用工具、统一返回结果
└── task // 定时任务
前端页面清单也要明确:用户端首页、视频列表页、视频详情页、搜索列表页、登录注册页、个人中心页;管理端登录页、视频管理页、上传页、分类管理页、用户管理页。把这些页面和对应的后端接口列成一张对应表,开发的时候照着做就行,不用边写边想。
2. 数据库是第一块地基:表结构设计与关键字段取舍
2.1 核心表的落地SQL
我设计的数据表围绕"用户、视频、分类、互动"这四条线展开。直接看建表SQL更直观,下面这个是整个系统最核心的视频信息表:
CREATE TABLE `video_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL COMMENT '视频标题',
`cover_url` varchar(500) DEFAULT NULL COMMENT '封面图地址',
`description` text COMMENT '视频简介',
`category_id` bigint(20) DEFAULT NULL COMMENT '所属分类',
`original_url` varchar(500) DEFAULT NULL COMMENT '原始文件地址(mp4)',
`hls_url` varchar(500) DEFAULT NULL COMMENT '转码后m3u8地址',
`duration` bigint(20) DEFAULT 0 COMMENT '时长(秒)',
`file_size` bigint(20) DEFAULT 0 COMMENT '文件大小(字节)',
`play_count` bigint(20) DEFAULT 0 COMMENT '播放量',
`like_count` bigint(20) DEFAULT 0 COMMENT '点赞数',
`status` tinyint(4) DEFAULT 0 COMMENT '0转码中 1已就绪 2已下架',
`upload_user_id` bigint(20) DEFAULT NULL COMMENT '上传者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_status` (`status`),
KEY `idx_play_count` (`play_count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频信息表';
配套的用户表和分类表比较常规:用户表字段是id、username、password、nickname、avatar、role、create_time;分类表是id、category_name、sort、create_time。互动类的评论表和收藏表也必须有,评论表用parent_id字段支持楼中楼回复,收藏表用video_id + user_id做联合唯一索引防止重复收藏。
2.2 几个值得琢磨的字段设计细节
为什么视频表要同时存original_url和hls_url? 因为转码是异步的,用户上传的MP4原始文件要留底,万一转码失败还能重新处理。播放时优先走hls_url,HLS流比直接播放MP4更适合网络传输,支持拖动、能根据带宽自适应码率(如果做了多码率的话),还能把ts切片放在带鉴权的路径下做防盗链。
status状态字段别省。 视频上传后到真正可播放之间有个转码窗口期,前端列表页要根据status判断是否显示"播放中"状态。很多同学上传后直接改库里一条记录完事,不做转码状态流转,答辩被问到"转码失败怎么办"直接卡住。
播放量为什么不用count(*)统计? 如果播放量和点赞都去查关联表count,数据库压力会很大。课程设计规模下直接在video_info表里放play_count字段,播放时执行UPDATE才能体现出你的性能意识。更优秀的做法是播放量先写Redis,再定时批量刷回MySQL,这个后面讲。
建表一定要用utf8mb4。 存emoji昵称和特殊字符时,utf8mb4比utf8省太多事,这是很多初学者踩过的坑。
2.3 数据访问层选MyBatis还是MyBatis-Plus
我的建议是直接用MyBatis-Plus。它内置了通用的增删改查,分页插件一行配置就能用,单表操作不用写XML,代码量能砍掉一半。对应到SpringBoot工程里,引入依赖后配置分页拦截器:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
查询视频列表时直接new一个LambdaQueryWrapper,按分类、关键词、状态条件拼装,配合Page对象做分页,干净利落。
3. 后端核心链路:上传、转码、鉴权、接口实现
3.1 视频上传接口和本地存储路径
上传接口用SpringBoot的MultipartFile接收前端传来的文件。有两点要特别注意:一是后端要做文件类型和大小校验,不能只靠前端限制;二是上传后要按日期或者业务ID建目录存储,避免所有文件堆在同一个文件夹里。
我当时的上传流程是:接收文件 → 校验类型(视频只允许mp4/avi/mov等)和大小(限制到200MB)→ 按日期生成存储目录 → 用UUID重命名文件保存 → 记录原始文件路径到数据库,状态置为"转码中" → 异步触发FFmpeg转码。
这里有个经验之谈: 上传接口的请求体大小在SpringBoot里要额外配置 ,否则文件稍微大点就被拦截了。
spring:
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
3.2 FFmpeg转码为HLS:这是整个项目最值钱的环节
视频上传后,真正的难点来了:怎么让浏览器端的播放器顺畅地播放视频。直接返回MP4文件地址给前端播放器,简单是简单,但拖动进度条要等半天,网络差一点就卡死,答辩时你讲不出任何亮点。
所以我把视频统一转成HLS格式。HLS的全称是HTTP Live Streaming,核心思路是把一个完整视频切成若干个小片段(ts文件),再用一个m3u8索引文件记录这些片段的地址。播放器先拉取m3u8文件,拿到片段列表后按需加载播放,不需要等整个视频下载完成。
转码靠的是服务器上安装的FFmpeg,核心命令如下:
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-hls_time 10 \
-hls_list_size 0 \
-hls_segment_filename "/data/videos/hls/video_1001_%03d.ts" \
/data/videos/hls/video_1001.m3u8
参数的含义可以这么理解:
-c:v libx264
表示视频用H.264编码,兼容性最好;
-c:a aac
表示音频用AAC编码;
-hls_time 10
表示每个ts切片时长10秒;
-hls_list_size 0
表示m3u8文件里保留所有切片信息,不限制数量;最后是输出的m3u8文件路径。
转码是个耗时操作,不能同步放在上传请求里。我用的方案是SpringBoot的
@Async
注解加线程池,上传接口返回后立刻告诉前端"正在转码",后端在后台异步执行FFmpeg命令。转码成功就更新数据库状态为"已就绪"并写入hls_url,失败就把状态改回可重新上传的状态。
3.3 统一返回结果、全局异常和JWT登录鉴权
接口风格要统一。我定义了一个Result类,所有接口都返回相同结构的JSON,前端拿到后只判断code,不用为每个接口单独适配:
public class Result<T> {
private Integer code; // 200成功,401未登录,500服务器错误
private String message;
private T data;
}
登录鉴权我用的JWT。用户登录成功后后端生成Token返回给前端,前端存在localStorage里,每次请求都在请求头带上
Authorization: Bearer <token>
。后端通过拦截器统一校验:
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (token == null || !JwtUtil.verify(token)) {
response.setStatus(401);
return false;
}
Long userId = JwtUtil.getUserId(token);
UserContext.set(userId);
return true;
}
}
有的公开接口(比如视频列表、视频详情)要放行,有的接口(比如后台管理、评论发送)必须登录,拦截器里加个白名单判断就行。Token校验通过后把当前用户ID存到ThreadLocal里,后续业务随时能取。
3.4 Redis缓存播放量的设计思路
播放量这个字段有个特点:写操作极其频繁,但单个用户看视频时我们并不需要立刻让所有人看到最新值。所以最优做法是:用户点进视频详情页时,先查Redis里有没有播放量,有就自增,没有就先把MySQL的值加载到Redis再自增;定时任务每隔一段时间把Redis里的播放量批量写回MySQL。
Redis的操作命令对应到Java代码,核心是这两步:
// 用户点进视频详情时
redisTemplate.opsForValue().increment("video:play:" + videoId);
// 定时任务里批量同步
Set<String> keys = redisTemplate.keys("video:play:*");
for (String key : keys) {
Long count = redisTemplate.opsForValue().get(key);
videoMapper.updatePlayCount(videoId, count);
redisTemplate.delete(key);
}
这个方案不复杂,但答辩时你说出来"播放量没有直接怼数据库,而是通过Redis异步刷回",老师会立刻觉得你是有工程意识的。
4. 前端从零到跑通:Vue项目搭建与m3u8播放的实战细节
4.1 环境配置和依赖安装的两三件事
Vue项目的环境配置,十个有八个卡在Node.js版本和依赖安装上。用Vite创建Vue3项目的话,注意Node版本不要太旧,建议Vite 5以上配合Node 18+,安装依赖时如果网络慢,把npm镜像切到国内源:
npm config set registry https://registry.npmmirror.com
npm create vue@latest guangying-web
这里多说一句,SpringBoot版本和JDK版本要匹配,之前见过同学用JDK8去跑SpringBoot 3.x,各种奇怪报错。SpringBoot 2.x用JDK8没问题,SpringBoot 3.x最低要求JDK17,别在这个地方耽误时间。
4.2 Vue Router路由设计与会话守卫
前端路由和页面骨架对应着之前列出的功能清单。我设计的路由表大概是:
/ 首页(推荐视频列表)
/video/:id 视频详情页
/search 搜索结果页
/login 登录页
/register 注册页
/user 个人中心
/admin/video 管理端视频列表
/admin/upload 管理端上传页
/admin/category 管理端分类管理
路由守卫要处理登录状态。比如个人中心必须登录才能进,后台管理页必须角色是管理员才能进。最简单的实现是全局前置守卫判断本地有没有token,重要页面再检查一下用户角色。
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token');
const role = localStorage.getItem('role');
if (to.meta.requiresAuth && !token) {
next('/login');
} else if (to.meta.requiresAdmin && role !== 'ADMIN') {
next('/');
} else {
next();
}
});
4.3 axios封装和后端联调的跨域处理
前端所有请求通过axios发出,我习惯把axios实例单独封装成一个文件,设置统一的baseURL和请求拦截器。baseURL用相对路径
/api
,然后交给Nginx做反向代理,这样开发环境和生产环境都不需要处理跨域。
const request = axios.create({
baseURL: '/api',
timeout: 30000
});
request.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
如果开发时非要走跨域,也可以在SpringBoot端配置CorsFilter放开所有来源,但那只适合本地开发联调,生产环境还是要走Nginx代理更安全。
4.4 播放m3u8的完整方案:video标签不行的替代品
这是这个项目里前端最容易卡住的地方。很多人想当然地以为
<video src="xxx.m3u8">
就能播放,实际你会发现大部分浏览器根本不支持在原生video标签里播m3u8格式,Safari除外。所以必须借助JavaScript库来解析和拉流。我这里推荐hls.js,库的体积不大,支持范围广,遇到不支持的浏览器还能降级处理。
实际播放器组件的核心逻辑:
import Hls from 'hls.js';
function playVideo(videoElement, videoUrl) {
// Safari原生支持HLS,直接用src就行
if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
videoElement.src = videoUrl;
videoElement.play();
} else if (Hls.isSupported()) {
// 其他浏览器用hls.js来解析m3u8
const hls = new Hls();
hls.loadSource(videoUrl);
hls.attachMedia(videoElement);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoElement.play();
});
}
}
loadSource
加载m3u8索引文件,
attachMedia
把视频流绑定到video元素上,等索引解析完成就可以播放了。这四个API就能解决核心的播放问题。如果还想做清晰度切换、倍速等进阶功能,Hls的API也能满足。
4.5 管理端上传组件:FormData传文件别用JSON
管理端上传页的核心操作是用户选择了视频文件后,通过FormData传给后端。这里要注意:文件上传的请求头不能手动设置Content-Type为application/json,要跨域就让浏览器自动生成multipart/form-data的边界,后端才能正确解析:
const formData = new FormData();
formData.append('file', file);
formData.append('title', this.form.title);
formData.append('categoryId', this.form.categoryId);
formData.append('description', this.form.description);
request.post('/video/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
上传大文件时要同步加进度条,axios原生支持
onUploadProgress
回调,实时计算已上传百分比。
5. 部署上线的完整流程与踩坑实录
5.1 服务器环境准备清单
部署用的是Linux服务器(CentOS/Ubuntu都行),需要安装的东西有:JDK(版本和SpringBoot匹配)、MySQL、Nginx、FFmpeg、Maven(打包时可以用,也可以本地打包后传jar上去)。装FFmpeg时注意要装完整的libx264编码库,否则转出来的视频可能不是H.264,播放兼容性会受影响。
MySQL安装后有一件事很多新手容易漏:默认情况下MySQL只允许本机连接,你要改一下配置或者执行授权,才能让后端服务连上:
CREATE USER 'video_user'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON guangying_db.* TO 'video_user'@'%';
FLUSH PRIVILEGES;
5.2 后端打包和进程启动
后端在项目根目录执行:
mvn clean package -DskipTests
打出来的jar包在target目录下,上传到服务器后启动:
nohup java -jar guangying-video.jar --spring.profiles.active=prod > app.log 2>&1 &
把日志输出到app.log文件,这样即使你关闭终端,Java进程也能继续跑。启动后立刻看日志排查错误,最常见的启动失败原因是数据库连不上、Redis连不上、端口被占用。
5.3 前端打包和Nginx反向代理
前端执行
npm run build
后生成dist目录,把它整个上传到服务器,然后配置Nginx。Nginx要做两件事:一是托管Vue打包后的静态页面,二是把
/api
开头的请求反向代理到Java后端,三是把视频文件所在的
/media
路径映射到磁盘目录。
一份可以直接套用的Nginx核心配置:
server {
listen 80;
server_name your_server_ip;
# 托管前端静态文件
root /usr/share/nginx/html/dist;
index index.html;
# 解决Vue Router的history模式刷新404
location / {
try_files $uri $uri/ /index.html;
}
# 后端接口反向代理
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 视频文件静态访问
location /media/ {
alias /data/videos/;
}
}
一定要记得,配置改完执行
nginx -t
检查语法再
nginx -s reload
。视频文件放在Nginx直接能访问的路径下,比让SpringBoot读文件再输出要高效很多。
5.4 部署阶段最常遇到的几个坑
我把自己部署时实际踩过的问题整理了一下:
- 防火墙和云服务器安全组没放行端口 。Tomcat运行在8080,Nginx运行在80,你本地访问不了先检查安全组和防火墙,别急着怀疑代码。
- 视频上传目录没有写权限 。Linux下上传文件时报permission denied,给目录chmod 755或者chown给运行Java进程的用户即可。
- FFmpeg命令在代码里执行失败 。先在命令行手动执行一遍,确认FFmpeg确实装了、编码器确实支持,再排查Java执行命令的路径问题。
- Vue刷新页面404 。这是Vue Router history模式的经典问题,必须让Nginx配置try_files把所有非静态文件请求落到index.html,否则路由一深刷新就报错。
- 接口请求跨域 。生产环境下你看到的跨域报错,绝大多数是Nginx代理没配置对,baseURL用相对路径就能绕开。
6. 答辩高频问题与报告写作的实用思路
6.1 老师最爱问的几个问题,和应该怎么答
"为什么选前后端分离架构?"
答:前后端通过HTTP接口通信,前端只关注页面渲染,后端只关注业务逻辑,开发时可以并行推进,部署时前端静态页面用Nginx托管、后端独立运行,也方便横向扩展。这个答案逻辑清晰,老师基本不会再追问。
"视频为什么要转码成m3u8?直接播MP4不行吗?"
答:MP4需要完整加载到一定位置才能播放,网络波动时体验很差;HLS把视频切成ts切片,播放器按需拉取,天然支持拖动,以后要扩展多码率自适应也方便。只要能把这个逻辑讲清楚,这段基本就是加分项。
"你的数据库有哪些索引?为什么这么设计?"
答:主键索引肯定有,另外视频表的分类、状态、播放量字段都建了二级索引,因为列表页主要按这几个字段做条件查询和排序。再补充一句用EXPLAIN验证过走索引,老师会认为你确实调过。
"如果几万人同时看你的视频,系统会怎样?怎么优化?"
答:第一个瓶颈是带宽和视频文件分发,方案是把视频文件托管到CDN或者对象存储,Nginx层面做缓存;第二个瓶颈是数据库的连接和查询压力,方案是播放量、点赞这些热点数据先走Redis,接口层面加缓存,数据库做读写分离。能把这三层方案说清楚,超出课程设计的要求了。
"你这个项目最大的难点是什么?"
答:我一般会说是视频转码和播放这一条链路。上传文件要控制大小和类型,转码要异步执行不能卡住请求,转完要更新状态,前端又要兼容不同浏览器的HLS播放。这条链路走通,整个项目的技术含量就立住了。
6.2 毕业设计报告的结构建议
报告不用写得多花哨,但结构要完整。需求分析先讲清楚要解决什么问题、有哪些角色、要什么功能;总体设计画系统架构和功能模块图(可以用绘图工具画,别用Mermaid);详细设计挑上传转码、播放、鉴权这几个核心流程展开;数据库设计把所有表结构和关联关系列出来;系统实现部分每个模块配关键代码和截图;测试部分写测试用例和结果;最后是总结和展望。
写报告时我强烈建议: 每个功能描述后面紧跟"实现思路"和"遇到的坑" 。比如播放模块写"一开始用了直接返回MP4的方案,后来发现网络波动明显,改成了FFmpeg转HLS的方案"。这种文字比一百句"系统功能完善、运行稳定"都有说服力,老师翻报告时最喜欢看这种真实开发痕迹。
6.3 演示环节的准备工作
答辩演示现场最容易翻车的不是代码,而是演示环境。我建议准备两套方案:一套是本地跑起来完整演示,另一套是提前录制好的录屏视频,防止现场网络或者端口出问题。演示路径按"用户注册登录 → 浏览列表 → 搜索 → 播放 → 评论 → 管理员上传新视频 → 看到转码完成并播放"的顺序走,这条路径几乎覆盖了系统所有核心功能,也方便老师顺着你的节奏提问。
最后说几句实在的
做这类视频平台项目,真正花时间去啃的其实就两个地方:后端转码链路和前端m3u8播放。这两块任何一个卡住了,整个项目都交不了差,但只要把链路走通了,剩下的增删改查页面都是在已有骨架上填充内容。我个人体会最深的是,做完这个项目之后,SpringBoot异步任务、拦截器、文件操作、Vue组件通信这些平时零散的知识,突然就串成了一条完整的线。面试时被问到项目经验,我会直接把视频转码的流程和播放器兼容方案讲一遍,比单纯背八股文有说服力太多。如果你也正在做类似的题目,希望能把这条路上的关键节点讲明白了,剩下就是按部就班把代码一行行敲出来。
更多推荐

所有评论(0)