简介:视频点播系统完美版前后端分离开源包,面向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 但播放器仍黑屏就往编码格式和浏览器兼容性上查。这条链路输通,播放器端的各种报错都能顺着状态码定位到根因。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐