SpringBoot+Vue短视频推荐系统毕设实战:用户画像与协同过滤
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环境能不能稳定跑了WindowWatermark状态后端这些概念你答辩时能不能用两分钟讲清楚集群资源够不够万一演示那天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 MapLong, Double calcUserSimilarities(Long targetUserId) { ListLong targetTags userTagMapper.selectTagIdsByUser(targetUserId); MapLong, Double simScores new HashMap(); ListLong allUserIds userMapper.selectAllIds(); for (Long otherId : allUserIds) { if (otherId.equals(targetUserId)) continue; ListLong otherTags userTagMapper.selectTagIdsByUser(otherId); // 求交集大小 SetLong inter new HashSet(targetTags); inter.retainAll(otherTags); // 求并集大小 SetLong 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上传完成待审核上传成功 → 01审核通过0 → 12审核驳回0 → 23已下架1 → 34转码完成可播放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脚本把账号、视频、行为数据全部预置好。到时候答辩老师让你操作你就不会因为现场手忙脚乱地造数据而冷场——这个小细节是真的能救场的那种实用。