
视频创作平台这类毕设题目每年都能在答辩现场见到好几个但做得像样的不多。很多人把视频创作平台理解成一个简单的视频列表加播放器结果答辩的时候被老师一个问题问到哑火你的视频是怎么存储的上传一个1GB的文件会不会把服务搞挂转码任务是怎么处理的你要是没想过这些问题这个项目基本就只是玩具级别。我前阵子刚好帮一个学弟捋过一个完整的 Java 视频创作平台项目从立项、设计、编码到部署全程跟了一遍。今天把这套东西整理出来说说怎么用 JavaSpring Boot把一个视频创作平台从零搭到能答辩、能跑演示、能回答老师追问的程度。这里面的东西不挑你是有 Java 基础的老手还是刚学完 SSM 的新手照着思路走至少不会在核心问题上翻车。1. 视频创作平台的业务拆解与功能规划很多毕设翻车翻在业务边界没划清楚。你要做一个视频创作平台那它到底是一个爱优腾的视频播放网站还是一个B站/YouTube的创作者中心两者的复杂度差了不止一个量级。从标题看核心落在创作管理系统上也就是说重点不是海量视频分发而是围绕创作者的一整套内容管理闭环注册登录、创作空间、视频上传与转码、封面生成、分类标签、发布管理、数据统计浏览量、点赞、评论以及普通用户的浏览、播放、互动。这跟纯视频门户的最大区别是系统必须有创作者视角和用户视角两套界面和两套权限。1.1 用户角色与权限模型用最朴素的 RBAC基于角色的访问控制模型就够了别上来就整用户-角色-权限三张表加一堆关联毕设没必要也容易把前端页面搞复杂。我建议直接两种角色普通用户USER浏览视频、搜索、点赞、收藏、评论、关注创作者。创作者CREATOR在普通用户能力基础上额外拥有创作中心入口可以上传视频、管理作品列表、查看每个视频的播放数据和评论列表。管理员ADMIN如果愿意做可以加用户管理、视频审核、违规内容下架。这个角色我不是必须推荐很多毕设不加管理员答辩时老师会问谁来保证内容安全加一个很简单的视频状态字段待审核、已发布、已下架就能圆回来。权限控制不用上 Spring Security 的重型过滤器链用拦截器HandlerInterceptor加 JWT Token 解析就够或者简单点用 Session 拦截器判断角色。我见过不少毕设用 Shiro也行但如果你对配置不熟答辩的时候被问Shiro 的 Realm 是干什么的容易卡壳。用 Spring Boot 拦截器反而更好讲请求进来 → 拦截器解析 Token → 把用户信息塞到请求上下文 → Controller 参数里直接拿当前用户。1.2 核心功能清单模块功能点关键实现用户模块注册、登录、Token发放JWT / Session密码加密存 MD5(盐) 或 BCrypt创作者中心作品管理、上传入口、数据概览角色判断 独立路由视频上传单文件上传、分片上传、断点续传MultipartFile / 分片合并视频处理转码为 H.264、截取封面、获取时长FFmpeg 命令行调用视频发布填写标题、简介、分类、封面、可见性表单 文件字段合并提交浏览播放列表页、搜索、分类筛选分页查询 播放地址鉴权互动模块点赞、收藏、评论、浏览量异步计数 Redis 缓存热点管理后台用户管理、视频审核/下架简单 CRUD 状态流转这个表格可以直接当成你开题报告的功能模块图来用每一行都是一个能写成一个小节的功能点工作量可视。1.3 需求边界划定哪些必须做哪些不要碰我见过很多同学一开始雄心勃勃想加视频剪辑在线直播AI 审核结果做到中期发现时间根本不够。答辩老师看的不是你功能列表长得吓人而是你做完的东西是不是完整闭环。必须做的核心闭环注册登录 → 进入创作者中心 → 上传视频 → 系统转码并生成封面 → 填写信息并发布 → 前台展示 → 用户观看并互动 → 创作者看到数据。这个链路从头到尾都通就已经是一个合格的毕设。建议放弃的功能在线剪辑工程量大、多人协同还要做同步协议、视频推荐算法做不好就是被老师追问的靶子、付费订阅涉及支付 API麻烦。这些有没有都不影响你拿高分反而砍掉它们能让核心逻辑更扎实。2. 技术选型与系统架构设计技术选型这件事一定要做有理由的选择而不是听别人说这个火所以用这个。答辩老师特别喜欢问为什么用 Redis为什么不用 JPA 用 MyBatis。你回答项目需要所以用了会被扣分回答因为某某场景有某某特性才能站住。2.1 后端框架Spring Boot 3.x 还是 2.xMyBatis Plus 还是 JPA先说版本。如果你 JDK 用的是 8那老老实实用 Spring Boot 2.7.x如果你电脑装的是 JDK 17 甚至 JDK 21可以用 Spring Boot 3.x但要注意它的一些坑javax 包名改成了 jakartaMyBatis Plus 要用 3.5.3 专版很多老教程的写法直接搬过去会编译报错。毕设求稳的话我推荐 Spring Boot 2.7.x JDK 8/11 的组合资料多、报错搜得到答案怎么都能跑起来。ORM 我强烈推荐 MyBatis Plus。理由很简单它内置了 BaseMapper单表的 CRUD 完全不用手写 XML你的注意力可以放在业务逻辑上而不是 UserMapper.xml 里那一条 selectById。而且在答辩的时候你可以很自然地说常规接口用 MyBatis Plus 的封装方法复杂统计查询手动写 SQL这个组合是业界最常见也是最实际的写法。JPA 也不是不行但 JPA 的懒加载、缓存机制、对象关系映射在答辩时容易挖坑一句为什么查出来是 LazyInitializationException就能让很多人当场卡住。MyBatis Plus 的 SQL 逻辑直白出了问题你知道它在跑什么。2.2 存储方案MySQL Redis 本地文件系统视频文件本身不能丢数据库里这应该是常识。数据库只存视频的元数据标题、简介、URL、大小、时长、状态、创建人 ID视频实体文件存服务器磁盘或对象存储OSS。我建议毕设用本地磁盘存储根目录下建 upload/video 和 upload/cover 两个目录。理由朴素不花钱、不依赖外网、答辩现场的断网环境也能正常演示。如果担心项目显得不够工业化可以在文档里写一句生产环境可替换为 OSS/MinIO本系统基于本地存储实现通过文件服务抽象层屏蔽存储差异然后留一个 FileStorageService 接口本地实现类里写文件读写逻辑。这一步在设计文档上是加分项代码上的工作量又很小。Redis 在这个项目里不是必选项但你加了它答辩的含金量立刻不一样。我建议至少用在两个位置一是热点视频的播放量计数避免每次都去 update Java_learning 表的 view_count明星字段而是先 INCR Redis 再异步同步到 MySQL二是视频分类列表的缓存分类这种变更频率极低的数据缓存到 Redis 里能省掉大量重复查询。2.3 模块工程结构与包组织工程结构不要搞那种大而全的多模块 Maven 项目parent 里挂一堆 api、common、admin、core 子模块对毕设来说过了启动和调试都要多花不少时间。就一个 Spring Boot 单体应用内部按分包com.creator.video ├── controller # 接收请求参数校验 │ ├── admin │ ├── creator │ └── user ├── service # 业务逻辑 │ ├── impl ├── mapper # MyBatis Plus 的 Mapper 接口 ├── entity # 数据库实体 ├── dto # 前端请求/响应参数对象 ├── config # 配置类跨域拦截、拦截器注册 ├── interceptor # 登录/权限拦截器 ├── common # 常量、统一返回结果、异常处理 └── util # JWT、加密等工具这个结构的核心思想是分层 按模块分包controller 只用 serviceservice 不用 controller 的东西entity 不直接返回给前端用 dto 隔离。有些同学图省事把 entity 直接挂在接口上返回结果查出密码字段返回给前端这在答辩现场被指出来会非常尴尬。Controller 层还应该做一个统一异常处理和统一返回结构。我自己固定习惯是 Result { code: 200, msg: success, data: ... }。一旦结构统一前端处理响应就简单很多全局异常捕获也只需要 RestControllerAdvice 一个类。2.4 数据库表设计别只建了 users 和 videos数据库表设计是答辩必考项。你用 MySQL 建表至少要覆盖这些user用户表id、username、password、nickname、avatar、role、status、create_timevideo视频表id、user_id、title、description、category_id、cover_url、video_url、duration、size、status、view_count、like_count、favorite_count、publish_timecategory分类表id、name、sortcomment评论表id、video_id、user_id、content、create_timefavorite收藏表id、user_id、video_id、create_time加唯一索引防止重复收藏likes点赞表id、user_id、video_id、status或者直接用 redis set 记录点赞用户几个关键细节视频状态 status 用 tinyint0 待审核、1 已发布、2 已下架。发布流程虽然简单但有一个待审核/已发布状态流转答辩老师就很难说你的系统没有内容管理意识。关注关系可以不做单独表如果做就是 follower_id 和 followee_id 两张表或者一张表搞定别搞复杂了。视频字段不要存绝对的完整路径比如 C:\Users\xxx\upload/xxx.mp4。存相对路径 /video/xxx.mp4访问时由前端拼上服务器地址。不然换环境跑 Demo 的时候所有路径全废那是本人亲身踩过的坑。详细一点video 表的建表语句可以是这样简化CREATE TABLE video ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 上传者ID, title varchar(100) NOT NULL COMMENT 视频标题, description varchar(1000) DEFAULT NULL COMMENT 视频简介, category_id bigint NOT NULL COMMENT 分类ID, cover_url varchar(255) DEFAULT NULL COMMENT 封面相对路径, video_url varchar(255) NOT NULL COMMENT 视频文件相对路径, duration int DEFAULT NULL COMMENT 视频时长(秒), size bigint DEFAULT NULL COMMENT 文件大小(字节), status tinyint DEFAULT 0 COMMENT 0待审核 1已发布 2已下架, view_count int DEFAULT 0, like_count int DEFAULT 0, favorite_count int DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, publish_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT视频信息表;索引不需要多但 user_id、category_id、status 这种查询条件字段一定要有答辩老师如果看到 SELECT 语句全表扫描基本会追一句数据量大怎么办索引就是最好的答案。3. 核心模块实现与关键代码我挑几个最有含金量、也是答辩最容易紧盯的模块来讲登录鉴权、视频分片上传、FFmpeg 转码、播放鉴权、互动与计数。这些是视频创作平台跟普通 CRUD 项目的分水岭。3.1 用户登录与 JWT 鉴权毕设用 JWT 已经是约定俗成的玩法。核心逻辑就三步登录成功发签 Token前端把 Token 存在 localStorage请求时放进请求头 Authorization后端拦截器校验 Token 合法性。我建议不要自己拿秘钥手动塞 payload 写一个手写 JWT直接用 jjwt 库。dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency工具类public class JwtUtil { private static final String SECRET your-secret-key; private static final long EXPIRE 1000 * 60 * 60 * 24; // 24小时 public static String createToken(Long userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET) .parseClaimsJws(token).getBody(); } }拦截器里面做的事拿到请求头 Authorization → 去掉 Bearer 前缀 → parseToken → 把 userId 放进 request 的 attribute → 放行。写一个 LoginInterceptor 和 AdminInterceptor注册的时候配置好哪些路径需要登录、哪些需要管理员权限。这里有一个实操注意点拦截器配置路径千万别写错不然会出现前端明明带了 Token 还是 401。我建议这样配registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /video/list, /video/detail/**, /video/cover/**, /video/stream/**);也就是说所有接口默认都要登录只有登录注册、游客能看的视频列表和视频流地址放行。这个设计逻辑在答辩时非常好讲未登录只能浏览和播放一旦要做互动或进入创作者中心就必须登录。3.2 视频上传分片上传才是真知识点普通表单上传用 MultipartFile 接收一个文件就完事了。但视频文件和图片不一样动辄几百 MB 到几个 G单次 POST 容易超时、内存溢出、断网重来。毕设里你不一定要把分片上传做到完美但你必须理解并实现它。业界通用思路是前端把文件切成片比如每片 5MB顺序传给后端后端保存每一个分片到临时目录等所有分片传完后触发合并接口把分片按序号拼接成完整文件。前端如果用 Vue可以用 vue-simple-uploader把分片大小、并发数一配就行。后端需要提供两个接口// 接收分片 PostMapping(/upload/chunk) public Result? uploadChunk(RequestParam(file) MultipartFile file, RequestParam(identifier) String identifier, RequestParam(chunkNumber) Integer chunkNumber) { String dir uploadDir /chunks/ identifier; File f new File(dir, chunkNumber .part); file.transferTo(f); return Result.success(); } // 合并分片 PostMapping(/upload/merge) public Result? merge(RequestParam(identifier) String identifier, RequestParam(fileName) String fileName) { File dir new File(uploadDir /chunks/ identifier); File[] parts dir.listFiles(); Arrays.sort(parts, Comparator.comparingInt(f - Integer.parseInt(f.getName().replace(.part, )))); File merged new File(uploadDir /temp/ fileName); try (FileOutputStream fos new FileOutputStream(merged)) { for (File part : parts) { Files.copy(part.toPath(), fos); } } // 删除临时分片目录 FileUtil.delete(dir); return Result.success(merged.getAbsolutePath()); }这个实现是最简版够演示了。但你在写进博文的时候有一个小设计别漏掉记录视频上传状态。你可以建一张 video_upload_record 表id、user_id、identifier、file_name、total_chunks、uploaded_chunks、status每次上传分片更新 uploaded_chunks这样断点续传只需要在合并前检查前端传的 uploadedChunks 是否等于 totalChunks。回答断点续传怎么实现的追问时你的回答就会比那些直接抄分片代码的同学深一层不光是分片合并还包含分片上传进度的持久化与对账。3.3 视频转码与封面截取FFmpeg 是加分大项视频上传完不能原样就用于播放。浏览器能直接播放的格式有限手机录的 MOV 很多浏览器不支持所以系统要在后端调用 FFmpeg 做转码输出统一的 H.264 AAC 的 MP4同时截取第一帧或指定时间点作为封面。转码命令大概长这样ffmpeg -i input.mp4 -c:v libx264 -c:a aac -movflags faststart -pix_fmt yuv420p output.mp4在 Java 里用 ProcessBuilder 调用命令行public void transcode(String inputPath, String outputPath) { ListString command new ArrayList(); command.add(ffmpeg); command.add(-i); command.add(inputPath); command.add(-c:v); command.add(libx264); command.add(-c:a); command.add(aac); command.add(-movflags); command.add(faststart); command.add(-pix_fmt); command.add(yuv420p); command.add(-y); command.add(outputPath); ProcessBuilder pb new ProcessBuilder(command); pb.redirectErrorStream(true); Process process pb.start(); process.waitFor(); }注意两个地方-movflags faststart 会让 MP4 文件把 moov 原子视频索引信息移到文件头部这样浏览器可以边下边播不用等整个文件下载完-pix_fmt yuv420p 是保证在浏览器里兼容性最好的像素格式漏掉它可能产生花屏或无法播放的问题。截取封面命令ffmpeg -i input.mp4 -ss 00:00:02 -frames:v 1 cover.jpg综合下来我会建议把转码和封面生成放进一个异步服务里用一个简单的线程池ThreadPoolTaskExecutor执行。上传接口先把源文件落盘返回视频正在处理中后台线程做转码和封面完成后再更新数据库中视频的状态为可发布。异步化会让你上传接口响应很快同时给答辩留下一个耗时任务异步处理的解题思路。实操里面最容易忽略的一点FFmpeg 的路径问题。在 Windows 上如果你直接写 ffmpeg必须保证 ffmpeg.exe 在 PATH 环境变量里部署到 Linux 服务器也一样。更稳妥的方式是配置 app.ffmpeg.path/usr/bin/ffmpeg读配置文件。这个细节看起来小但现场演示的时候转码失败立刻黑脸。3.4 播放器加载与播放地址鉴权视频播放的常规做法是前端写一个 video 标签src 指向后端提供的一个接口比如 /video/stream/{videoId}。这里有个安全感和答辩质量的矛盾点如果不鉴权视频直接暴露任何人都能下载源文件如果太复杂的鉴权URL 签名、防盗链毕设演示又会给自己添乱。我建议做一层轻量鉴权视频文件不放在静态资源目录下而是通过一个接口输出流登录用户或请求里带合法 Token 才能访问。实现用 ResponseEntity GetMapping(/video/stream/{videoId}) public ResponseEntityResource stream(PathVariable Long videoId, RequestHeader(value Authorization, required false) String token) { // 校验token或者视频如果是公开状态允许游客临时访问 Video video videoService.getById(videoId); if (video null || video.getStatus() ! 1) return ResponseEntity.notFound().build(); Resource resource fileStorageService.loadAsResource(video.getVideoUrl()); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(video/mp4)) .body(resource); }这种接口方式比直接把 video_url 暴露给前端更安全而且你在答辩时可以讲这是一种服务端流式输出不是简单静态文件托管为后续实现播放权限、转码后切片HLS留下了扩展点。3.5 点赞、收藏、评论与播放量计数互动模块的难点是并发。一次点赞如果直接 UPDATE video SET like_count like_count 1在高并发下会产生行锁竞争。毕设里你可以不搞太复杂但至少要体现一个思路热点数据先走 Redis再异步落库。我推荐两种做法选其一做法 A纯 MySQL 计数每个点赞操作先查 likes 表判断是否已点赞再执行 UPDATE 计数。简单直接也够用。唯一要留心的是 likes 表加唯一索引user_id, video_id防止重复点击产生脏数据。做法 BRedis 计数 定时落库点赞只写 RedisSETEX 一个 key比如 like:video:123 是一个 set成员是用户 id播放量用一个 INCR 指令value 就是视频 id系统每隔 5 分钟跑一个定时任务把 Redis 中攒的计数同步到 MySQL。这个方案在答辩中更亮眼。我自己实际项目里用的是做法 B 的简化版核心就两个逻辑// 用户点赞 public void like(Long videoId, Long userId) { Boolean first stringRedisTemplate.opsForSet().add(like:video: videoId, userId.toString()); if (Boolean.TRUE.equals(first)) { stringRedisTemplate.opsForValue().increment(like:count: videoId); } } // 定时同步到MySQL Scheduled(fixedDelay 60000) public void syncLikeCount() { SetString keys stringRedisTemplate.keys(like:count:*); for (String key : keys) { String videoId key.substring(like:count:.length()); Integer count Integer.parseInt(stringRedisTemplate.opsForValue().get(key)); videoMapper.updateLikeCount(Long.valueOf(videoId), count); stringRedisTemplate.delete(key); } }当然这只能保证最终一致会出现最多 1 分钟的计数延迟但这在真实业务场景里很正常答辩老师听到你说用最终一致性换性能反而会认可你有工程意识。4. 开发过程中典型的坑与排查实录这部分内容全是实操现场踩出来的体感比任何教程都真实遇到同款问题可以直接照方抓药。4.1 MultipartFile 传到一半就报 MaxUploadSizeExceededException症状上传一个几百 MB 的视频Nginx 或 Spring 直接抛异常前端收到 500。原因Spring Boot 默认单文件上传上限是 1MB你没配。解决spring: servlet: multipart: max-file-size: 2048MB max-request-size: 4096MB如果你前面做了分片上传单片 5MB这里的限制其实不用开这么大。但如果你图省事直接做整文件上传那 2GB 的上限必须配到否则必炸。4.2 FFmpeg 转码进度看不到以为卡死了ProcessBuilder 执行 ffmpeg 时如果把输出流不读取缓冲区满了 ffmpeg 会被阻塞看起来像程序卡住。我排过好多次这种问题几乎都是因为没消费子进程的输出流。解决在 process.waitFor() 之前起一个线程读 errorStream 和 inputStream。或者用 commons-exec 的 DefaultExecuteResultHandler。我用得很顺手的一个方式是先开两个线程new Thread(() - { read(process.getInputStream()); }).start(); new Thread(() - { read(process.getErrorStream()); }).start(); int exitCode process.waitFor();同时你还可以设置一个超时机制比如 5 分钟没处理完就 process.destroyForcibly()防止某个坏视频文件把整个转码线程池耗干。4.3 MySQL 视频表里存了路径一换环境全挂开发时存储相对路径这也是我在第 2 节特意强调的事。我见过学长的毕设代码里 video_url 存的是 http://localhost:8080/upload/xxx.mp4拿到别的电脑上一跑全部显示不出来本地路径失效。别让这种低级事故砸了答辩。更好的做法是数据库里只存逻辑路径 /file/video/2024/05/xxx.mp4页面展示时用一个全局方法拼接域名前缀。项目里写一个 UrlUtils 或配置属性前端拿到的完整地址由后端统一生成。4.4 前端跨域和带 Token 的图片加载后端如果单独部署在 8080前端跑到 5173Vite 默认端口跨域是必现的。全局配置一个 CORSConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }但注意一点如果你用了拦截器校验 Token跨域的预检请求OPTIONS会在进入拦截器之前就被拦截器处理掉导致真正的前端请求因为预检没放行而失败。解决办法是在拦截器里加一行判断if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; }这个坑特别隐蔽不加这一行前端会一直报跨域请求失败但不是 CORS 配置问题是拦截器把预检请求拦了。4.5 点赞数被刷Redis 里每秒 INCR 几十次毕设演示时如果被围观的同学反复点赞会显得这个系统太容易刷量。真实环境下的反刷机制IP 限制、设备 ID、行为特征对毕设来说太重但我建议至少做一层用户维度限流同一个用户对同一视频点赞后端判断 likes 表里已存在记录就拒绝而不是前端按钮置灰就完事。前端置灰可以被绕过后端校验才是真的校验。我习惯把发放点赞令牌做成一个接口点赞前先 GET /like/status/{videoId}如果已经点了后端再次调用点赞接口时直接返回已点赞。5. 性能优化与部署上线要点毕设到这一步就接近收尾了。这一部分做得好不好直接决定答辩打分是在优秀档还是良好档。我把部署和优化串成一条线来说。5.1 热点视频列表的缓存策略视频首页轮播图、热门视频列表数据不常变但会被反复查询。每刷一次首页都去 MySQL 里 ORDER BY view_count LIMIT 10就是白白消耗数据库。可以把热门榜单缓存到 Redis设置 5 到 10 分钟过期。实现很简单public ListVideoVO getHotVideos() { String key hot:videos; String cached stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(cached)) { return JSONUtil.toList(cached, VideoVO.class); } ListVideoVO list videoMapper.selectHotVideos(10); stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(list), 5, TimeUnit.MINUTES); return list; }这里要注意的是缓存穿透如果数据库里没有热门视频缓存也没数据每次请求都会打到数据库。可以在代码里判断 list 为空时也缓存一个空数组过期时间设短一点比如 60 秒。5.2 定时任务清理临时文件分片上传产生的 chunks 目录、合并后的 temp 文件、转码前的源文件备份都会吃掉大量磁盘空间。写一个定时任务每天凌晨清理超过 24 小时的临时目录。Scheduled(cron 0 0 3 * * ?) public void cleanTempFiles() { File chunksRoot new File(uploadDir /chunks); File[] dirs chunksRoot.listFiles(); if (dirs ! null) { for (File dir : dirs) { if (System.currentTimeMillis() - dir.lastModified() 24 * 60 * 60 * 1000) { FileUtil.delete(dir); } } } }这个模块加进去5 行代码但能让答辩老师看出你的系统是有运维意识的而不是个玩具。5.3 部署时环境准备清单毕设部署通常就一台云服务器2核4G 就够。按照这个顺序操作基本不会乱安装 JDK 8 或 11配置 JAVA_HOME。安装 MySQL 8.0把本地导出的 sql 导入。安装 Redis。服务器安装 FFmpegapt install ffmpeg 或 yum install ffmpeg。把 Spring Boot 项目打成 jar 包mvn clean package。上传 jarnohup java -jar video-platform.jar 启动。前端项目 npm run build把 dist 目录丢给 Nginx配置 location /api 反向代理到后端 8080。Nginx 配置反向代理的时候如果不配的话前端会一直请求 /api 得到 404location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }注意 proxy_pass 后面那个斜杠有斜杠表示把 /api 前缀去掉再转发没有斜杠则是完整的 /api 路径转发。我遇到过很多次这种前端请求正常后端返回 404都是这个斜杠的问题。还有视频文件的 Nginx 直出路径如果你让 Java 接口输出视频流就不需要配置如果你想让 Nginx 直接访问本地静态文件就要加 location /file/ 的 root 映射。5.4 答辩演示时的几个小建议这部分不算代码但很关键。平时开发你可能开着可写日志、没关 debug演示的时候控制台刷屏、页面网络请求全是 500 警告印象分会打折扣。我建议正式演示前做三件事把日志级别调到 WARN只保留关键异常日志。准备一条 1 分钟内的短视频用于演示转码不要现场传一个 1GB 的大视频等转码就等半天尴尬到脚趾抠地。提前测一遍断网环境如果没有云服务器至少保证本机演示时不要依赖外网 CDN很多视频播放器组件或前端库如果走 CDN 加载断网时页面空白这是最常见的翻车原因。6. 从毕设代码到可扩展项目的进阶方向项目做完、答辩完代码别急着压箱底。这个系统其实是很好的起点后面你想继续深入几个方向难度和收益我列个对比。方向技术重点难度价值视频分片播放 HLSFFmpeg 切 m3u8 M3U8 播放器中商用级刚需评论系统改造引入 WebSocket 实时弹幕中交互体验提升推荐算法简单的内容标签匹配 用户行为记录中高算法岗简历亮点短视频竖屏信息流双列流、翻页预加载、点赞动画中紧跟行业形态我个人觉得最容易入手的是 HLS。源 MP4 整文件播放占带宽、不支持进度条快进精度高也防不了盗链。如果你用 FFmpeg 把一个视频切成 4 到 6 秒的切片生成 m3u8 索引文件前端用 hls.js 播放系统整体就高级感上来了。而且你已经有了 FFmpeg 这套 CLI 调用经验切 HLS 只是把输出命令换一下ffmpeg -i input.mp4 -codec: copy -start_number 0 -hls_time 6 -hls_list_size 0 output.m3u8另外把本地存储抽象成 MinIO 或者 OSS是批量生产级的一个套路。你只要把 FileStorageService 接口做好加一个 OssFileStorageServiceImpl在配置文件里切换实现类型。这个扩展点写进简历面试官普遍会觉得这个人有抽象设计意识。我在帮学弟做这个项目的时候最大感悟是毕设项目不要太贪大但一定不要在每个关键环节上只做表面功夫。上传只做单文件没有问题但你要能说明为什么不支持分片下一步怎么演进用本地存储没有问题但你要能说明云存储的接入边界在哪里不搞推荐算法没有问题但你要能说出业务上可以用什么手段优化曝光。这些能讲清楚取舍的能力答辩老师最买账。最后再分享一个小技巧把数据库表的设计文档、接口文档用 Markdown 整理成一份 PDF答辩之前打印出来。老师问任何一张表、任何一个接口你都能指着文档回答展示出来的工程素养比代码本身还加分。这个习惯我到现在做项目还在用实测有效。