news 2026/10/8 8:59:32

基于Spring Boot的视频播放网站开发实战:从上传转码到Vue部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot的视频播放网站开发实战:从上传转码到Vue部署

做了好几个类似Spring Boot的视频网站项目之后,我觉得可以把我复盘出来的东西认真写一写。这个"基于Spring Boot的视频播放网站"算不上什么特别新奇的项目,但如果你真打算动手做——不管是为了毕设、个人作品集,还是接一个小型商业项目——这里面从技术选型到部署上线的坑,比想象中要多得多。尤其是当你把前端、视频处理、权限控制这些模块串在一起,Spring Boot本身那些看似简单的配置,往往会变成最花时间的部分。

这篇文章我按我实际打过的项目来拆,从整体设计思路、核心表结构、上传与转码流程,到Vue打包后怎么塞进Spring Boot部署、哪些配置容易踩雷,全部会聊到。

1. 内容整体设计与思路拆解

1.1 先想清楚你要做的是"网站"还是"平台"

拿到"基于Spring Boot的视频播放网站"这个需求时,第一步不是急着建工程,而是要先界定边界。视频播放网站往小说只是一个能上传视频、播放视频的Web应用;往大了说,它会牵扯到视频转码、弹幕、评论、会员权限、后台管理、流量统计等一系列模块。

我一般会根据项目目标把它拆成三个版本:

  • 基础版:用户注册登录、视频上传、视频列表、视频播放(HTML5播放器直接拉静态视频文件)、后台管理(上架下架、分类管理)。
  • 进阶版:视频分片上传与断点续传、FFmpeg转码、视频封面生成、用户收藏点赞评论、浏览记录。
  • 完整版:会员体系与付费视频、弹幕系统、推荐系统、视频水印、后台数据看板、权限体系细粒度控制。

标题里只说了"基于Spring Boot的视频播放网站",但当你真正上手时,至少要到进阶版才算是"网站"而不是"静态视频列表页"。因为如果不做转码和分片,直接拿MP4在浏览器里播,移动端兼容性和加载速度会让你崩溃。

1.2 技术选型为什么是这套组合

Spring Boot背后的Spring生态成熟稳定,社区活跃度高,整合第三方组件极其方便。我实际使用中选型如下:

模块选型原因
后端框架Spring Boot 2.7.x稳定,兼容性比3.x好,对JDK8/11友好
持久层MyBatis-Plus省去大量单表CRUD代码,分页插件好用
权限认证Spring Security + JWT无状态认证,适合前后端分离
数据库MySQL 8.x主流,事务支持好
缓存Redis用于验证码、热门榜单、播放次数缓存
视频存储MinIO(本地私有化)或阿里云OSS本地环境用MinIO,公网项目用OSS
视频处理FFmpeg转码、截图、水印全靠它
前端Vue 2 / Vue 3 + Element UI打包后由Spring Boot托管静态资源,部署简单

这里有个值得注意的取舍:为什么不选Spring Boot 3.x?虽然3.x出来很久了,但它基于Jakarta EE,部分老版本依赖(比如某些MyBatis-Plus插件、非官方starter)会出现兼容性问题。如果你的项目有现成的旧业务代码要迁移,或者使用了一些不太活跃的第三方库,2.7.x反而是最稳的。如果是全新项目且团队成员习惯新生态,那3.x也没毛病,只是要做好依赖适配的心理准备。

1.3 项目结构怎么划分才不混乱

Spring Boot项目结构虽然网上说法很多,但我实际打完几个项目后觉得,按业务模块分包比按技术类型分包更顺畅。下面这个结构是我比较常用的:

com.example.videoplayer ├── common # 通用工具、返回结果封装、异常处理 │ ├── Result.java │ ├── ResultCode.java │ └── GlobalExceptionHandler.java ├── config # 配置类(WebMvc、Redis、MinIO、拦截器) ├── controller # 接口层(按业务拆Controller) ├── service # 业务层 │ └── impl ├── mapper # MyBatis-Plus的Mapper接口 ├── entity # 数据库实体 ├── dto # 请求/响应对象(避免直接用实体暴露给前端) ├── utils # JWT工具、MD5工具、FFmpeg命令工具 └── task # 定时任务(清理临时文件、刷新缓存等)

按业务模块化背后的逻辑是,视频播放网站的功能会持续叠加,今天加弹幕、明天加推荐,如果所有Controller堆在一个包下,后期维护成本剧增。从第一天起就按业务拆包,比之后重构省心十倍。

2. 核心表结构设计与权限模型

2.1 画清楚六张核心表

视频播放网站的表设计不复杂,但关系到核心体验。我用了六张表撑起主业务:

表名核心字段用途
userid, username, password, avatar, role, vip_expire用户信息与角色
videoid, title, description, cover_url, video_url, status, category_id, uploader_id, play_count, created_at视频主表
video_categoryid, name, sort视频分类
commentid, video_id, user_id, content, parent_id, created_at评论(支持层级)
danmuid, video_id, user_id, content, time_point弹幕(记录视频时间点)
play_recordid, user_id, video_id, progress, updated_at播放进度记录

视频表里有个字段经常被忽略——status。它不能只做简单的"上架/下架",我习惯定义成一组状态:

  • 0:转码中(上传后正在后台处理)
  • 1:待审核(转码完成,等待管理员审核)
  • 2:已发布(审核通过,可正常播放)
  • 3:已下架(违规或管理员手动下线)

这样设计的好处是,前端可以明确区分提示文案:"视频正在转码中请稍后""视频正在审核中""视频已下线",而不是笼统地显示"播放失败"。状态机的价值不在于多复杂,而在于让用户在任何情况下都知道发生了什么。

2.2 用户角色跟视频权限怎么拆分

很多初学者会把权限简单的做成"普通用户"和"管理员"两个角色。但如果要做付费视频、VIP会员视频,这个模型就不够用了。

我实际使用的是角色(Role) + 资源状态双重校验:

  • 用户表里的role字段:普通用户、管理员、超管。
  • 视频表里的vip_only字段:标记该视频是否需要VIP。
  • 用户表里的vip_expire字段:记录VIP到期时间。

在后端校验的时候,不能只判断"是不是VIP",而是要判断"VIP是否在有效期内"。这里我写过一个经典bug:用户VIP过期后,因为之前生成的JWT里写了vip=true,导致过期后仍能播放VIP视频。后来改成每次请求都查库校验vip_expire时间,问题才解决。JWT里的信息永远只能做展示,不能做权限判断的唯一依据。

权限拦截器的核心思路是这样的:

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头取token // 2. 解析token,能解析出来说明登录过 // 3. 判断请求的接口是否需要VIP权限,需要就校验vip_expire // 4. 校验通过放行,否则返回402/403 return true; } }

2.3 数据库层面的防刷设计

做视频网站一定会遇到刷播放量的问题。刚开始我的做法是每次播放请求直接video.play_count + 1,结果一个用户反复刷新页面,播放量就涨得离谱。后面改成:同一个用户对同一个视频,Redis里存一个24小时有效的标识,有过就不计数,没有才加1。这样避免频繁操作MySQL,Redis自身的原子性也防住了并发下的计数错误。

3. 视频上传、转码与播放链路

3.1 为什么上传必须做分片

直接往服务器上传一个几百MB的MP4,表面看没什么问题,实际上一旦网络波动,TCP连接断开,整个文件就要重传。这对用户体验是毁灭性的。

所以我的上传流程是:

  1. 前端计算文件MD5(通过Web Worker,不卡UI)。
  2. 后端检查MD5对应的文件是否已存在(秒传判断)。
  3. 后端生成本次上传的uploadId,前端按固定大小(如5MB)把文件切成多个分片。
  4. 每个分片独立上传,后端收到后写入临时目录,并记录分片索引。
  5. 所有分片传完,前端主动请求"合并分片"接口。
  6. 后端按索引顺序拼接分片,生成完整文件,再扔进视频处理队列。

分片大小需要根据实际网络环境调整。个人项目部署在普通服务器上,5MB一个分片比较合适;如果在内网环境,可以调到10MB甚至20MB;如果用户可能在弱网环境下使用,2MB更稳。没有绝对的标准,但建议设成可配置的项,而不是写死在代码里。

3.2 FFmpeg转码:不转码之前一切都是白干

如果你仅仅把MP4上传后直接给前端播放,很快会遇到几个问题:

  • 浏览器对视频编码格式支持不一致(尤其是Safari对某些MP4的H.264编码没问题,但对WebM就可能无法播放)。
  • 视频文件体积大,加载缓慢,拖拽进度条卡顿。
  • iPhone上部分视频无法正常播放。

解决这些问题的标准方案就是转码为HLS协议输出。用FFmpeg把MP4转成m3u8索引文件和一个个.ts分片,前端使用hls.js播放。这样视频首屏加载快、拖动进度条流畅,兼容性也最强。

实际用到的核心命令大概是这样的:

ffmpeg -i input.mp4 -profile:v baseline -level 3.0 -start_number 0 \ -hls_time 10 -hls_list_size 0 -f hls output.m3u8

解释下几个参数:

  • -profile:v baseline:使用H.264的baseline级别,兼容性最好,老设备也能解。
  • -hls_time 10:每个ts分片时长10秒,个人网站建议10-15秒,太短会导致请求过多。
  • -hls_list_size 0:表示保留所有分片在m3u8列表中,否则默认只保留最近几个分片。

转码是非常耗CPU的操作。在2核4G的小服务器上,一个30分钟的视频可能转码要跑5-10分钟。所以绝不能在前台接口里同步执行转码,必须丢到后台异步处理。我用的是最简单的方案:上传完成后,把转码任务信息扔进数据库任务表,然后用Spring Boot的@Async注解异步执行,另一台机器或同一台机器的后台线程池去消费。

3.3 视频播放鉴权怎么做才不卡顿

视频直链如果不做鉴权,别人拿到URL就能随意播放、下载甚至盗链。但如果你每次播放都先请求后端接口拿一个临时播放地址,再交给播放器,又会增加一次跳转延迟。

我的方案是URL签名防盗链:

  • 视频上传后,保存原始路径,不直接暴露。
  • 前端请求/api/video/getPlayUrl?id=xx,后端校验用户权限(是否登录、是否VIP),校验通过后用HMAC算法生成一个带过期时间的签名URL。
  • 播放器拿这个签名URL去请求视频,后端解析签名,过期或无效则拒绝。

签名URL的格式类似这样:

/video/2025/04/01/xxxx.mp4?sign=md5(uuid+expireTime+密钥)&expire=1714560000

这样即使URL被别人抓包拿到,过期后也会失效。签名密钥一定不能放在前端代码里,只放在后端配置中。

4. Spring Boot项目实战难点记录

4.1 Spring Boot自动装配原理在项目中的实际引用

很多初学者在网上刷到过"Spring Boot自动装配原理"的面试题,但在实际项目中,什么时候真正用到这个知识?我举一个我项目里遇到的例子。

我想给项目加一个统一的日志切面,用来记录每个接口的耗时和异常。刚开始我在pom.xml里引入spring-boot-starter-aop,然后写一个@Aspect类。但系统里有个第三方SDK,它自己带了一个旧版本的AOP相关依赖,启动时直接报了NoSuchMethodError。

排查的路径就是围绕自动装配:Spring Boot在spring.factories或AutoConfiguration.imports中注册了大量自动配置类,它们通过@ConditionalOnClass、@ConditionalOnMissingBean等条件决定是否生效。当项目里同时存在两个版本的AOP库时,自动配置会依据类路径上的实际类来决定装配什么,这时版本冲突就会导致装配出错误的结果。

后来我用mvn dependency:tree查清楚冲突来源,在pom.xml里用exclusion排除掉第三方SDK传递的旧依赖,问题才解决。所谓"熟悉自动装配原理",在实战中就是遇到启动报错或Bean找不到时,能快速从自动配置的角度定位到类路径冲突、条件不满足之类的根因。问我为什么推荐spring-boot-starter-*统一管理版本?因为很多奇怪的无厘头报错,最后查下来都是依赖版本不一致。

4.2 Vue打包放进Spring Boot的具体操作

这是热搜里一个非常高频率的关键词:"vue打包放进springboot中"。很多人在开发环境前后端分离跑得很爽,但部署线上时不想单独整一台Nginx,想直接让Spring Boot把前端静态文件也托管了。

可以,而且很简单,但要注意几个坑。

首先在Vue项目里需要调整打包配置。以Vue 2为例,修改vue.config.js:

module.exports = { publicPath: './', // 关键:用相对路径,否则部署到Spring Boot子路径下会白屏 outputDir: 'dist', assetsDir: 'static', productionSourceMap: false }

publicPath如果默认是/,打包出来的JS/CSS路径会以根路径开始,而Spring Boot托管时资源路径通常在/static/或直接用/,一旦路径对不上,最典型的表现就是页面白屏,控制台报404找不到js。

然后,在Spring Boot中把打包好的dist目录放到src/main/resources/static里(或者打到Jar外的指定目录),同时为了支持Vue Router的history模式(非hash模式),需要配置一个路径转发规则:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 将非接口路径的请求都转发到index.html,交给前端路由处理 registry.addViewController("/").setViewName("forward:/index.html"); registry.addViewController("/{path:[^\\.]*}").setViewName("forward:/index.html"); } }

这个配置的意思是,所有不包含点的路径(即不是静态资源文件的请求)都转发到index.html。否则你在前端路由/video/detail刷新一下,Spring Boot会返回404。

4.3 Spring Boot版本太高导致的依赖问题

搜索词里"springboot版本太高"能成为热搜,说明这事踩坑的人真不少。Spring Boot 2.7升3.x最典型的问题是javax.*变jakarta.*。比如:

  • 旧代码import javax.servlet.http.HttpServletRequest在Spring Boot 3里直接编译不过,得改成jakarta.servlet.http.HttpServletRequest。
  • 很多第三方库如果不适配Jakarta,在Spring Boot 3下根本启动不了。

另外有些同学喜欢用最新版Spring Boot(比如3.3.x),然后去搜索资料,网上的教程还停留在2.x时代,照抄之后各种报错。我的建议是:如果你是为了快速做项目而不是尝鲜,选稳定版本而不是最新版本。我用2.7.x完成了好几个项目,一次都没遇到大坑。当然这有个前提:你不依赖JDK17+的新语法特性。Spring Boot 3.x的新特性当然很好,但团队里的协作成本、第三方库兼容风险都要提前测。

4.4 定时任务与配置管理

视频播放网站里,定时任务用到的场景很多,我举几个实际案例:

  1. 每天凌晨清理临时上传目录中超过24小时未合并的分片文件。
  2. 每10分钟同步一次Redis中的播放量到MySQL。
  3. 每天刷新用户VIP过期状态。

Spring Boot里开启定时任务非常简单,启动类上加上@EnableScheduling,然后在具体方法上加@Scheduled(cron = "...")。但真正线上跑的时候,有几个坑值得注意:

  • 单机环境下@Scheduled默认是单线程执行的。如果任务A跑了很久,任务B会排队等。如果你有多个任务且耗时不一,建议自己配置一个线程池:
@Configuration public class SchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }
  • 定时任务要考虑幂等。比如"同步播放量到MySQL"这个任务,如果你部署了两台实例做负载均衡,每台都会跑一次任务,播放量就double了。解决方案是加分布式锁(Redis setnx)或者干脆让定时任务只在某一台机器上开启(配置开关)。

4.5 Spring Boot配置里的那些不用可惜的细节

最后聊几个Spring Boot配置中的实用细节,属于"书上不讲、实际很香"的类型:

第一个是application.yml里的多环境配置。做视频网站时,我习惯拆成application-dev.yml(本地开发)、application-test.yml(测试服务器)、application-prod.yml(生产环境)。启动时用spring.profiles.active=prod指定即可。这背后的逻辑是,不同环境的数据库地址、Redis地址、OSS密钥都不一样,硬写在一个文件里每次部署都要改,太苦了。

第二个是自定义配置项。MinIO的endpoint、AccessKey、SecretKey,签名URL的密钥等,这些不属于Spring Boot标准配置范畴,但你会需要它们,直接在application.yml里自定义:

myconf: minio: endpoint: http://127.0.0.1:9000 access-key: admin secret-key: admin123 bucket: video

然后用@ConfigurationProperties绑定到一个配置类里:

@Data @Component @ConfigurationProperties(prefix = "myconf.minio") public class MinIOProperties { private String endpoint; private String accessKey; private String secretKey; private String bucket; }

这样配置也规范化了,代码里不会出现魔数。我一直坚持一个原则:任何和环境相关的值,绝不硬编码在业务代码里。

第三个是全局异常处理。Spring Boot项目里接口报错默认返回的是一堆堆栈信息,对前端极不友好。配置一个@RestControllerAdvice类,统一捕获异常并转成JSON格式返回,前端就能根据code字段统一处理错误弹窗。这事越早做越好,因为项目一大,接口一多,再去拦截就非常费力。

5. 部署上线与性能优化的几点心得

项目写完后,真正上线又有一轮新的磨炼。我当时部署选的是宝塔面板 + Docker。原本纠结要不要用K8s,后来想清楚了,个人项目或中小型项目用Docker Compose管理几个容器就够了,K8s的运维成本对一个小团队来说是负担而不是加分项。

部署清单大致是这样的:

  1. 用Docker跑MySQL和Redis,数据目录挂载到宿主机,避免容器删除后数据丢失。
  2. 写Dockerfile打包Spring Boot应用,注意Docker镜像使用JDK基础镜像,Jar包直接COPY进去。
  3. MinIO单独用Docker跑,映射端口9000(API)和9001(控制台)。
  4. Vue打完包后要么塞进Spring Boot的Jar里,要么单独用Nginx托管,两者都可以。如果视频量大,建议还是单独跑Nginx放静态资源,把Spring Boot纯粹当后端API服务器,这样静态资源和后端接口都互不干扰。

性能优化这部分,我总结了几条实在的:

  • 视频播放的核心瓶颈在带宽和视频编码,不在框架。你需要关注的是视频这个文件被访问时是否走本地磁盘IO,尽量给MinIO走内网地址而非公网。
  • 热门视频列表不要每次查数据库,缓存到Redis里,设置5分钟过期即可,能扛住大部分流量。
  • 视频转码任务堆积时,不要无限增加线程,要看CPU核数。2核机器建议FFmpeg并发数控制在1-2个,否则系统Load会爆炸。

6. 常见问题与排查技巧实录

我整理了一些做这个项目时高频出现的问题,直接列成速查表方便你排查:

问题表现排查思路与解决方案
前端打包后放进Spring Boot白屏控制台报错找不到JS文件检查Vue的publicPath是否设为./;清浏览器缓存
视频播放器转圈不播放m3u8请求404确认视频转码是否完成;检查MinIO/OSS的访问权限;确认签名URL是否过期
上传大视频超时Nginx返回504调整Nginx的client_max_body_size和proxy_read_timeout,上传接口走独立路径不过长超时
VIP视频未登录也播放成功JWT里vip字段过期每次播放请求必须查库校验vip_expire,不能只依赖JWT断言
数据库连接池爆掉报错Connection is not available排查有没有连接没关闭;调整HikariCP最大连接数;检查慢SQL是否锁表导致连接被占满
定时任务重复执行播放量翻倍检查是否多实例部署,加上Redis分布式锁

有一个隐蔽的问题要特别提一下:如果前端用了hls.js播放m3u8,而后端Nginx对m3u8和ts文件的Content-Type没配好,浏览器不认也会导致播放失败。需要在Nginx配置里加上:

location ~* \.(m3u8|ts)$ { add_header Cache-Control no-cache; types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } }

我当初为了这个"能下载但播不了"的问题折腾了整整一个晚上,最后才发现是Content-Type的问题。

7. 最后再分享两个实操小技巧

第一个是关于FFmpeg转码时的进度监控。由于转码是异步的,用户需要知道"视频处理到百分之多少了"。FFmpeg本身会输出time=字段来表示当前处理时间,我写了一个监听器去解析FFmpeg命令行输出的time参数,再用已处理时间 / 视频总时长算出百分比,存到Redis里。前端轮询接口就能实时展示转码进度,体验会好非常多。

第二个是关于Vue打包后静态资源的版本问题。Spring Boot里静态资源默认有缓存,前端改了JS重新打包扔进去,用户浏览器可能还是旧资源。解决方法是让前端打包时给JS和CSS文件名加上hash值(Vue CLI默认会做),同时后端设置带no-cache的响应头,这样每次部署后用户拿到的都是最新的文件,又不会每次都下载重复资源。

做这个项目最大的感受是:Spring Boot本身的上手难度并不高,真正考验人的是整个视频处理链路和部署细节。你越是对那些"看起来不起眼的配置"较真,线上出的幺蛾子就越少。希望这篇总结能让你在动手做"基于Spring Boot的视频播放网站"时,少走点我走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 8:59:32

SpringBoot2+Vue3校园闲置物品交易系统解析与二次开发指南

前段时间有位准备毕设的读者找我聊,说自己想做个“校园闲置物品交易系统”,但搜了一圈资料,要么是 SpringBoot2 配 JSP 的老古董,要么只有前端界面没有后端逻辑,真正前后端分离、源码完整还带文档的项目少得可怜。我当…

作者头像 李华
网站建设 2026/10/8 8:59:31

antSword-2.1.9源码深度解析:WebShell通信框架原理与定制开发

简介:本资源为开源Web渗透测试工具「中国蚁剑」2.1.9版本的完整源码包,面向网络安全从业者、渗透测试学习者及安全开发人员,用于深入理解轻量级WebShell管理工具的架构设计与安全机制。压缩包共2777个文件,以1471个JavaScript核心…

作者头像 李华
网站建设 2026/10/8 8:58:32

Flutter OpenHarmony游戏列表应用:dio网络请求与状态管理实战

1. 项目解读与整体设计思路1.1 从标题拆解核心需求看到这个标题,第一眼就能抓住三个关键词:Flutter、Open Harmony、dio。这是三天学习进阶的典型路径——第一天熟悉环境,第二天搞定基础组件,第三天开始接触真正的数据驱动应用。游…

作者头像 李华
网站建设 2026/10/8 8:57:46

Linux高性能调优:架构、内核参数与系统选型适配实战

同一台服务器,别人压测能跑到极限,你上线就隔三差五出幺蛾子,CPU看着没满,吞吐就是上不去。这种事儿在Linux圈子里太常见了。不少人第一反应是堆硬件、加实例,但真正的问题往往出在更底层——你的 架构设计、内核参数…

作者头像 李华
网站建设 2026/10/8 8:57:17

VS Code可视化调试Linux Coredump文件:完整配置与实战指南

最近排查一个服务崩溃问题,进程直接没了,日志最后一行停在某个诡异的地方,core文件倒是生成了,但一看大小,几个G,gdb进去啥也看不明白,函数调用栈全是问号。那时候我就在想,要是能用…

作者头像 李华
网站建设 2026/10/8 8:54:40

Flutter shuffler 鸿蒙化适配:打造大文件随机抽取命令行工具

最近在帮团队做数据样本处理工具链的鸿蒙化改造,发现一个很有意思的 Flutter 三方库 shuffler,它的核心能力刚好命中了我们一个棘手需求:从几十 GB 的日志文件里随机抽取指定行数,用于模型训练样本和线上问题复现。当时第一反应是…

作者头像 李华