Spring Boot + Vue 视频点播系统:前后端分离架构与播放鉴权实战
简介:基于Java、Spring Boot与Vue.js开发的前后端分离视频点播系统,功能完整、界面美观,适合计算机相关专业学生用于毕业设计或课程设计,也可作为全栈开发入门进阶的练习项目。压缩包共包含278个文件,其中Java源码83个、Vue组件42个、TypeScript脚本42个、MP4演示视频11个,另有XML、YML等配置文件与样式资源,整体大小仅为10.25MB,结构紧凑、便于部署。平台功能涵盖前台首页、视频列表、视频详情、交互中心以及个人中心,后台功能包括总览、视频管理、分类管理、标签管理、评论管理、用户管理、运营管理、日志管理和系统信息等模块;server与web目录清晰分离,方便按需修改与功能扩展。目前已有126人学习下载,项目经测试可快速部署运行,适合课程作业提交、毕设演示或初期项目立项参考,同时也可作为学习Spring Boot与Vue.js全栈开发的实践素材。
1. 视频点播毕设的选型逻辑:为什么是 Spring Boot + Vue
视频点播 + Spring Boot + Vue 这套组合,可能是计算机类毕业设计里最常被点名的选题。但很多同学做着做着就卡住了:一是视频文件怎么存、怎么播,二是前后端怎么把数据串起来。这个项目把这两条链路都补齐了——后端 Spring Boot 提供 REST API,前端 Vue.js 负责页面渲染和数据交互,B/S 结构把前台展示和后台管理拆成两个端,正好对应课设里「用户看得见的部分」和「管理员管得着的部分」。从工程代码看,server 目录是完整的 Java 后端工程,web 目录是标准 Vue 前端工程,功能覆盖视频列表、详情播放、交互评论,以及后台的视频、分类、标签、评论、用户、日志等管理模块,作为毕业设计或课程设计,覆盖面和演示效果都够用。更关键的是,这套代码模块边界清晰,改着不费劲——想换 OSS 存储、给评论加审核流、把单机缓存换成 Redis,改动范围都能控制在一个模块内,这在答辩现场是实打实的加分项。
2. 后端骨架:分层架构、实体设计与访问拦截器
2.1 前后端分离的工程结构:server 与 web 的职责边界
项目代码分两个目录:server 是 Spring Boot 后端工程,web 是 Vue.js 前端工程。这一点很重要,说明项目没有用传统的 Thymeleaf 模板方案,而是前后端完全分离——后端只暴露 JSON 接口,前端通过 Ajax 请求消费数据。这套模式的好处是后端调试不用管页面,前端开发不用碰 Java,两端只要把接口约定写在文档里就能并行推进。
一个典型的 Spring Boot 分层结构在 server/src/main/java 下按包名划分如下:
com.example.vod
├── controller // 控制层,接收前端请求
├── service // 业务层,处理核心逻辑
│ └── impl // 业务实现类,如 ThingServiceImpl.java
├── mapper // 持久层,操作数据库
├── entity // 实体类
├── config // 配置类,如 WebConfig 拦截器注册
└── interceptor // 拦截器,如 AccessInterceptor.java
目录说明:controller 只负责参数接收和结果包装,不写业务代码;service 层用接口 + impl 的方式实现,便于扩展事务和隔离逻辑;mapper 层直接对应数据库表。这套约定在 Spring Boot 项目里非常主流,你接手后按包找代码基本不会迷路,答辩时老师问「你的代码组织方式是什么样的」,直接把这张结构图讲清楚就能过第一关。
2.2 数据模型设计:视频、分类、标签、评论的关联关系
从后台功能反推,数据库最少要有这几张表:用户表 t_user 、视频表 t_video 、分类表 t_category 、标签表 t_tag 、视频-标签关联表 t_video_tag 、评论表 t_comment 、日志表 t_log 。视频表和分类表是外键关联,一条视频属于一个分类;视频和标签是多对多关系,用中间表维护,避免一张表里存逗号分隔的标签 id——这在模糊查询时会非常痛苦。
视频表的字段设计通常长这样:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| title | varchar(100) | 视频标题 |
| cover_url | varchar(200) | 封面图地址 |
| video_url | varchar(200) | 播放地址 |
| category_id | bigint | 所属分类 |
| play_count | int | 播放次数 |
| status | tinyint | 上架状态 0 下架 1 上架 |
| create_time | datetime | 创建时间 |
create_time 默认值直接设 CURRENT_TIMESTAMP ,应用层不用手动塞时间。 status 用 tinyint 而不是 boolean,是为了后续扩展出「审核中」「草稿」等状态留余地;如果你在答辩时说这是「为状态机扩展做预留」,比「我用布尔值不够用所以改了」听起来专业得多。
2.3 AccessInterceptor 拦截器:登录态校验与白名单放行
AccessInterceptor 是后端鉴权的核心,它实现 HandlerInterceptor 接口,在请求到达 Controller 之前检查是否携带合法登录凭证。以常见的 Token 方案为例:
public class AccessInterceptor implements HandlerInterceptor {
private UserService userService;
public AccessInterceptor(UserService userService) {
this.userService = userService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行预检请求,跨域时浏览器会先发 OPTIONS
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
// 从请求头拿 token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\"}");
return false;
}
// 校验 token,也可以解析 JWT,这里用 Redis 校验存在性
User user = userService.getUserByToken(token);
if (user == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\"}");
return false;
}
// 把用户信息放进 request 上下文,后续 Controller 直接取
request.setAttribute("currentUser", user);
return true;
}
}
逻辑说明: Authorization 头是行业惯用的 token 携带位置,前端在 Axios 拦截器里统一添加; HttpStatus.UNAUTHORIZED 返回 401 状态码,前端拿到后跳转登录页。对 OPTIONS 请求的放行非常关键——前后端分离部署时跨域请求会先触发预检,不放行的话前端浏览器会直接报 CORS 错误,而接口根本没到 Controller 层。
拦截器注册要写一个配置类:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private UserService userService;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AccessInterceptor(userService))
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/register", "/api/video/list");
}
}
excludePathPatterns 是匿名访问白名单,包含登录、注册和视频列表。这里有一个容易踩的坑:后续新增了公开接口(比如「热门推荐」),如果忘记把它加进白名单,前端会莫名收到 401,而且因为 token 存在,排查起来很容易走弯路。我的习惯是写一个常量类统一管理放行路径,并在注释里注明每个路径的用途。
2.4 UserController 设计:登录注册与个人中心接口
UserController 负责用户模块的入口,常见接口有 /api/login 、 /api/register 、 /api/user/info 、 /api/user/update 。登录成功后后端返回 token 已经是行业惯例:
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
User user = userService.login(dto.getUsername(), dto.getPassword());
if (user == null) {
return Result.error("用户名或密码错误");
}
String token = userService.generateToken(user.getId());
return Result.success(token);
}
}
参数说明: LoginDTO 是前端传来的用户名和密码; userService.login() 内部先按用户名查出用户,再用 BCrypt 校验密码散列值,比对成功后才生成 token。密码不能明文存储,这是答辩时高频追问点,提前准备好「BCrypt 加盐哈希、每次登录生成新 token、token 有效期 24 小时」这套话术,老师基本不会再深挖。
到这一步,后端骨架已经立起来了。接下来的核心问题是:视频列表、详情页这些具体接口怎么设计,播放地址怎么管理,以及权限校验如何应用到视频链路中。
3. 视频点播核心链路:列表、详情与播放鉴权
3.1 视频列表接口:分页与模糊查询的封装
视频列表接口是前台首页和后台管理共用的,区别只在查询参数和返回字段。设计上用一个通用分页接口:
@GetMapping("/api/video/list")
public Result pageList(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId) {
PageInfo<Video> videoPage = videoService.queryPage(page, size, keyword, categoryId);
return Result.success(videoPage);
}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | Integer | 否 | 页码,从 1 开始,默认 1 |
| size | Integer | 否 | 每页条数,默认 10 |
| keyword | String | 否 | 标题模糊搜索 |
| categoryId | Long | 否 | 分类 ID 精确筛选 |
page 从 1 开始而不是 0,这是 MyBatis PageHelper 的约定; keyword 和 categoryId 都是可选项, required = false 保证不带参数也能查全量。 PageInfo 里封装了总条数、总页数等分页元数据,前端表格组件直接绑定这些字段就能渲染出分页器。
业务实现类放在 ThingServiceImpl 里,使用 MyBatis-Plus 的 LambdaQueryWrapper 做条件拼接:
@Service
public class ThingServiceImpl implements ThingService {
@Autowired
private VideoMapper videoMapper;
public PageInfo<Video> queryPage(int page, int size, String keyword, Long categoryId) {
LambdaQueryWrapper<Video> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(Video::getTitle, keyword);
}
if (categoryId != null) {
wrapper.eq(Video::getCategoryId, categoryId);
}
wrapper.orderByDesc(Video::getCreateTime);
return PageHelper.startPage(page, size).doSelectPageInfo(() -> videoMapper.selectList(wrapper));
}
}
这里有一个容易踩的大坑:PageHelper 的 startPage 必须紧跟着第一条查询语句执行,中间不能插入其他数据库操作,否则分页参数会串到别的查询上,导致返回数据张冠李戴。如果项目里同时依赖了 PageHelper 和 MyBatis-Plus,还要检查两者的拦截器优先级,常见的报错是 Cannot find table 'xxx' 或分页不生效——优先看插件版本是否匹配。
3.2 视频详情页的数据聚合:播放地址、标签与推荐
视频详情页需要的数据比列表多不少。一次接口调用,前端要拿到的信息包括:视频基本信息、播放地址、封面、所属分类、标签列表、点赞数、播放次数、相关推荐视频。通常会把聚合逻辑放在 service 层完成:
public VideoDetailVO getDetail(Long videoId, Long currentUserId) {
Video video = videoMapper.selectById(videoId);
if (video == null) {
throw new BizException("视频不存在");
}
// 播放次数 +1,这里的 play_count 在列表页还会再读一次
videoMapper.incrementPlayCount(videoId);
VideoDetailVO vo = new VideoDetailVO();
vo.setVideo(video);
// 查询标签列表
vo.setTags(tagMapper.selectByVideoId(videoId));
// 查询当前用户是否已点赞
if (currentUserId != null) {
vo.setLiked(likeMapper.exists(videoId, currentUserId));
}
// 同分类下的推荐视频,排除当前视频
vo.setRecommend(videoMapper.selectByCategory(video.getCategoryId(), videoId));
return vo;
}
聚合的关键点在于:一次请求尽量在后端把该查的数据都查完,避免前端为了渲染一个页面发起五六次请求。这也是答辩能讲清楚的设计点——「n+1 查询问题」。如果在 service 层用循环去查标签和评论,视频一多接口响应时间就会肉眼可见地变慢,提前用批量查询( selectBatchIds 或 IN 查询)能把性能问题消灭在设计阶段。
3.3 播放鉴权与防盗链:签名 URL 的边界用法
经典课设项目一般把视频文件放在本地服务器静态目录,播放地址直接是 /static/videos/xxx.mp4 。如果直接开放静态路径,任何人都能拼 URL 把整个视频库拖走。常见做法是生成带签名和过期时间的播放地址:
http://your-server/vod/20240511/001.mp4?sign=md5hex&expire=1715490000
签名逻辑可以很简单:把文件路径 + 过期时间 + 一个固定盐做 MD5,播放请求到达后端时先校验 expire 是否超时,再校验 sign 是否匹配。但如果你做的只是本地演示环境,这一层加不加性价比不高——先跑通播放链路,再考虑防盗链。
| 视频格式 | 播放 type 参数 | 前端方案 | 兼容性情况 |
|---|---|---|---|
| MP4 | video/mp4 | video.js 原生播放 | 全端兼容,体积大 |
| M3U8 | application/x-mpegURL | video.js + hls.js | iOS/Android 浏览器均支持,但首次加载稍慢 |
如果项目的视频是 m3u8(HLS 流),要注意播放器能解析索引文件并逐段拉取 ts 分片。前端选型会在第 4 章展开,但这里先明确一个原则:m3u8 方案适合长视频和移动端兼容,MP4 方案适合短平快的演示。课设场景如果没有特殊要求,MP4 最省事。
提示:如果演示环境网络不稳定,建议将视频源文件压到 720p 以内、单个文件控制在 200MB 内,否则现场加载卡顿会被误判成代码问题。
4. 前端工程落地:Vue 组件化与播放器集成
4.1 web 前端目录结构:views / components / router 的职责划分
web 目录是标准 Vue 工程,关键目录结构如下:
web/src
├── api // 接口请求封装层,按模块拆分
│ ├── video.js
│ ├── user.js
│ └── admin.js
├── router // 路由配置,含路由守卫
├── views // 页面级组件
│ ├── home // 前台首页
│ ├── list // 视频列表
│ ├── detail // 视频详情
│ ├── mine // 个人中心
│ └── admin // 后台管理页面
├── components // 公共组件,如 VideoPlayer.vue
└── utils // 工具函数,axios 实例等
views 下每个文件夹对应一个路由页面, components 里的组件被页面引用,这种「页面文件夹 + 公共组件」的组合在中小型 Vue 项目里最常见。页面级组件通过 import VideoPlayer from '@/components/VideoPlayer.vue' 引入播放器,列表页和详情页复用同一个播放器组件,只是传入不同的 src 属性,这样播放器的初始化、销毁逻辑都收敛在一个组件里。
4.2 Axios 封装与统一错误处理
前端请求后端接口必须走 axios。建议把 axios 实例单独抽出来,设置 baseURL 和请求拦截器,统一做 token 注入。
// utils/request.js
import axios from 'axios';
import { message } from 'ant-design-vue';
const request = axios.create({
baseURL: '/api',
timeout: 10000
});
// 请求拦截器:从本地存储取 token,注入请求头
request.interceptors.request.use(config => {
const token = localStorage.getItem('vod_token');
if (token) {
config.headers.Authorization = token;
}
return config;
});
// 响应拦截器:统一处理 401 和业务错误码
request.interceptors.response.use(
response => response.data,
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('vod_token');
window.location.href = '/login';
}
message.error('请求失败,请稍后重试');
return Promise.reject(error);
}
);
export default request;
这段代码的价值在于把「登录态管理」和「错误提示」收敛到一个文件里。新增接口时不需要重复处理 token 和 401,页面代码只要关心业务数据本身。需要注意的细节是: localStorage 存 token 有 XSS 风险,但课设项目里这是可接受的取舍;如果你希望更安全,可以改用 httponly Cookie,但要同时处理 CSRF 问题,成本和复杂度都会上升。
4.3 播放器适配:video.js + hls.js 选型
视频点播最核心的前端组件是播放器。常见组合有纯 video.js,或 video.js 加上 hls.js 插件。如果视频源是 MP4,video.js 原生就能播;但如果是 m3u8(HLS 流),需要在 video.js 里注册 hls.js 作为解码器:
<template>
<video ref="videoElement" class="video-js vjs-big-play-centered"></video>
</template>
<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import 'videojs-contrib-hls';
export default {
name: 'VideoPlayer',
props: {
src: { type: String, required: true },
type: { type: String, default: 'application/x-mpegURL' }
},
mounted() {
this.player = videojs(this.$refs.videoElement, {
controls: true,
autoplay: false,
preload: 'metadata',
sources: [{ src: this.src, type: this.type }]
});
this.player.on('error', () => {
console.error('视频播放出错,src=', this.src);
});
},
beforeDestroy() {
// Vue 3 场景对应 beforeUnmount 生命周期
if (this.player) {
this.player.dispose();
}
}
};
</script>
type 默认为 application/x-mpegURL 是给 m3u8 用的;如果你的视频是 mp4,把 type 改成 video/mp4 ,video.js 会走原生 video 元素播放,不依赖额外解码器。打包时注意 hls.js 的 worker 相关静态资源路径要配置正确,否则在某些浏览器下会报跨域 worker 错误,典型表现是视频第一帧出不来但控制台没有明显报错。
组合方案选型上,如果你只做 PC 端演示,MP4 + video.js 原生即可,依赖最少、坑最少;如果老师要求「移动端也要能看」,那就必须上 m3u8 + hls.js,因为 iOS Safari 对 MP4 的 range 请求支持不够稳定。
4.4 交互中心与个人中心:评论、收藏与播放历史的落地
交互中心承载评论和互动功能,核心是评论列表 + 发布入口 + 回复功能。评论区用滚动加载即可,不需要 websocket——这是点播系统,不是直播弹幕。个人中心的数据来源是 /api/user/info 、 /api/user/history 等接口,页面按区块组织:
| 功能区块 | 对应页面 | 核心接口 |
|---|---|---|
| 我的资料(头像、昵称、密码) | /mine/profile | PUT /api/user/profile |
| 我的收藏(视频列表 + 取消收藏) | /mine/favorite | GET /api/user/favorites |
| 播放历史(最近观看) | /mine/history | GET /api/user/history |
播放历史实现逻辑:前端在视频详情页 mounted 时调一次记录接口,后端按用户 id + 视频 id 做 upsert 保存观看时间,列表查询时按时间倒序。这个功能不需要额外引入中间件,一张历史表 + 两条 SQL 就能完成,但演示效果很好——老师上次看的视频,这次打开还在历史记录里,细节上的完整度很加分。
注意:交互中心的评论接口必须走登录态,前端要在请求时带上 token,后端用拦截器校验。如果评论接口没走拦截器,匿名用户随便刷评论,后台评论管理模块就直接失去意义。
5. 后台管理模块实战:总览统计与运营闭环
5.1 OverViewController 聚合查询:今日新增、播放量与趋势图
后台「总览」页对应 OverViewController,作用是把平台核心指标放到一个面板上。常见指标包括:今日新增用户、今日播放次数、视频总量、评论总量、近 7 天播放趋势。这些指标来自多条统计 SQL:
-- 今日新增用户数
SELECT COUNT(*) FROM t_user WHERE DATE(create_time) = CURDATE();
-- 视频总量(只统计上架中的)
SELECT COUNT(*) FROM t_video WHERE status = 1;
-- 近 7 天每日播放量
SELECT DATE(create_time) AS day, SUM(play_count) AS total
FROM t_video
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DAY(create_time);
第 3 条 SQL 的 DATE_SUB(CURDATE(), INTERVAL 7 DAY) 是 MySQL 里常用的相对时间计算写法,注意它拿到的是当天零点,如果要精确到小时级,改成 NOW() - INTERVAL 7 DAY 。用 MyBatis-Plus 的 QueryWrapper 写这种原生统计并不直观,推荐直接用 @Select 注解或 XML 文件写原生 SQL。
统计接口的返回值建议统一封装成 VO:
{
"userCountToday": 12,
"videoCount": 234,
"commentCount": 892,
"playCountToday": 1203,
"weekTrend": [
{ "date": "05-10", "plays": 110 },
{ "date": "05-11", "plays": 156 }
]
}
这个 JSON 结构直接对应前端 ECharts 图表的 data 字段。总览页通常用 4 个数字卡片 + 1 个折线图展示,前端代码量不大,但视觉效果很抓人,答辩演示时第一屏就能留下「工程完整」的印象。
5.2 视频、分类与标签的管理闭环
后台视频管理页面的核心操作是「列表查询」+「新建/编辑」+「上架/下架」+「删除」。每一步对应一个后台接口:
| 操作 | 接口 | 说明 |
|---|---|---|
| 查询 | GET /api/admin/video/page | 支持 keyword、categoryId、status 多条件筛选 |
| 新建 | POST /api/admin/video | 表单里上传封面和视频文件 |
| 编辑 | PUT /api/admin/video/{id} | 修改标题、分类、标签 |
| 删除 | DELETE /api/admin/video/{id} | 物理删除,连带清掉标签关联表 |
| 上架/下架 | PUT /api/admin/video/{id}/status | 只改 status 字段,不影响其他数据 |
分类管理如果做成单级分类,一张表 + 增删改查接口就够。做成树形分类(如「资讯—科技」)则需要后端递归组装树,这里要注意 Jackson 序列化父子引用时容易触发无限递归,解决方法是加 @JsonIgnore 或改用 DTO 返回而不是直接返回实体。
标签和分类的区别要讲清:分类是树形层级、每个视频只有一个分类;标签是扁平结构、每个视频有多个标签。数据库用中间表 t_video_tag 维护关系,删除视频时要先删除中间表数据,否则会残留孤儿数据,时间长了关联查询会出现预期之外的结果。
5.3 评论审核与用户状态管理:封禁后的 token 怎么办
评论管理后台的常规操作是:按视频标题或用户昵称筛选评论、删除违规评论、封禁用户。用户状态管理在用户表加一个 status 字段即可:0 正常、1 封禁。登录接口里加一行判断:
if (user.getStatus() == 1) {
return Result.error("该账号已被封禁,请联系管理员");
}
逻辑简单,但答辩时大概率会被问:「封禁用户后,他手里已拿到的 token 还能用吗?」这个问题值得提前想清楚:如果项目用的是无状态 JWT,token 本身不失效,封禁只影响下一次登录;要让封禁立即生效,常见的三种做法是:
- 在 token 里带用户状态版本号,状态变更后续签;
- 每次请求都查一次用户状态(最简单,多一次查询);
- 把 token 存 Redis,封禁时删掉对应 key。
三种方案成本递增。课设场景里,在拦截器中补充一次用户状态查询是最稳妥的取舍,代码只多两三行,但能挡住「封禁用户仍可操作」的演示翻车场景。
后台功能的接口链路到这里就齐了,下一步把代码真正跑起来,看看部署环节有哪些容易栽跟头的地方。
6. 部署与验证:跨域、Nginx 与播放异常速查
6.1 本地启动三步走
本地跑通项目顺序建议先数据库、再后端、最后前端。第一步,在 MySQL 中创建数据库并导入初始化脚本:
mysql -uroot -p < server/src/main/resources/db/init.sql
第二步,修改后端的 application.yml ,确认数据源账号密码、端口号、文件保存路径,然后直接启动主类,看到 Tomcat started 说明后端就绪。第三步,在 web 目录下执行:
npm install
npm run dev
npm run dev 默认起在 8080 端口,如果和后端端口冲突,在 vue.config.js 里配置 devServer.port ,同时配置代理把 /api 转发到后端地址,本地联调就不需要处理跨域。
6.2 跨域问题与 Nginx 配置
前后端分离部署时跨域问题首当其冲。本地联调的推荐方案是代理转发;生产环境部署建议用 Nginx 做统一入口,既解决跨域,又能托管前端打包产物:
server {
listen 80;
server_name your-domain.com;
location / {
root /opt/vod/web/dist;
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;
}
}
try_files 是 Vue Router history 模式刷新 404 的标准解法; proxy_pass 末尾没有斜杠,表示把 /api 前缀也透传给后端,这样后端不用改接口路径。前端打包产物放在 /opt/vod/web/dist ,改完 Nginx 配置后 nginx -s reload 即可生效。
6.3 常见失败场景速查
| 现象 | 第一步排查 | 第二步排查 |
|---|---|---|
| 前端页面空白 | 浏览器 console 有无 JS 报错 | network 请求是否全部返回 200 |
| 登录提示 401 | 后端拦截器 excludePathPatterns 是否包含登录接口 | 前端 Authorization 头是否传对 |
| 视频无法播放 | video 元素请求的 HTTP 状态码 | 404 查文件路径,403 查签名过期 |
| 分页数据错乱 | PageHelper.startPage 后是否紧跟查询 | 插件版本是否与 MyBatis-Plus 冲突 |
答辩前把三条链路完整走一遍:用管理员账号进后台改一条视频状态、用普通用户账号评论一条视频并后台删除、停掉后端服务再启动验证视频恢复播放——这三个操作覆盖了项目最核心的鉴权、数据流和播放链路。每一条都能对应到具体代码位置,现场演示时 即使紧张也不会出现「功能点了没反应」的尴尬。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)