简介:基于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 冲突

答辩前把三条链路完整走一遍:用管理员账号进后台改一条视频状态、用普通用户账号评论一条视频并后台删除、停掉后端服务再启动验证视频恢复播放——这三个操作覆盖了项目最核心的鉴权、数据流和播放链路。每一条都能对应到具体代码位置,现场演示时 即使紧张也不会出现「功能点了没反应」的尴尬。

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

Logo

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

更多推荐