SSM+Vue+MySQL在线视频点播系统:部署、播放与答辩指南
简介:一份基于Java SSM框架、Vue与MySQL构建的在线视频点播系统毕业设计项目,面向计算机专业学生,可用于毕业设计、课程设计或期末大作业。项目已通过导师审核,前后端源码、数据库脚本、论文文档均包含在内,下载后即可运行,无需修改。技术栈覆盖Spring/SpringMVC/MyBatis与Vue,开发环境基于IDEA,数据库采用MySQL8.0,辅以Maven构建和Tomcat部署配置,整体结构清晰。压缩包总大小48.04MB,共含1414个文件,其中js、css、jsp、java等前后端源码占比最高,png、jpg、gif等图片与swf、mp4素材支撑界面与演示效果,另附sql脚本和doc论文,方便研究者按模块查阅。系统功能完善、界面美观、管理便捷,具备实际应用价值;目前已有44人学习/下载,适合需要一份完整可运行参考项目的学生开发者快速搭建演示系统;项目经过严格调试,确保可以运行,可放心下载使用。
1. 拿到「在线视频点播系统」压缩包之后,真正该做的是这三件事
拿到一个写着头衔「基于java+ssm+vue+mysql的在线视频点播系统源码+数据库+论文(高分毕设项目).zip」的资源包,多数人第一反应是解压、导入、跑起来。但真正让这个项目从「能演示」变成「能答辩」的,是你清楚每一层在干什么:后端 SSM 管接口和数据,前端 Vue 管页面和播放器,MySQL 存用户、视频和播放记录,论文负责把设计理由讲圆。这套组合做在线视频点播,是 Java 方向毕设里最稳的一条路,适合时间紧、需要完整交付物、又想在答辩时讲出细节的人。接下来按落地顺序把部署、实现和常见坑讲清楚。
2. SSM + Vue + MySQL 这套组合为什么是毕设常青树:分层职责与五张核心表
先看这套技术栈凭什么能撑起一个点播系统。SSM 是 Spring、SpringMVC、MyBatis 的缩写,Spring 管对象和事务,SpringMVC 管请求分发,MyBatis 管 SQL 映射。Vue 负责页面交互和播放器,MySQL 做持久化。这个组合最大的优势不是性能,而是每一层都能被拆开讲清楚,答辩老师顺着问下去你都有话接。
2.1 技术选型:SSM 加 Vue 为什么能扛住毕业答辩
SSM 里面最容易被问的是 Spring 容器和 SpringMVC 的请求流程。一次点播请求从前端发出,先被 DispatcherServlet 拦截,再通过 HandlerMapping 找到对应的 Controller,Controller 调 Service,Service 调 Mapper,Mapper 执行 SQL 返回数据,结果再一层层传回前端的 Vue 组件。这个链路就是整个项目的骨架,代码里的包结构也基本照它排:controller、service、mapper、entity 四层。
前端部分,绝大多数毕设包用的是 Vue2 配上 Element UI、Vue Router、Axios。Vue2 生态成熟,教程多,遇到问题几乎都能搜到现成答案;Vue3 也可以选,但有些组件库和旧教程对不上,短时间交付风险大。我一般做毕设导向的项目时,优先 Vue2,不是因为 Vue3 不好,而是因为你的核心目标是快速跑通并讲清楚业务,不是追新。
MySQL 选 5.7 还是 8.0 也会影响落地速度。5.7 的资料多、驱动配置简单;8.0 默认的认证插件是 caching_sha2_password,连接时要用新驱动并且配好时区参数,否则启动就报错。不管装的是哪个版本,安装时把字符集选成 utf8mb4、端口保持默认 3306,后面能少很多事。顺带说一句,这套技术栈和 SpringBoot + MyBatis 的项目在业务代码上差别不大,但既然源码锁定了 SSM,就把分层的配置逻辑吃透,答辩时被问「SpringBoot 和 SSM 有什么区别」反而成了送分题。
2.2 数据库设计:用户、视频、分类、评论、播放记录五张表先立住
点播系统的核心不是把视频文件存进数据库,而是存元数据。视频文件本身放在服务器磁盘,数据库里只保存路径、时长、封面、播放次数这些字段。把视频存成 BLOB 是新手最常见的错误,会让数据库体积膨胀、查询变慢、备份困难,答辩时这个点一定被追问。
下面这套建表语句是点播系统最精简的版本,直接对着执行即可:
CREATE DATABASE IF NOT EXISTS vod_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE vod_system;
CREATE TABLE category (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
sort INT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
nickname VARCHAR(50),
avatar VARCHAR(255),
role TINYINT DEFAULT 0 COMMENT '0-普通用户 1-管理员',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE video (
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT,
title VARCHAR(100) NOT NULL,
cover VARCHAR(255),
url VARCHAR(255) NOT NULL,
duration INT DEFAULT 0 COMMENT '视频时长,单位秒',
play_count INT DEFAULT 0,
status TINYINT DEFAULT 1 COMMENT '1-上架 0-下架',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (category_id),
INDEX idx_status (status)
) ENGINE=InnoDB;
CREATE TABLE comment (
id INT PRIMARY KEY AUTO_INCREMENT,
video_id INT NOT NULL,
user_id INT NOT NULL,
content VARCHAR(500) NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_video (video_id)
) ENGINE=InnoDB;
CREATE TABLE play_record (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
video_id INT NOT NULL,
last_play_time INT DEFAULT 0 COMMENT '上次播放到第几秒',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_video (user_id, video_id)
) ENGINE=InnoDB;
这里有几个参数值得单独说。 video 表的 duration 用 INT 存秒数,而不是用 VARCHAR 存「00:12:33」,因为秒数可以直接做排序、统计和续播计算。 play_record 表对 (user_id, video_id) 建了唯一键,保证一个用户对一个视频只有一条播放记录,续播时走更新而不是新增。 comment 表故意不建外键,原因是评论写入频繁,外键校验有开销,业务上在 Service 层保证视频存在就够了。这个设计取舍在答辩里是加分项。
索引只加了查询会用到的 category_id 和 status ,没给每个字段都加索引。数据量在毕设演示范围里,这些索引足够。表结构设计时想清楚,后面业务跑起来再改字段就没有后悔药了,比如用户表一开始没加 role 字段,后面做管理员功能就得 ALTER TABLE,连带改一堆代码。
2.3 Mapper 层的三层约定:XML 位置、驼峰映射与分页
MyBatis 的 SQL 写在哪里,直接影响项目能不能跑起来。常见约定是 Mapper 接口和 XML 文件同名,XML 放在 resources/mapper 目录下。比如 VideoMapper.java 对应 VideoMapper.xml 。接口里定义方法,XML 里写 SQL,两者通过 namespace 和语句 id 绑定。
SSM 项目里最容易被忽略的是驼峰映射开关。数据库列名是 create_time ,实体属性是 createTime ,不配置的话查询结果里 createTime 永远是 null。在 mybatis-config.xml 里加下面这段:
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
mapUnderscoreToCamelCase 开了之后,MyBatis 自动把下划线列名转成驼峰属性名。 logImpl 配成 STDOUT_LOGGING 会把 SQL 打印到控制台,排查问题时特别有用,答辩演示时也能展示 SQL 日志。
分页有两种做法:用 PageHelper 插件,或者手写 limit。PageHelper 要注意版本和 MyBatis 版本匹配,否则拦截器不生效,翻页字段会被忽略;手写 limit 更可控,代码不依赖插件,逻辑也清楚:
List<Video> search(@Param("keyword") String keyword,
@Param("offset") int offset,
@Param("limit") int limit);
对应的 XML 里写 LIMIT #{offset}, #{limit} , offset 和 limit 的计算放在 Service 层: offset = (pageNum - 1) * pageSize , limit = pageSize 。前端传 pageNum 和 pageSize ,后端返回给前端时再把总数查出来,用于计算总页数。这套分页逻辑几乎没有黑匣子,既不会因为插件版本问题报错,也能在答辩时讲清楚翻页原理。
3. 把源码包变成本地能跑的站点:从 IDEA 导入到数据库初始化的完整步骤
理论部分到这里,开始动手落地。整个启动流程可以拆成四段:先看目录结构判断项目形态,再启动后端,再跑前端,最后初始化数据库。这个顺序不要乱,数据库可以提前导入,但后端配置不改对,导入了也连不上。
3.1 解压之后先看目录:30 秒判断前后端分离还是混合部署
打开压缩包第一件事不是双击 pom.xml,而是看顶层目录。常见有两种结构:一种是后端工程加一个独立前端目录,比如 vod-server 和 vod-web 并列;另一种是前端打包后的 dist 文件直接放在后端的 webapp 目录下。判断标准很简单:有没有 package.json 和 vue.config.js ,有这两个文件就说明前端是独立工程,需要单独安装依赖、单独跑。
然后看后端结构。SSM 工程必须是 Maven 结构,根目录有 pom.xml 。再看 src/main/resources 下面有没有 jdbc.properties 、 mybatis-config.xml 、 mapper 目录,有这三样说明配置是完整的。这一眼看完,就清楚自己要改哪些文件了。这种资源包最常见的用途是二次开发和交付:你先把项目跑通了,才能把前端打包发出去给别人看,否则发过去也是一堆跑不起来的代码。
如果前端只有 webapp 下编译后的静态文件,没有源码工程,那么要么直接部署这个 war 包,要么放弃原有静态文件、自己重新拉一个 Vue 工程来对接后端接口。混合部署的方式在联调的时候最别扭,因为代理和目标地址都挤在一起,改一处错三处。
3.2 后端启动三板斧:导入 Maven、改数据库连接、启动 Tomcat
先在 IDEA 里用 Import Project 选择后端根目录的 pom.xml ,导入方式选 Maven。依赖下载阶段容易踩坑的是 IDEA 内置 Maven 的 settings.xml 指向了不存在的本地仓库,结果一直在转圈报错。IDEA 的 Maven 设置里把 Home Directory 指向你自己本机的 Maven 安装目录,这样依赖路径和命令行一致,后面用起来才顺。JDK 选 1.8,环境变量配好 JAVA_HOME ,这是 java 环境配置的基本盘。
改数据库连接是后端启动前最重要的一步。SSM 通常在 jdbc.properties 里配置:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/vod_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
jdbc.username=root
jdbc.password=你的密码
这里面的参数每一个都有讲究。MySQL 8 必须用 com.mysql.cj.jdbc.Driver ,MySQL 5.7 用的是 com.mysql.jdbc.Driver ,写错会报 ClassNotFoundException 。 serverTimezone=Asia/Shanghai 不加的话,连接会报时区相关的异常,而且项目里所有时间字段会和本地时间差 8 小时。 useSSL=false 是为了避免 MySQL 8 在非 SSL 连接时刷警告日志。密码字段直接明文写在 properties 里是毕设项目的常态,别觉得简陋,答辩时能说一句「生产环境会用加密配置」就够了。
启动方式有两种:在 IDEA 里配置 Tomcat Server Local,把 war exploded 部署进去;或者用 Maven 插件在命令行跑 mvn tomcat:run 。我个人更推荐 IDEA 配置 Tomcat 的方式,因为能看到完整堆栈日志。Application context 填 / ,监听端口默认 8080。启动后看到 Starting ProtocolHandler 且没有红色异常,再用浏览器访问 http://localhost:8080/ 验证一次。
3.3 前端环境:Node 版本、npm install 和 vue.config.js 代理
前端工程如果是 Vue2,Node 版本建议用 14 或 16。Node 18 以上偶尔会在 npm run build 阶段报内存溢出或者依赖编译错误,这种玄学问题换 Node 版本就好。IDEA 可以直接打开前端目录,但我更习惯在命令行窗口操作,因为报错信息更直接。
安装依赖前先确认镜像源,免得卡在网络环境上:
node -v
npm -v
npm install --registry=https://registry.npmmirror.com
npm run dev
--registry 参数指定了这次安装使用的镜像源,不会永久改动全局配置,项目里别的同事不受到影响。如果之前装过一半失败,把 node_modules 目录和 package-lock.json 删掉再重装,比反复重试更干净。
Vue 的 dev server 默认也是 8080,和后端 Tomcat 撞端口,必须在 vue.config.js 里改掉并配上代理:
module.exports = {
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
这段配置的逻辑是:前端页面跑在 http://localhost:3000 ,所有以 /api 开头的请求交给 dev server 代理,转发到后端的 http://localhost:8080 。 pathRewrite 把 /api 前缀剥掉,是因为后端的 Controller 里写的 @RequestMapping("/video/list") 并没有 api 这个前缀。于是前端请求写成 axios.get('/api/video/list') ,到后端就变成了 /video/list 。这个映射关系是整个前后端联调最核心的一步,配错了直接表现就是前端页面能打开、接口全是 404。
3.4 初始化数据库:命令行和可视化工具两种导入姿势
先把 .sql 文件用文本编辑器打开,看开头有没有 CREATE DATABASE 。有就可以直接整体导入,没有就先手动建库再导表。命令行方式最直接:
mysql -u root -p
source /path/to/vod_system.sql;
USE vod_system;
SHOW TABLES;
SELECT COUNT(*) FROM video;
source 后面写的路径如果是 Windows 系统,反斜杠要改成斜杠,否则 MySQL 客户端会报路径错误。导入完成后 SHOW TABLES 应该能看到前文那五张核心表, video 表里如果预置了数据, COUNT(*) 会返回一个大于 0 的数字,这个数在答辩演示时可以直接对应上页面里的视频条数。
用 Navicat 导入就更省事,右键目标数据库选择「运行 SQL 文件」,编码选 utf8mb4。导入后随便打开一张表看中文是否正常,如果有乱码,九成是文件编码选错了或者数据库字符集不是 utf8mb4。到这里,数据库、后端、前端三端就绪,整个项目已经能注册用户、能看列表,下一步就是把点播链路接起来。
4. 在线点播的核心链路:视频上传、HLS 切片与 Vue 播放器接入
系统跑起来之后,最核心的业务链路是视频点播本身。这条链路涉及三个环节:文件怎么传上去、视频怎么转成适合点播的格式、前端怎么播放。任何一个环节脱节,用户看到的就是黑屏。
4.1 上传接口:MultipartFile、文件路径与 UUID 重命名
视频文件不能存进数据库,要存服务器磁盘,数据库只保存访问路径。上传接口用 SpringMVC 的 MultipartFile 接收,先校验扩展名,再按日期建目录,文件名用 UUID 重命名,避免中文文件名乱码和同名文件覆盖:
@PostMapping("/video/upload")
public Result upload(@RequestParam("file") MultipartFile file,
@RequestParam("categoryId") Integer categoryId) {
String original = file.getOriginalFilename();
String ext = original.substring(original.lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replace("-", "") + ext;
String datePath = new SimpleDateFormat("yyyy/MM").format(new Date());
File dest = new File(uploadDir + File.separator + datePath, fileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
String url = "/upload/" + datePath + "/" + fileName;
Video video = new Video();
video.setTitle(request.getParameter("title"));
video.setUrl(url);
video.setCategoryId(categoryId);
videoService.create(video);
return Result.success(video.getId());
}
这段代码里的 uploadDir 建议放在配置文件里,本地开发配绝对路径比如 D:/vod-upload ,线上用 /data/vod-upload 。 datePath 按年月分目录,避免所有文件堆在一个目录里,目录层级一多也不会拖慢磁盘检索。 file.transferTo(dest) 要求目标父目录必须存在, mkdirs() 就是为这个做的。还有个容易被忽略的点: url 存的是相对路径 /upload/2025/06/xxx.mp4 ,前端拿到后拼上服务器地址去访问,如果存成绝对路径,换服务器就全断了。
上传接口还应该做扩展名白名单, .mp4 、 .m3u8 可以, .exe 必须拒绝。SpringMVC 配置文件里还要给上传目录加静态资源映射,否则文件传上去了,浏览器访问 /upload/... 直接 404。这块配置漏掉的话,现象就是列表里有视频、封面也正常,点开就黑屏。
4.2 播放方案选型:mp4 直出与 HLS 切片
最简单的播放方式是把 mp4 路径直接交给前端 <video> 标签,写完马上能看。但大文件 mp4 有两个问题:起播慢,用户要等缓冲;拖动进度条时如果服务端不支持 Range 请求,整个视频会重新加载。在线点播系统更常见的做法是转成 HLS 格式,也就是 m3u8 索引文件加 ts 切片。起播时浏览器只加载第一个切片,拖动进度条也只需要按需加载对应切片,体验好很多。
转切片用 ffmpeg 一条命令就能完成:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac \
-hls_time 10 -hls_list_size 0 -hls_segment_filename "output_%03d.ts" \
output.m3u8
-hls_time 10 表示每 10 秒切成一个 ts 文件,数字越小起播越快,但切片数量变多,服务器上有更多小文件; -hls_list_size 0 表示 m3u8 文件里保留全部切片列表,而不是只保留最近几段,否则用户拖动到后半部分会发现索引里找不到对应切片; -c:v libx264 是 H.264 编码,浏览器兼容性最好。生成后把 m3u8 和 ts 文件都放到上传目录或者单独的切片目录,数据库 video 表的 url 字段存 m3u8 的相对路径即可。
如果答辩要展示视频处理能力,可以再说一句:上传后异步调 ffmpeg 转切片,转完成后再把视频状态改成「已上架」,用户需要等待的时间更短。这个异步设计属于加分细节,但要注意 ffmpeg 命令是耗时的,不能直接写在请求线程里。
4.3 前端接入:hls.js 免插件播放 m3u8
Chrome 和 Firefox 不原生支持 HLS,最常见的方案是用 hls.js。它在浏览器支持 MSE 的前提下,把 m3u8 拉下来再喂给 <video> 元素,用户不需要安装任何播放器插件。热词里常说的「vue 播放 m3u8 免安装」,本质就是这一套。
在 Vue 组件里封装播放逻辑:
import Hls from 'hls.js';
export default {
data() {
return { hls: null };
},
methods: {
playVideo(videoUrl) {
const video = this.$refs.player;
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoUrl; // Safari 原生支持
} else if (Hls.isSupported()) {
if (this.hls) this.hls.destroy();
this.hls = new Hls();
this.hls.loadSource(videoUrl);
this.hls.attachMedia(video);
this.hls.on(Hls.Events.ERROR, (e, data) => {
console.error('hls error:', data);
});
}
}
},
beforeDestroy() {
if (this.hls) this.hls.destroy();
}
};
canPlayType('application/vnd.apple.mpegurl') 这个分支是给 Safari 和 iPhone 用的,它们原生支持 HLS,不需要 hls.js。 Hls.isSupported() 判断当前浏览器是否支持 MSE,Chrome、Edge 都走这条路。 beforeDestroy 里 destroy() 是必须的,不然切换路由后 hls.js 还在后台拉视频流,白费流量也拖慢页面,这个细节很多人不做。
播放器 UI 可以用 video.js 或者原生 <video> 加自定义控制条,核心播放逻辑就是上面这段。真机测试时注意一个坑:m3u8 文件里写的 ts 路径如果是相对路径,浏览器会以 m3u8 的 URL 为基准去拼接,所以页面域名、接口域名、视频文件域名必须保持同源,否则会报跨域错误。这就是「后端明明写好了但播放黑屏」最常见的黑匣子之一。
5. 避坑指南:SSM 点播系统从启动到播放的 5 个常见翻车现场
这一章写点血泪经验。下面的问题我几乎每个都亲手踩过,按「现象 → 原因 → 解决」的顺序列出,排查时先对号入座,能省下大把抓头发的时间。如果你是从第 3 章一路照着做过来的,大部分问题应该已经绕开了;如果还没跑通,这章就是你的排查清单。
5.1 数据库连接失败:Access denied 和 Communications link failure 是两种病
现象:后端启动时控制台报 Access denied for user 'root'@'localhost' ,或者报 Communications link failure 。
原因:前者是密码错误或用户名不对,后者通常意味着 MySQL 服务没启动、端口被改过不是 3306、或者 URL 里的主机地址写错。
解决:先打开 Windows 服务管理器看 MySQL 服务状态,再在命令行里用 mysql -u root -p 手动试一次连接,最后才去检查 jdbc.properties 。有个容易忽略的细节:MySQL 8 默认认证插件是 caching_sha2_password,如果项目里用的还是 5.x 的老驱动,会报连接失败。确认 pom.xml 里 MySQL 驱动的版本和数据库版本对应上。
5.2 8080 端口被占用:后端起不来,前端代理却指向 8080
现象:Tomcat 启动日志报 Port 8080 required by Tomcat v9.0 is already in use ;前端 npm run dev 也可能撞上同一个端口。
原因:Windows 上非常常见,别的程序占着 8080,或者上一次启动的 Tomcat 进程没有真正退出。
解决:用 netstat -ano | findstr 8080 查到占用端口的 PID,到任务管理器结束对应进程。如果不想动其他程序,就改 Tomcat 的 HTTP 端口,比如改成 8081,同时一定要把前端 vue.config.js 里代理的 target 也改成 http://localhost:8081 。改端口时前后端两处要同步,前端 devServer.port 只影响页面访问地址, proxy.target 才影响接口转发地址,两处别搞混。
5.3 前端请求全 404:代理配了但 axios baseURL 没走对
现象:页面能打开,但登录请求一提交就 404,打开浏览器开发者工具,发现请求地址是 http://localhost:3000/login 而不是带 /api 前缀的地址。
原因:axios 的 baseURL 配成了 / ,而 vue.config.js 里代理只对以 /api 开头的请求生效。前端的请求地址不对,代理根本没接住。
解决:把 axios 实例的 baseURL 统一设成 /api ,这样所有请求自动带上前缀,代理按照 /api 规则转发,再用 pathRewrite 剥掉前缀。或者反过来,后端所有接口统一加上 /api 前缀,代理里不做 pathRewrite 。两种方案都能通,认准一种用到底,别混用,混用的后果就是一部分接口通一部分不通,排查起来非常烧脑。前端的登录请求、视频列表请求、评论请求都走同一个 axios 实例,这个习惯从一开始就养好。
5.4 视频播放黑屏或 404:静态资源映射和路径拼接不一致
现象:文件上传成功,数据库里 url 字段有值,前端 <video> 的 src 也赋值了,但浏览器请求 /upload/xxx.mp4 返回 404,或者干脆黑屏不加载。
原因:最常见的是后端没有给 /upload/** 加静态资源映射,文件在磁盘上但 Tomcat 不知道去哪找;其次是前端在拼接视频地址时把服务器地址重复拼了一段,比如数据库存的是 /upload/... ,前端又拼了一遍 http://localhost:8080/upload/... 导致路径变成 http://localhost:8080/upload/upload/... 。
解决:后端在 SpringMVC 配置或 WebMvcConfigurer 里加:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + uploadDir + "/");
}
addResourceHandler 是外部访问的 URL 模式, addResourceLocations 是磁盘上的实际路径,注意 file: 前缀不能丢,结尾的斜杠也不能丢。排查时最直接的办法是在浏览器里单独打开视频文件的完整 URL,如果能播放就是前端路径拼接问题,打不开就是静态资源映射问题,一次就能定位。
5.5 Mapper 报 Invalid bound statement (not found)
现象:项目能启动,但一调用某个 Service 方法立刻报 BindingException: Invalid bound statement (not found): com.xxx.mapper.VideoMapper.countList 。
原因:三种常见情况: mapper.xml 文件没被 MyBatis 扫描到;XML 里 namespace 写错,和接口全限定名对不上;XML 文件没被编译进 target/classes 目录。
解决:先看 resources/mapper 目录下有没有对应的 XML 文件,然后检查 applicationContext.xml 里 MyBatis 的配置是不是指向 classpath*:mapper/*.xml 。 namespace 必须和接口的包名加类名完全一致,比如接口是 com.vod.mapper.VideoMapper ,XML 里就必须写成 namespace="com.vod.mapper.VideoMapper" 。在 IDEA 里如果改完 XML 还报错,执行一次 clean 再重新编译,确保 target/classes 里拷贝了最新的 XML。这个问题和人肉记忆没关系,照着配置逐项核对就能解决。
6. 让毕设从「能跑」到「能答辩」:三个验证手段与一个加分项
系统能跑了,下一步是为答辩做准备。高分不是靠功能多,是靠你能把功能讲明白、把验证步骤摆出来。
6.1 答辩前的验证三板斧
第一,用 Postman 把核心接口按顺序过一遍:注册、登录、上传视频、获取视频列表、发表评论、拉取播放记录。每个请求记下状态码和耗时,能在答辩时说出来「这个接口做了哪几步、参数从哪来、异常返回什么格式」。论文里的技术架构图和数据库 ER 图,也一定要和实际跑的系统保持一致,答辩老师拿着论文对页面问,对不上就是硬伤。
第二,验证播放的边界。把 ffmpeg 的 -hls_time 从 10 调到 5,对比起播速度和切片数量的变化;再用手机浏览器访问一遍,确认 hls.js 在移动端也正常。答辩证最怕的就是演示到视频播放那一步黑屏,提前用真机把整条链路走一遍,比答辩前一晚再修要好得多。
第三,准备一条演示脚本:按用户操作顺序把关键页面截图存档,数据库里预置几条分类明确、封面漂亮的视频数据。答辩现场导师不一定按顺序问,脚本能帮你快速定位到对应的代码位置,讲起来不慌。
6.2 一个加分项:播放记录续播,让功能从「有」到「完整」
播放记录表和 last_play_time 字段就是为了这个。打开视频页时请求一次记录,把上次的位置赋给 video.currentTime ,播放中更新记录,下次进入还能续播。实现成本很低,但在答辩里属于「别人没有而你有」的细节:
this.video.currentTime = record.lastPlayTime || 0;
this.video.addEventListener('timeupdate', () => {
if (this.video.currentTime % 5 < 1) {
this.savePlayRecord(this.video.currentTime);
}
});
注意 timeupdate 事件触发频率很高,真的每秒写一次数据库会产生大量无效请求。这里按 5 秒节流,或者只在页面关闭前上报一次,都是可接受的方案。我做这类项目最深的教训是:不要等全部功能做完再整理验证,先把「注册 → 登录 → 搜索 → 播放 → 评论 → 续播」这条用户主路径跑通,再填充周边功能。数据库、接口、前端每一环都要能讲出「为什么这么设计」,答辩就稳了。希望帮到你。
更多推荐
所有评论(0)