记一次SpringBoot短视频推荐系统毕业设计的完整复盘
每年到毕业季前后,总有同学拿着“Java后端 + Vue前端”的技术栈来问我,做什么题目既好落地、又能讲出点东西来。短视频推荐系统是我这几年看来性价比很高的一个方向。它表面上是“做一个视频App后台”,实际上把用户权限、内容管理、推荐算法、前后端分离开发这些高频考点全串起来了。就算推荐部分砍到最简,光是把SpringBoot+Vue这套流程跑通,再配合一个“看得见效果”的推荐结果页,答辩和简历就都有了实打实的素材。
这篇文章不聊虚的,纯粹以过来人的视角,把从选题到落地、从数据表设计到推荐链路搭建、从前端页面联调到答辩追问点的完整过程,一步步拆开讲清楚。如果你正准备做类似的毕设,或者公司内部要快速搭一个内容分发方向的演示系统,这篇的思路可以直接拿来用。
1. 选题思路与技术选型:为什么短视频推荐系统值得做
1.1 这个题目到底在考察什么
短视频推荐系统乍一听是个“大厂项目”,但拆开看,它的核心环节不多:内容管理、用户行为采集、推荐分发、管理后台。这四个环节恰好覆盖了毕业设计最常见的能力要求:数据库设计能力、业务逻辑抽象能力、简单算法实现能力、前后端联调能力。
我之前见过不少同学选了泛泛的“XX管理系统”,代码写完之后,答辩时老师一句“这个系统的核心难点是什么”就能把人问住。但换成推荐系统,核心难点非常明确:推荐结果是怎么算出来的?为什么这个用户看到的是这些视频而不是另一些?这个问题本身就自带技术深度。
另外,这个题目还有一个隐藏优势:效果可视化。管理系统做完以后页面都是表单和表格,看不出“智能”在哪里。推荐系统不一样,用户登录后首页刷出来的内容能明显感知到“是不是越来越合口味”,演示效果好很多。
1.2 技术栈选型对比与取舍
毕设技术栈的原则只有一条:不要为了炫技选自己完全不懂的框架,要选“能讲清楚原理”的。我比较推荐下面这套组合:
| 层次 | 选型 | 理由 |
|---|---|---|
| 后端框架 | SpringBoot 2.7 + MyBatis-Plus | 生态成熟、代码量少、资料多 |
| 数据库 | MySQL 8.0 | 存储用户、视频、行为等结构化数据 |
| 缓存 | Redis | 存热门视频列表、登录Token、推荐结果缓存 |
| 前端 | Vue 3 + Vite + Element Plus | 开发效率高,组件库现成 |
| 鉴权 | Sa-Token 或 JWT | 轻量,毕设不用上Spring Security全家桶 |
| 文件存储 | 本地存储 + Nginx静态映射 | 不依赖第三方OSS,部署简单 |
有同学喜欢把Spring Cloud、RabbitMQ、Elasticsearch全堆上去,结果系统复杂到连启动都费劲。我的建议是:微服务和消息队列放到“扩展思路”PPT里讲就行,真正实现的时候用单体+Redis,稳才是第一位的。
这套选型的好处在于:每个组件你都能说清楚“它是干什么的”以及“为什么选它不选别的”。比如Redis,你可以讲“用Redis缓存热门视频列表,减少数据库压力”——这就够了。
1.3 系统功能模块怎么划分
标准的短视频推荐系统可以拆成三个端:
- 用户端(Vue页面):首页视频流、视频详情、点赞/收藏/关注、发布视频、个人中心。
- 管理端(Vue页页面):视频审核、用户管理、分类管理、内容统计。
- 后端服务(SpringBoot):统一提供RESTful接口,处理推荐、上传、互动、管理等全部业务逻辑。
推荐链路单独划成一个模块,放在service层里,不要跟业务代码混在一起。我当时指导的一个同学就是把推荐计算写在了controller里,后期改个参数要改三层方法,血泪教训。
这就是前后端分离架构,静态资源用Nginx部署,后端跑在8080端口,通过接口交互。
2. 数据库设计与核心表关系
2.1 核心数据表盘点
数据库是整个系统的地基,表设计的好坏直接决定后期开发的痛苦程度。我按“用户体系—内容体系—行为体系—管理辅助”四个维度来建表。
用户体系:
- user:用户ID、昵称、头像、密码加密串、注册时间、状态。
- follow:关注关系,user_id + follower_id 联合唯一,用于“关注的用户发布了新视频”这种feed。
内容体系:
- video:视频ID、作者ID、标题、简介、视频URL、封面URL、分类ID、状态(草稿/待审核/已发布/下架)、发布时间、播放数、点赞数、收藏数。
- category_info:分类ID、分类名称。
- tag:标签表。
- video_tag:视频和标签的多对多关联表。
行为体系:
- user_like:用户点赞记录,user_id + video_id 联合唯一。取消点赞就删记录。
- user_favorite:收藏记录表,结构与点赞类似。
- view_history:浏览记录表,记录用户每次刷视频的时长、时间戳,是推荐算法的重要输入。
管理辅助:
- admin_user:管理员账号表,与管理端登录对应。
这套表结构不算多,但足以支撑推荐算法的“数据闭环”。
2.2 行为数据表与推荐数据流
推荐系统都说“数据决定上限”,落实到表设计上,就是一定要把用户行为完整记下来。很多新手做推荐系统,表里只有用户和视频两张表,那推荐就成空中楼阁了。
行为表里最重要的字段是view_history。设计时我一般建议至少保留这几个字段:user_id、video_id、watch_duration、video_duration、create_time。为什么要有watch_duration?因为“看了多久”比“看没看”更能反映用户兴趣。完整看完30秒短视频和看了2秒就划走,应该代表完全不同的态度。
推荐的数据流可以这么理解:用户刷视频,产生行为数据,行为数据被推荐模块读取,结合user表的偏好标签和video表的分类属性,生成新的推荐候选集,再过滤掉已经看过的内容,最终形成用户的个性化feed流。这套链路是整个系统能否在答辩时说清楚的核心。
2.3 表结构设计要避开的坑
有几个雷区我见过的频率最高:
第一,密码不能明文存储。至少用BCrypt或者MD5加盐,哪怕毕设也一样。答辩老师如果看到数据库里明文密码,印象分会大打折扣。
第二,播放数和点赞数不要每次都去count自表。应该在video表里做冗余字段,点赞时直接update count,被点赞量高的数据,再走一个异步任务定期核对。
第三,行为表必须加联合唯一索引。否则用户重复点赞会生成脏数据,推荐统计直接偏掉。
另外,凡是作为查询条件的字段,比如user_like表的user_id、video_id,都要加索引。实测下来,数据量到几万条以后,没有索引的查询会明显变慢,答辩演示时前端转圈圈就很尴尬。
删除行为表之前,先确认对应的业务状态是否允许物理删除。比如点赞表可以物理删,但浏览记录一般保留。
3. 推荐链路:从行为采集到个性化排序
3.1 推荐系统的四层结构
推荐系统不管做多简单,流程上都离不开四个环节:召回、过滤、排序、兜底。
- 召回:从全量视频里粗筛出几百个候选。
- 过滤:把用户已经看过的、状态异常的、作者已注销的剔除。
- 排序:按“得分”从高到低排列。
- 兜底:如果用户没有行为数据,推荐热门视频,保证用户不会面对一片空白。
很多毕设的推荐之所以看起来“不智能”,是因为跳过了召回和排序,直接把全表按照播放量排序。那本质上就是热门排行,跟“个性化推荐”没半毛钱关系。
我用一个接地气的类比来解释四层结构:你去食堂吃饭,窗口师傅先帮你把几十道菜里你“可能不爱吃的”排除掉;再根据你平时爱吃的口味排个序;最终端给你一盘菜。师傅的口味判断就是用户的指纹画像,排除和排序就是推荐引擎的工作。
3.2 冷启动策略:新用户和新视频怎么办
冷启动是推荐系统绕不开的问题,也是答辩老师最喜欢追问的点。处理方案不追求绝对最优,但流程必须自洽。
对于新用户,还没有行为数据时,直接走“热门兜底”策略:把全局近7天播放量、点赞量、收藏量按一定权重加权求和,倒序输出。这里给一个参考公式:
score = 播放量 * 0.4 + 点赞量 * 0.35 + 收藏量 * 0.25
不用搞复杂的时间衰减,只要加一个“发布时间必须小于7天”的SQL条件,就能保证热门内容不全是老视频。
对于新视频,问题是推荐系统根本不会推它,因为它的互动数据全部是0。解决办法是打“新鲜期保护标签”:新视频在发布后24小时内默认给予一个基础权重加成,保证至少有几百次曝光,让真实行为数据产生。这也就是“随机探索”机制的简化版。
零基础的同学如果觉得这两个逻辑讲起来不够“深”,可以补一个概念:EE机制,Exploitation(利用)和Exploration(探索)。利用是推用户喜欢的内容,探索是偶尔掺几条新内容看用户反应。那句话一说,导师立刻会觉得你懂行。
3.3 基于标签偏好与协同过滤的召回实现
召回部分我建议从简单有效的方案开始:标签偏好召回 + ItemCF协同过滤召回,两个渠道的结果做一次合并,再进排序。
标签偏好召回的逻辑是这样的:维护一张user_tag_preference表,记录用户对每个标签的偏好分。用户每次看视频超过一定时长(比如超过视频总时长的一半),就把这条视频的所有标签偏好分加1;如果秒划走,减0.5。偏好分越高,说明用户对这个标签越感兴趣。
召回时,从视频表里关联video_tag,按用户偏好分从高到低取候选视频,分数作为初步得分。下面是核心代码示意:
public List<Video> recallByTagPreference(Long userId, int limit) { // 1. 查询用户最近的行为日志(只看有效的正向行为) List<ViewLog> logs = viewLogMapper.selectRecentPositive(userId, 30); // 2. 统计标签权重 Map<Long, Double> tagWeight = new HashMap<>(); for (ViewLog log : logs) { List<Long> tagIds = videoTagMapper.selectTagIdsByVideoId(log.getVideoId()); for (Long tagId : tagIds) { tagWeight.merge(tagId, 1.0, Double::sum); } } // 3. 根据标签权重召回视频候选 if (tagWeight.isEmpty()) { return videoMapper.selectHotVideos(limit); } return videoMapper.selectByTagWeights(tagWeight, limit); }ItemCF协同过滤在毕设阶段不需要全量算,只算简化版:如果用户A喜欢了视频X,同时用户B也喜欢了视频X,那么用户B喜欢的其他视频,对用户A来说也可以作为候选。翻译成SQL和代码,就是查“偏好相似用户”再查“这些用户喜欢的视频”。
这一步的实现不建议写得太重,用定时任务每10分钟把相似度结果算一遍,存Redis,推荐时直接取。这样既能讲清楚协同过滤思想,又避免了实时计算巨大消耗。
3.4 实时热度计算与最终排序
候选集召回之后,视频列表还是乱的,需要一个统一的排序分。推荐系统的排序阶段本质上是把多个维度的特征加到一起打分。
一个适合毕设的排序公式如下:
final_score = 偏好分 * 0.5 + 热度分 * 0.3 + 新鲜度分 * 0.2
- 偏好分来自用户标签画像对视频的匹配程度。
- 热度分来自视频的播放、点赞、收藏的综合值。
- 新鲜度分:发布时间越近,分越高。可以用一个时间衰减系数计算,比如这个简单公式:新鲜度分 = 1 / ln(当前时间 - 发布时间 + 2)。
把三个分加权求和,最终排序,取前50条作为一次推荐流结果。同时利用Redis缓存这套结果,key设计成“recommend:user:{userId}:page:{page}”,过期时间设为10分钟,这样用户下滑翻页时,不用每次重新计算推荐分。
推荐结果缓存过期问题要小心。如果用户刷到一半就退出去,再次进来时推荐流如果完全变了,体验会有点怪。
4. 前后端实现:SpringBoot接口与Vue页面联调
4.1 推荐流接口设计与分页策略
推荐流接口要设计得让前端好对接。我的建议是:
GET /api/recommend/feed?page=1&size=10返回结构:
{ "code": 200, "data": { "list": [ { "videoId": 101, "videoUrl": "/static/video/xxx.mp4", "coverUrl": "/static/img/xxx.jpg", "title": "今天拍了一条猫", "authorName": "某某同学", "likeCount": 320, "hasLiked": false, "tags": ["萌宠", "日常"] } ], "hasMore": true, "nextPage": 2 } }前端用Vue3的滚动加载实现,滚动到底部时请求下一页。这个交互方式是短视频App的标配,实现起来也很简单:给页面容器绑定滚动事件,当scrollTop + clientHeight > scrollHeight - 200时,触发下一页加载。
分页这里注意一个细节:不要用传统偏移量分页的下拉刷新,而是用“游标”的概念。因为推荐流是动态变化的,如果用页码,用户翻到第3页时第1页的数据可能已经更新了,会出现重复视频。简单做法就是上面的page+size,但配合一个dedupe策略:前端把本轮会话已经展示过的videoId收集起来,请求下一页时带上这些ID,后端过滤掉。
4.2 管理端的视频审核与分类管理
内容管理后台是“短视频推荐与内容管理系统”的另一个重头戏,也是很多同学容易漏掉的部分。推荐流做得再好,管理端如果没有审核和上下架能力,系统的“管理”两个字就名存实亡。
管理端的技术栈我建议用Vue3 + Element Plus,因为表格、弹窗、表单这些管理场景Element Plus做得非常成熟。视频审核页面做成一个表格列表,每一行显示:视频封面、标题、作者、上传时间、状态,操作列放“通过”和“下架”按钮,这两按钮背后调用的接口是:
PUT /api/admin/video/{videoId}/status请求体里带上status(枚举值:0草稿、1待审核、2已发布、3已下架)。后端拿到请求后,先更新video表状态字段,然后清理Redis里对应的推荐缓存,这就是“状态变更后要触发缓存刷新”的意识。
分类管理页面就是简单的CRUD:新增分类、修改分类名称、删除分类。删除分类时要记得处理视频表外键引用,不能直接删,要先做“该分类下是否有视频”的校验。
4.3 视频上传与预处理链路
视频上传是整个系统里面坑比较多的环节,需要重点说明。前端上传可以直接用Element Plus的upload组件,但要注意几点:
第一,上传接口要单独设计,不要跟其他业务接口混在一起。我建议拆两个接口:一个负责上传文件并返回文件ID和URL,一个负责把视频内容和文件关联起来。
POST /api/upload/video // 传视频文件 POST /api/video/publish // 发布视频(填标题、分类、封面、视频文件ID)第二,视频文件需要做大小限制和类型校验。SpringBoot端可以配置:
spring: servlet: multipart: max-file-size: 100MB max-request-size: 100MB类型校验不要只依赖前端的accept属性,后端也要校验Content-Type是否是video/mp4、video/quicktime这些常见类型。
第三,视频封面可以做“前端上传后截取第一帧”,或者干脆做成“用户手动上传封面图”,因为服务端用Java做视频截图处理需要引入FFmpeg,复杂度会上升不少。毕设阶段,我的建议是直接让用户传封面图,开发量小,演示效果好。
4.4 前后端联调时最容易翻车的几个环节
前后端联调是整个项目里我见过同学耗时最多的阶段,问题往往集中在以下几个点:
跨域问题:前端页面9000端口,后端接口8080端口,隔着一道CORS。后端的解决方案是统一开启跨域配置,要么写一个CorsFilter,要么在Controller上加@CrossOrigin。建议全局配置一次:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600); } }图片和视频的URL直接打不开:本地存储的文件如果只存在磁盘路径里,前端访问不了。解决办法是配一个Nginx静态映射,把磁盘的upload目录映射成一个HTTP路径。Nginx的关键配置:
location /static/ { alias /data/app/upload/; expires 7d; }文件名的处理:前端上传的中文文件名,后端要重命名,避免乱码和路径穿越风险。用UUID + 原始扩展名是最稳的。
5. 常见问题与排查技巧实录
5.1 推荐结果不准:数据稀疏怎么治
我在实际指导过程中,遇到过的新手最常见的问题,就是推荐结果完全“不相关”。用户明明看了好几个萌宠视频,推荐流里却全是美女跳舞。排查下来,原因基本绕不开这三个:
第一,用户行为数据太少。解决方法是减少偏好统计的阈值,比如把“超过50%时长算正反馈”放宽到“超过30%且观看时间大于10秒就算”。第二,标签关联设计不合理。视频没有正确打标签,偏好标签匹配就无从谈起。第三,排序公式中热门分占比过高,个性化信号被稀释了。可以适当调整,把偏好分从0.5提升到0.6。
5.2 重复推荐与疲劳度
用户刷了十几次之后,发现推荐流里不断出现已经看过的视频。这个问题本质上是因为过滤阶段漏了“浏览历史”这一环。
解决方案有两个:一是在召回时SQL排除view_history表里已有的videoId;二是在排序后、返回前用Java内存过滤,用Set收集已看过的ID做排除。两者都做更稳。另外,可以把“最近3天内看过的视频”单独存Redis,key值为“user:seen:{userId}”,过期时间72小时,这样过滤查询很快。
5.3 并发下的缓存与性能
答辩现场如果老师要求看系统性能,并发问题是绕不开的话题。推荐计算如果每次都实时跑SQL联表,在数据量变大之后会越来越慢。我的建议是:把“排序结果”整体缓存到Redis,按userId维度存,10分钟过期。只有缓存过期时才重新计算。
热门视频列表也可以做缓存,比如“hot:videos”这个key,每天凌晨清理一次。用Redis的List或ZSet结构存储,查询时直接LRange取前50条,能扛住不小的流量。
5.4 答辩时的高频追问点与回应思路
每年答辩时,推荐系统被问得最多的问题就那几个,提前准备就不会慌。
“你这个推荐算法相比协同过滤有什么异同?”回应的思路是:协同过滤是经典的二阶段方案,我实现了ItemCF的简化版;同时,在工程落地时加了标签偏好、热度兜底和时间衰减,组成了一个混合推荐策略。这句话虽然在算法层面不够“硬核”,但能在工程层面展示自己的思考。
“如果数据量到千万级,你的推荐系统会怎么优化?”这种问题不是真让你改架构,而是看有没有扩展意识。回答可以提三个方向:用消息队列异步处理行为日志、引入专门的服务器存储用户向量并做近似检索、把热门推荐和实时推荐拆成两条链路分开部署。每一个都是加分项。
“你的推荐会不会导致信息茧房?”这个问题我觉得反而最好回答,因为你只要做了“随机探索”机制,就可以很自信地说:系统在排序时会保留一定比例的新内容位,保证用户能看到新类型的内容。
写在最后的心里话
这个项目带着我前前后后带过不少同学做完,每轮回头看都能有些新体会。如果让我再做一个短视频推荐系统,我一定在项目初期就把“日志采集”的代码写齐,而不是等推荐效果不行了再回头补行为数据。数据是推荐系统的燃料,这个道理做几次就懂了。
还想提醒一句:别想着把推荐算法一步到位做到大厂水平,那本来也不是一个毕设的目标。把链路跑通、把核心逻辑讲透、把工程细节做完整,这套系统和背后的思考过程,足以证明你的能力。
如果你正在纠结怎么做推荐流去重,或者卡在Vue端视频播放组件的封装上,欢迎按这个思路先把骨架搭起来。系统能转起来之后,哪怕算法调参再笨拙,你也已经跑赢大多数只做了管理系统的毕业设计了。