前后端分离视频点播系统源码实战:部署、转码与播放鉴权全解析
简介:视频点播系统完美版前后端分离开源包,面向PHP开发者、站长及视频平台学习者,提供一套可直接部署的视频点播网站解决方案。资源共2000个文件,以PHP后端逻辑、HTML/CSS/JavaScript前端页面为核心,辅以SQL数据库脚本、图片、视频素材等,整体81.5MB。文件类型覆盖js、php、html、css、jpg、sql等,完整支撑视频点播系统的前台展示、后台管理、附件配置等全流程业务。已有265人学习下载。包内附带清晰的搭建说明,涵盖前后端双域名配置、数据库导入、伪静态设置等关键步骤,并预留后台管理入口及常见配置项,开发者可据此快速完成环境部署与功能调试。通过这份开源包还能深入理解ThinkPHP框架下的前后端分离架构、视频模块开发及服务器部署实践,同时便于二次开发,扩展视频分类、播放器样式等功能。
1. 视频点播系统源码拿到手,真正的活从解压开始
"完美版"是分发者的话术,不是工程承诺。一套标注"前后端分离开源版"的视频点播系统源码,解压后一般是:一个 vue 前端目录、一个 springboot 后端目录、一份 sql 脚本和一个 README。能看不能跑的居多,能跑通但播不了的也不少。
从 zip 到可播放,这条链路绕不开四个环节:架构认识、环境对齐、上传转码、鉴权播放。新手按顺序走能把系统拉起来,老手直接看鉴权播放和末尾自测清单。
这套源码适合想在公司内网搭视频培训系统的全栈工程师,也适合独立建站的内容从业者,前提是能同时拿下 Vue、Spring Boot 和 FFmpeg。
2. 前后端分离的视频点播系统:先看懂目录与数据模型
2.1 视频点播为什么绕不开前后端分离
视频点播和普通 CMS 最大的区别是资源形态。视频文件体积大、加载时间长、播放状态多,前端需要独立的播放器控制逻辑和码率切换能力,后端则要处理上传、转码、存储分离。两者耦合在模板引擎里会非常痛苦,所以现在的开源视频点播系统基本都走前后端分离:Vue 负责页面与播放器,Spring Boot 提供 REST API。
另一个原因是团队分工。视频站点迭代最频繁的是前端样式和播放交互,后端接口相对固定。前后端分离之后,前端可以单独部署到 CDN,后端只暴露 API,视频文件走独立存储域名,三者互不阻塞。这也是"springboot vue前后端分离"被反复检索的底层原因——不是框架流行,而是这类系统的业务形态天然要求前后端解耦。
2.2 解压后先认目录:前后端源码的边界
拿到 zip 先别急着 npm install,先花十分钟把目录结构摸清楚。常见的布局有两种:根目录下直接分 frontend/ 和 backend/(也有叫 web/ 与 server/ 的),另一种是前端构建产物嵌在后端 static 目录里——这种严格说不算前后端分离,部署时要手工拆开。
从解压后最关心的文件列一张对照表:
| 路径 | 作用 | 需要改什么 |
|---|---|---|
| backend/src/main/resources/application.yml | 后端配置 | 数据库连接、上传路径、jwt 密钥 |
| backend/vod.sql | 初始化脚本 | 导入 MySQL,一般不用改 |
| frontend/src/api/request.js | axios 实例 | baseURL 与 token 注入逻辑 |
| frontend/vue.config.js | 开发转发 | 跨域目标端口 |
| README.md | 部署说明 | 按实际环境核对版本 |
一个值得注意的细节:很多开源视频点播项目是从若依框架改造来的。如果源码里出现 com.ruoyi 包名或 SysUser 这类表,说明它继承了若依的权限骨架,这时不要试图删掉这些"多余的表",登录鉴权依赖它们。反过来说,若依框架前后端分离的成熟度,恰好保证了这类项目登录和菜单大概率能直接使用,排查范围可以缩小到视频业务本身。
2.3 视频点播的核心表结构
无论前端用什么框架,视频点播的表结构大体一致:视频表、分类表、用户表、播放记录表。从 sql 脚本里先找到 vod_video(也可能是 video)表,这是整套系统的核心:
CREATE TABLE `vod_video` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL COMMENT '视频标题',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`cover_url` varchar(500) DEFAULT NULL COMMENT '封面图地址',
`origin_url` varchar(500) DEFAULT NULL COMMENT '原始视频路径',
`hls_url` varchar(500) DEFAULT NULL COMMENT 'm3u8 播放地址',
`duration` int NOT NULL DEFAULT 0 COMMENT '时长,单位秒',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0转码中 1可播放 2转码失败',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频主表';
这张表里 status 字段最容易被忽略。很多"完美版"源码把上传和转码做成同步调用,接口返回时视频已经能播;一旦改成异步队列,前端列表接口必须过滤 status=1,否则用户会点到一堆黑屏视频。hls_url 存的是转码完成后的相对路径,由前端拼接存储域名访问,不要在前端硬编码本地绝对路径。
配套表里再看 vod_category(分类,树形结构常见 parent_id 字段)和 vod_play_record(播放进度)。播放进度表是判断一套源码是否真正做过生产的重要标志——只有注册登录、没有播放记录上报的源码,演示性质更强,接 CDN 时这类缺失会直接暴露。
3. 本地跑通最小环境:从 zip 到首页能播
3.1 环境清单与版本匹配
前后端分离项目最大的部署障碍不是代码,是版本不匹配。Vue 2 + Spring Boot 2 的组合最常见;如果前端是 Vue 3 + Vite,Node 版本要求高于 16,老项目硬用高版本 Node 反而会报 OpenSSL 错误。先按这张表核对环境:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 以 pom.xml 里 java.version 为准 |
| Maven | 3.6+ | 后端构建工具 |
| Node.js | 14/16/18 | 与 Vue 版本匹配,用 nvm 切换 |
| MySQL | 5.7 或 8.0 | 注意 utf8mb4 编码 |
| FFmpeg | 4.x | 转码与封面截图 |
| Redis | 按需 | 仅当配置里有 redis 时才需要 |
提示:pom.xml 里如果是 Spring Boot 2.x,JDK 11 通常比 JDK 8 合适;Spring Boot 3.x 必须配 JDK 17,而且不少老开源项目没有迁移,编译报错先回来对版本。
一个判断技巧:用文本编辑器打开 pom.xml 看 spring-boot-starter-parent 的 version,打开 package.json 看 vue 和 vue-router 的版本。两者对不上时,优先降 Node 而不是升 Spring Boot,前端依赖安装失败和启动报错的大头都在 Node 版本上。
3.2 后端启动的三处关键配置
后端是整套系统的地基。在 application.yml 里找到三项配置,逐个改成本地环境的值:
spring:
datasource:
url: jdbc:mysql://localhost:3306/vod_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 你的密码
servlet:
multipart:
max-file-size: 2GB
max-request-size: 2GB
vod:
upload-root: /data/vod/upload
hls-root: /data/vod/hls
storage-domain: http://localhost:8080
multipart 的两个参数是视频上传的关键,Spring Boot 默认单文件 1MB,不调大会直接报 MaxUploadSizeExceededException。upload-root 和 hls-root 是自定义配置项,告诉后端原始视频和转码产物分别落在哪里,启动前手动创建目录并确认写权限。
导入数据库并启动:
mysql -uroot -p < vod.sql
cd backend
mvn clean package -Dmaven.test.skip=true
java -jar target/vod-server.jar --spring.profiles.active=dev
mvn 打包时间取决于依赖下载速度,第一次可能要好几分钟。启动日志出现 "Started Application" 后先别急着启动前端,用 curl 验证接口:curl http://localhost:8080/api/video/list 能返回 JSON,说明后端和数据源正常。这一步能省掉后面一半的联调时间。
3.3 前端启动与跨域转发配置
前端目录下执行 npm install,网络慢可以切淘宝镜像源。安装完成后,关键动作是确认开发环境的转发配置。vue.config.js 里最常见的配置:
module.exports = {
devServer: {
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
这段配置的含义是:浏览器里所有以 /api 开头的请求,在开发服务器这一层被转发到 8080 端口,浏览器感知不到跨域,所以后端不需要额外配 CORS。如果你改了后端端口,这里必须同步改,否则前端页面能打开但所有接口报 404 或 500。
启动前端:
cd frontend
npm install
npm run dev
打开 http://localhost:8081,能看到登录页说明前后端通道已打通。用 sql 脚本里预置的管理员账号登录,进去先别点视频,去"视频管理"里传一个几十 MB 的 mp4 验证完整上传链路,小文件能通,大文件的问题多半在 nginx 的请求体大小限制或 multipart 配置上。
4. 视频上传、转码与播放链路的实现
4.1 上传接口与存储策略
视频点播系统源码的核心价值在上传与转码这一段。常见的实现是:前端用 el-upload 或 FormData 提交 MultipartFile,后端按日期分目录存储原始文件,再交给转码服务。存储目录按日期分层的目的是避免单目录文件过多,Linux 下单目录文件超过数万个后访问性能明显下降。
后端上传接口的核心代码通常是这样:
@PostMapping("/api/video/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file,
@RequestParam("title") String title) {
String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
File dir = new File(uploadRoot, datePath);
if (!dir.exists() && !dir.mkdirs()) {
return Result.fail("上传目录创建失败");
}
String ext = getExt(file.getOriginalFilename());
String filename = UUID.randomUUID().toString().replace("-", "") + ext;
file.transferTo(new File(dir, filename));
videoService.createTask(title, dir.getAbsolutePath() + "/" + filename);
return Result.ok("上传成功,进入转码队列");
}
这段代码有三个值得留意的点:文件名用 UUID 重命名,避免中文和特殊字符引发 URL 编码问题;元信息入库与文件落盘分成两步,先写转码任务而不是直接写 vod_video,方便失败重试;ext 必须做白名单校验,只放行 .mp4/.mov/.avi,否则任意文件上传会变成攻击入口。
源码里与这套存储策略配套的目录结构一般是:
| 路径 | 内容 |
|---|---|
| {upload-root}/yyyy/MM/dd/{uuid}.mp4 | 原始视频文件 |
| {hls-root}/yyyy/MM/dd/{uuid}/output.m3u8 | 转码后的播放索引 |
| {hls-root}/yyyy/MM/dd/{uuid}/output_000.ts | ts 分片,按序号递增 |
| {hls-root}/yyyy/MM/dd/{uuid}/cover.jpg | 封面截图 |
4.2 用 FFmpeg 完成 HLS 转码与截图
转码是视频点播和普通文件上传的分水岭。开源视频点播系统中最普遍的做法是用 FFmpeg 把上传的 mp4 转成 HLS 协议需要的 m3u8 和 ts 分片。直接好处是播放器不用等整个文件下载完,拖拽进度和码率切换都依赖这种分片格式。
转码命令在源码的 script 或 service 层,核心参数一般是这套:
ffmpeg -i input.mp4 \
-c:v libx264 -profile:v main -crf 23 -preset veryfast \
-c:a aac -b:a 128k -ac 2 \
-vf "scale=-2:720" \
-hls_time 10 -hls_list_size 0 \
-hls_segment_filename "output_%03d.ts" \
-f hls output.m3u8
参数含义从左到右:libx264 是兼容性最好的 H.264 编码器,所有浏览器都能解;crf 23 是画质与体积的平衡点,数值越小越清晰也越大;preset veryfast 牺牲少量压缩率换取转码速度;-hls_time 10 表示每个 ts 分片 10 秒,太长首屏慢,太短请求频繁;-hls_list_size 0 表示 m3u8 保留所有分片,点播必须加这个参数。
转码完成后还要截一张封面图,常用命令是截取第 5 秒画面:
ffmpeg -i output.m3u8 -ss 5 -vframes 1 -q:v 2 cover.jpg
提示:转码是 CPU 密集型操作,普通服务器上一部 1GB 的电影要几分钟。生产环境建议引入消息队列削峰,否则多个上传同时到达时 FFmpeg 进程会互相拖垮。
4.3 前端播放器和 token 鉴权怎么配合
前后端分离的视频点播系统里,播放器选型高度统一:PC 网页端用 hls.js 播放 HLS,手机端依赖原生支持。很多"完美版"源码直接写死 video 标签的 src,开发环境能播,一上生产就黑屏——hls.js 加载分片时会带上页面的 Referer,后端一旦加防盗链就断流。
播放器接入的核心逻辑:
import Hls from 'hls.js'
function playVideo(videoEl, m3u8Url, token) {
const authedUrl = `${m3u8Url}?token=${encodeURIComponent(token)}`
if (Hls.isSupported()) {
const hls = new Hls({ maxBufferLength: 30 })
hls.loadSource(authedUrl)
hls.attachMedia(videoEl)
hls.on(Hls.Events.ERROR, (event, data) => {
if (!data.fatal) return
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
hls.startLoad()
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
hls.recoverMediaError()
}
})
} else if (videoEl.canPlayType('application/vnd.apple.mpegurl')) {
videoEl.src = authedUrl
}
}
token 拼在 m3u8 地址上而不是放在 header 里,原因是 hls.js 内部的请求可以带 header,但 iOS 原生的 video 标签发起的是纯 URL 请求。最常见的做法是"登录拿 token,播放时拼 URL",token 带过期时间,配合后端签名校验做防盗链。
axios 请求拦截器里再统一注入 token,负责登录态校验和播放记录上报:
axios.interceptors.request.use(config => {
const token = localStorage.getItem('vod_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, error => Promise.reject(error))
拦截器的边界要讲清楚:它管的是 /api 下的业务请求,管不了 video 标签加载的 ts 分片。分片鉴权要么走 URL token 过期,要么走 Referer 白名单,这是 vue 前后端分离请求 token 处理里最容易漏掉的一环。
4.4 播放地址过期与防盗链
生产环境不能把 m3u8 地址做成永久有效。常见做法是后端下发带过期时间的签名 URL,在地址上附加 expires 和 sign,nginx 校验签名与时间,失败直接返回 403。签名放在 URL 上是为了兼容 iOS 原生播放器。
这个策略成本很低,但大部分开源源码没有实现。拿到源码后先确认后端是否签发带签名的播放地址;没有的话,至少在 nginx 加一层 Referer 校验,把外部盗链挡在门外。
5. 把这套源码调到能用的收尾技巧
5.1 一套 5 分钟自测清单
源码调完不要急着部署,按这个顺序自测:后端启动后 curl 列表接口看是否返回 JSON;上传一个 10MB 的 mp4 看是否进入转码队列;转码完成后访问 m3u8 是否 200;前端列表页是否只显示 status=1 的视频;退出登录后直接访问 m3u8 地址是否被拦截。五步全过,这套视频点播系统才算真正可用,否则部署到服务器上只会更难排查。
5.2 zip 解压乱码与导入失败的处理
Windows 上解压 Linux 打包的 zip,中文文件名乱码是最高频的问题。源码包编码不对不只是难看,路径对不上会让前端资源引用直接 404。Linux 下解压时指定源编码:
unzip -O GBK vod.zip -d ./vod
-O 是 Info-ZIP 的编码选项,GBK 对应 Windows 默认中文编码;macOS 自带的 unzip 不支持,改用 ditto 解压或在 Windows 上用 Bandizip 按指定编码重新解压。zip 文件本身损坏时先执行 zip -T vod.zip 做完整性测试。解压后 pom.xml 或 package.json 导入 IDE 失败,先查文件编码是否为 UTF-8,"导入资源包失败"这类报错九成是编码和路径问题,不是代码问题。
5.3 播放黑屏时先看这三个位置
黑屏问题按出现频率排序:先看 m3u8 是否能直接访问,再看 ts 分片请求是否 403,最后看后端返回的 URL 是否能被公网访问。用一条命令把分片列表拉下来比对:
curl -s http://localhost:8080/vod/output.m3u8 | head -20
能看到 ts 文件名列表说明 m3u8 正常;接着取其中一条 ts 地址 curl -I 看状态码。403 是防盗链或 token 问题,404 是文件路径和存储域名对不上,200 但播放器仍黑屏就往编码格式和浏览器兼容性上查。这条链路输通,播放器端的各种报错都能顺着状态码定位到根因。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)