1. 这类毕业设计的第一步,不是写代码而是把推荐系统"拆到能答辩"
先说我看到这个题目时的第一反应:SpringBoot + Vue + 短视频推荐 + 内容管理,这一套组合出来,几乎等于把"计算机专业毕设的中等偏上难度"画了个标准像。
为什么这么说?因为它不是简单的CRUD管理系统那种传统毕设(图书管理、考勤打卡之类换个壳),但也没有难到要你用深度学习模型去做CTR预估、在线学习。它的核心挑战在于:推荐系统如何落地、CMS与推荐链路如何打通、前后端如何在一个能演示、能答辩、能跑通的状态下协同工作。
很多同学拿到这个题目后容易犯一个方向性错误——一上来就研究协同过滤公式、看Flink实时计算文档,甚至准备上个TensorFlow做向量召回。结果代码写了两周,连"视频上传后怎么被推荐给另一台电脑上的浏览器"都没跑通。
我的建议是:动手前先把这条完整链路画清楚。从用户上传视频开始,经过后台审核、打标签、转码存储,到用户刷视频产生行为日志,再到后台基于行为更新画像、计算推荐列表,最后通过Vue端瀑布流接口展示出来——这中间每一步分别由哪个模块干、数据存在哪、谁调用谁,只有这张图在脑子里清晰了,你后面写代码才不会越写越乱。
这种题目的合理定位是:一个可运行的、推荐逻辑透明可解释的、前后端分离的短视频内容管理和分发系统。算法可以简单,但链路必须完整。"做了多少功能"在答辩时远不如"我能把这条链路的每一步讲清楚"更能打动评委。
2. 架构选型这段,说清楚"为什么是SpringBoot + Vue"就赢了一半
2.1 整体技术栈与分层设计
我给你的选型建议,不是最炫的,但是最稳、最好答辩、最好扩展的:
| 层次 | 选型 | 核心职责 |
|---|---|---|
| 前端 | Vue 3 + Element Plus + Axios + Vue Router + Pinia | 用户端视频流、创作者上传、管理后台界面 |
| 后端 | Spring Boot 2.7.x + MyBatis-Plus + Spring Security + JWT | 业务API、权限控制、推荐计算 |
| 数据层 | MySQL 8.x + Redis | 业务数据持久化、热数据缓存、排行榜 |
| 存储 | MinIO(或本地磁盘映射) | 视频文件、封面图、转码后的HLS切片 |
| 工具链 | Maven、FFmpeg、Postman、Nginx | 构建、转码、接口调试、部署 |
这套组合的好处有三个:第一是资料多,任何一个环节报错都能搜到解决方案;第二是每层都有明确的"可解释性",答辩时问你为什么用Redis,你能说清ZSet计数排行榜的原理,而不是含糊地说"为了快";第三是它天然适配前后端分离的部署模式,你可以在开发环境用Vite代理,在演示环境直接把前端打包进SpringBoot的static目录,怎么都能跑。
2.2 为什么不在毕设阶段引入Flink/Kafka这类组件
注意我上面没有列Kafka、Flink、Elasticsearch,这不是因为它们不好,而是因为毕设的时间和答辩风险是有限的。
引入Kafka和Flink之后,你的视频行为日志确实能做到秒级实时处理,听起来很高端,但随之而来的是一连串问题:你的Windows笔记本上Flink环境能不能稳定跑了?Window+Watermark+状态后端这些概念你答辩时能不能用两分钟讲清楚?集群资源够不够?万一演示那天Flink作业因为内存配置没起来,你整个系统都瘫痪了,那不是给自己挖坑吗?
我比较推荐的做法是:行为日志先写到MySQL再定期批量计算,或者直接通过Redis异步更新计数。这是生产环境里中小型项目常见的低成本方案,逻辑简单又能讲出道理。如果你确实想让项目有"大数据感",可以用Spring Boot定时任务(@Scheduled)方式模拟流批计算的效果,在攒够一批行为日志后触发推荐权重更新——这既是真实的工程简化,也保留了日后扩展的方向。
3. 推荐系统模块:不用整深度学习,把"画像 + 召回 + 排序"讲透就够
3.1 用户画像建模:从注册信息到行为权重
个性化推荐的第一步是给用户打标签。毕设阶段不需要搞复杂的用户兴趣向量,用行为权重表就够了。
我当年做的时候设计了这样一张行为权重映射:
| 用户行为 | 权重分 | 存储方式 |
|---|---|---|
| 点赞 | +3 | 行为日志表 + Redis更新标签分数 |
| 评论 | +4 | 同上 |
| 收藏 | +5 | 同上 |
| 完播(观看超过80%) | +2 | 同上 |
| 转发 | +6 | 同上 |
| 滑走(观看不足3秒) | -2 | 仅记日志,定时任务扣分 |
每当用户产生行为,接口里除了响应前端之外,还会异步(用Spring的@Async或者线程池)更新一张user_tag_score表。这张表的结构很简单:用户ID、标签ID、最近30天累积分值。为什么要强调"最近30天"?因为兴趣是有时效性的,一个用户三个月前迷健身视频,现在可能转去看美食制作了,时间窗越大画像越"钝"。
标签本身从哪来?这里有一个很务实的选择:短视频上传时由创作者手动勾选标签 + 后台管理员微调,比上HanLP分词自动提取靠谱得多。等你把整个系统跑通了,再考虑用HanLP对视频标题和简介做分词处理,自动扩展标签也不迟。手动标签和自动标签你可以在数据库里用source字段区分,答辩时正好可以说"系统支持自动与手动两种打标方式"。
3.2 召回策略:热门兜底、标签匹配、协同过滤三层架构
推荐系统里召回干的事是"从海量视频中捞出一批候选集"。毕设项目的数据量撑不起百万级视频,但召回的结构一定要有层次,否则答辩被问"推荐列表怎么来的"时,你只能说"从数据库查出来的",这就很被动了。
我建议做三层召回:
第一层:热门视频兜底。只要用户没有足够的行为数据(冷启动阶段),就把全站播放量TopN直接推给他,用Redis的ZSet存视频ID与播放量分数,天然支持排序。这一层不依赖用户画像,简单可靠。
第二层:标签匹配召回。根据用户画像里权重最高的前5个标签,去视频表查带这些标签的视频,按发布时间和点赞量过滤出最近一周的新鲜内容,确保推荐列表里永远有"新的东西"。
第三层:协同过滤召回。这是最能拿分的一层。UserCF(基于用户的协同过滤)的思路是:找到与当前用户兴趣最相似的其他用户(用Jaccard系数或余弦相似度计算),把这些用户近期喜欢的、当前用户没看过的视频推荐出来。我当年用Java实现在Utils里就是个两层循环:
// 简化版 UserCF:计算目标用户与所有其他用户的相似度 public Map<Long, Double> calcUserSimilarities(Long targetUserId) { List<Long> targetTags = userTagMapper.selectTagIdsByUser(targetUserId); Map<Long, Double> simScores = new HashMap<>(); List<Long> allUserIds = userMapper.selectAllIds(); for (Long otherId : allUserIds) { if (otherId.equals(targetUserId)) continue; List<Long> otherTags = userTagMapper.selectTagIdsByUser(otherId); // 求交集大小 Set<Long> inter = new HashSet<>(targetTags); inter.retainAll(otherTags); // 求并集大小 Set<Long> union = new HashSet<>(targetTags); union.addAll(otherTags); double jaccard = union.isEmpty() ? 0 : (double) inter.size() / union.size(); if (jaccard > 0.3) { simScores.put(otherId, jaccard); } } return simScores; }这段代码不算难,但它是真实的"推荐算法实现",比直接调库讲起来更有底气。候选集出来之后,还要做一件事:过滤用户已经看过的视频和30天前的老视频,这一步用一条NOT IN加上时间条件的SQL就完成了。
3.3 排序策略:加权公式比"猜一个分数"靠谱
所有召回候选集汇总后,要统一打分排序。我把排序策略做成一个透明可调的加权公式,这也是答辩时最能展示你工程思维的地方之一:
score = 0.35 * 用户标签相似度 + 0.25 * 发布时间新鲜度(按小时衰减) + 0.20 * 热度分(播放量、点赞量综合) + 0.20 * 行为交互分(完播率、收藏率)每个系数都在后端配置中心或数据库参数表里存着,答辩时你可以说"这个系数是可配置的,不同场景可以调优"。没有人指望你在毕设阶段做AB实验验证系数最优,但这个"平台化"的思路会给你加分。
排序之后取前20~30条,作为该用户本次请求的推荐列表,分页返回给前端。推送出去的同时,把列表缓存在Redis里,Key就用recommend:list:{userId}:{page},防止用户疯狂下拉时反复计算同样结果。
3.4 数据收录与更新的节奏感
推荐结果的更新要讲节奏。如果用户刚点了一个赞,立刻整个推荐列表大变,体验反而很怪。我的做法是:用户交互后,只更新Redis里的热门榜和该用户画像表,推荐列表每十分钟或用户再次进入刷新时重新计算。
然后在Spring Boot里写一个@Scheduled任务,每十分钟做三件事:从行为日志表聚合出最近用户行为增量、重新计算CDN层之外那批活跃用户的推荐列表缓存、把Redis中的播放/点赞计数批量落库。这既避免频繁写库导致锁竞争,又能让数据基本及时。
你可能要问:视频审核通过后,怎么进推荐池?很简单,审核通过就是把视频状态从0改成1,同时插入一条Redis里的新视频队列。定时任务优先从这个队列取数据计算推荐,新视频会拿到"发布时间新鲜度"的额外加成,自然有初始流量。
4. 内容管理后台:视频上传、转码、审核这条链路最容易翻车
4.1 分片上传与校验:别等视频传了一半才发现格式不对
短视频系统绕不开文件传输。本地开发阶段用视频文件直接上传到MinIO就行,但毕设现场演示时网络不稳定,动不动传一半断了,所以我建议花点精力做分片上传。
前端Vue端用el-upload配合File.slice()切成每片5MB,后端Spring Boot接口接收片序号和总片数,全部上传完成后触发合并请求。后端要校验三个东西:文件扩展名(mp4、mov、avi等)、文件大小上限(比如200MB)、视频时长(用FFmpeg去探测,超过5分钟就提示不适合短视频场景)。
保证幂等:分片上传时先创建uploadId,后端把它们暂存在一个临时目录,同一个uploadId反复传同一个分片不会重复写入。4.2 FFmpeg转码与封面截取:把一劳永逸的事做在前面
原始视频动辄几十上百MB,直接让前端用<video>标签播,加载慢且卡顿。这部分建议把视频转成HLS切片(.m3u8 + .ts片段),做成智能码率。做法是后端在视频上传成功后调用FFmpeg命令:
ffmpeg -i input.mp4 -profile:v baseline -level 3.0 -hls_time 10 -hls_list_size 0 -hls_segment_filename "output_%03d.ts" output.m3u8这个命令会把视频切成10秒一个的ts分片,生成一个m3u8索引文件。前端用video.js或者hls.js就能流畅播放。视频转码的同时,用FFmpeg在视频第3秒抽一帧作为封面:
ffmpeg -i input.mp4 -ss 00:00:03 -vframes 1 cover.jpg注意转码是一个耗时操作,不要放在请求线程里等结果,否则前端请求接口会卡几十秒。正确姿势是把转码任务丢进线程池异步执行,转码完成后回调更新视频状态为"转码完成",前端轮询到这个状态后展示播放按钮。答辩时这一块的"异步任务设计"是可以重点讲的内容。
4.3 内容状态机:审核流程要能讲出"状态流转"
内容管理系统最核心的其实不是增删改查,而是状态机设计。我把视频状态字段设计成:
| 状态码 | 含义 | 流转路径 |
|---|---|---|
| 0 | 上传完成待审核 | 上传成功 → 0 |
| 1 | 审核通过 | 0 → 1 |
| 2 | 审核驳回 | 0 → 2 |
| 3 | 已下架 | 1 → 3 |
| 4 | 转码完成可播放 | 1 且转码任务完成 → 4 |
后台管理员上传视频后,先进入0,系统检查无违规后管理员点通过,变为1,同时异步触发转码,转码结束变为4,这个状态下的视频才能进推荐池。下架操作则是由管理员手动触发的,一旦下架,推荐池里立即剔除。
这个状态机听起来简单,但你要是用散落的if-else写在接口里,后面加需求时会改到崩溃。我建议用一张video_status_log表记录每次状态变更的操作人、时间、备注,既方便后期排查,答辩时也能展示"系统具备可审计性"。
4.4 后台数据看板:用一张表撑起"可视化"
毕设展示时,评委大概率会问你:"你的系统如何体现管理效果?"这时候一个粗糙的数据看板比十张表单都有说服力。
我做的是展示四块数字:今日新增视频数、总播放量、活跃用户数、推荐点击率。这四个指标的数据来源分别是视频表按日聚合、Redis的ZSet计数器、用户行为日志去重统计、以及行为表里推荐位点击次数除以推荐曝光次数。甚至可以不画复杂图表,用Element Plus的进度条和卡片组件就能做得很清爽。
5. Vue端容易被追问的前端细节:路由权限、播放体验与打包部署
5.1 动态路由与权限控制
用户端和后台管理用的是同一个Vue应用(也可以是两个项目),但权限要区分。我用的方案是:登录后返回角色编码(USER或ADMIN),前端根据角色用router.addRoute()动态挂载对应路由表;同时配置全局路由守卫,未登录时跳转登录页,用户角色访问管理员路由时直接404。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else if (token && to.meta.roles && !to.meta.roles.includes(userRole)) { next('/403') } else { next() } })后端再配合Spring Security的拦截器二次校验接口权限,形成前后端双重控制。这是面试官和答辩老师都很认可的"纵深防御"思想。
5.2 视频播放体验:m3u8流播放与触底加载
前端播放转码后的视频,我的建议是直接用hls.js包一层,它免安装、支持所有主流浏览器,加载m3u8后自动处理分片。用户端首页用无限滚动代替翻页,滚动到底部自动加载下一页推荐视频。这里要注意的是,瀑布流里的每个视频卡片不要一进来就自动播放全部视频,那样流量直接爆炸。我是hover时试播3秒预览片段,点击后进入详情页再完整播放。
5.3 把Vue打包塞进SpringBoot的两种部署法
开发阶段用Vite的代理解决跨域就行。演示环境我推荐两种部署方式:
一是只用SpringBoot:把Vue构建后的dist目录里的内容拷到SpringBoot的src/main/resources/static目录,再把history模式改成hash模式,一个jar包直接启动,前后端同端口,彻底避开跨域。这种最省事,但生硬。
二是前后端分离部署:Vue跑在Nginx上,SpringBoot跑在独立端口,Nginx配置/api前缀的反向代理。这种方式更专业,你的答辩PPT里还能放一张Nginx配置截图体现部署能力。
6. 排错实录:构建与联调过程中最常踩的四个坑
6.1 找不到合适的SpringBoot版本
有段时间SpringBoot 3.x刚出,很多人直接Spring Initializr默认下拉了最新版,结果发现JDK要17,很多旧教程里的配置类全部失效,网上查资料全是2.x的。我的建议是:毕设阶段别追新,用SpringBoot 2.7.x配合JDK 1.8,资料多、问题少、兼容性好。如果你不小心创建了高版本,可以在pom.xml里把parent版本改回2.7.18,并把Java version改回1.8。这不是技术退步,是工程上的风险控制。
6.2 Maven依赖冲突与构建失败
短视频系统里只要涉及MinIO和HLS相关库,就很容易出现包冲突。我碰到过okhttp版本冲突、jackson版本冲突、hutool和新版fastjson不兼容三种情况。排查大招就一句:mvn dependency:tree,看看冲突的包是从哪个依赖传递引入的,再在pom里用<exclusions>排除。构建失败别慌,先看报错最后几十行带Caused by的部分,那才是根因。
6.3 跨域问题与前端环境变量
开发时Vite代理配置:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }后端同时配CorsFilter允许本地端口。不少同学把跨域配置写在了Spring Security过滤器链之前,导致拦截器先拒了请求跨域配置没生效,这里要搞清楚过滤器的执行顺序。
6.4 Vue打包后路径空白
Vite的默认base是/,如果直接丢到子路径下访问就是一片空白。解决办法在vite.config.js里设base: './',路由模式用hash。这个坑几乎每个做前后端分离的同学都踩过,提前排查掉能省两小时。
7. 答辩前准备好这些问题,比功能列表更有说服力
最后这部分是我最想让你提前排练的——答辩时评委大概率会打断你的演示,直接提技术问题。我整理了几个被问频率极高的问题和应对思路:
"用户画像具体存在哪?怎么更新的?"
回答框架:用户行为以JSON格式追加写入行为日志表,同时异步更新user_tag_score表一张关系表;Redis里存一份画像热数据用于推荐候选集的快速检索;定时任务负责将Redis数据与MySQL对齐。
"你的协同过滤复杂度很高,数据量大的话怎么办?"
如实回答三层策略:第一层热门榜单是O(1)读取;第二层标签匹配靠索引;第三层UserCF才需要算相似度,但已经限制了只对最近30天活跃用户计算,且用向量存储提前算好了离线结果。你的项目没到大数据量,但这个伸缩性的回答思路是成立的。
"推荐结果有没有做过评测?"
毕设阶段可以做一个简单但认真的指标:推荐点击率(CTR),即从推荐位发出的内容最终被点击播放的比例。如果点击率明显高于热门榜随机内容的点击率,说明基本达到了个性推荐的效果。把这个数据记录在答辩PPT里,是最直观的量化证据。
做完这个项目之后我自己有个很深的体会:这个题目真正的价值不在于推荐算法本身有多深奥,而在于它逼着你把"数据怎么流转、状态怎么管理、权限怎么控制、计算怎么异步化"这些工程问题全部考虑一遍。这些能力才是日后工作中天天都要用的。
最后分享一个答辩现场很加分的小动作:在你的源码里留一个"一键初始化演示数据"的SQL脚本,把账号、视频、行为数据全部预置好。到时候答辩老师让你操作,你就不会因为现场手忙脚乱地造数据而冷场——这个小细节,是真的能救场的那种实用。