基于协同过滤与Spring Boot的个性化音乐推荐系统实战解析

发布时间:2026/10/19 16:35:36
基于协同过滤与Spring Boot的个性化音乐推荐系统实战解析 每次到了毕业设计季总有人跑来问“推荐系统到底怎么做才能既出效果又能过答辩”。这次我拆解的正是一个基于协同过滤算法的个性化音乐推荐系统后端用Spring Boot搭建算法走的是经典的协同过滤路线。这个项目不追求花哨的深度学习而是把推荐系统的核心链路完整做了一遍用户行为采集、相似度计算、评分预测、Top-N推荐、前端展示、离线评测该有的环节一个不少。无论你是计算机专业的应届生还是想快速上手推荐系统的开发者这篇文章都能让你少走不少弯路。我把项目的设计思路、算法原理、关键代码、踩坑记录全部整理出来可以直接照着复现。1. 项目概述与核心设计思路1.1 这个项目到底解决了什么问题很多同学选“音乐推荐系统”作为毕业设计本质上是想找一个技术覆盖面广、难度可控、演示效果又直观的题目。纯做增删改查的管理系统显得单薄直接上深度学习模型又容易把战线拉太长协同过滤恰好卡在中间它既能让评委看到你对算法有真正的理解又能在一套标准化的Web项目里跑通完整的业务闭环。这个系统解决的核心问题很简单当一个用户在音乐平台里听歌、收藏、下载之后系统怎么根据他和其他用户的行为相似性自动找出他可能喜欢但还没听过的歌曲。更直白一点系统要做三件事记录用户行为、计算人与人或歌与歌的相似关系、生成个性化推荐列表。拿我做过的这个项目举例它包含Web端和后台管理端两套界面。普通用户登录后可以看到“猜你喜欢”推荐模块能看到系统推荐的歌曲列表以及推荐原因管理员端则可以维护歌曲信息、查看用户行为日志、手动干预推荐结果。整个项目跑起来之后演示效果非常直观答辩的时候也很好讲清楚推荐链路。1.2 为什么是协同过滤算法而不是深度学习这是我在项目设计阶段第一个要回答的问题。很多人一听到“推荐系统”就先想到深度神经网络、Embedding这些东西但冷静下来想一个本科毕设项目最需要的是什么是能在有限时间内把数据流跑通、把算法讲透、把结果展示清楚。协同过滤的优势在于三点。第一它不依赖复杂的特征工程只需要用户对物品的行为矩阵这大大降低了数据准备的门槛。第二它的核心思想“物以类聚、人以群分”非常容易向非技术背景的人解释答辩时评委很容易听懂。第三它的代码实现是可控的自己手动实现相似度计算和评分预测也就两三百行比调深度学习框架更能在答辩时展示你的工程能力。深度学习当然更好但毕设项目的时间和算力都不允许把效果调到令人满意的程度。所以我选择了基于用户的协同过滤UserCF为骨架同时兼容基于物品的协同过滤ItemCF作为对照。两套算法放在同一个系统里可以通过配置切换这本身就是项目的一个加分项。2. 推荐算法核心原理与数据模型设计2.1 用户行为如何变成评分数据协同过滤的第一步是把零散的用户行为转换为“用户-歌曲-评分”的三元组。真实场景下用户不会给每首歌打分所以就要用隐式反馈来估计。我在项目里设计了五类行为播放、收藏、下载、搜索点击、完整听完。每类行为赋予不同的权重规则如下行为类型行为分值备注播放1.0一次播放记1分收藏3.0主动行为权重高下载4.0强意图权重最高搜索后点击2.0有明确倾向完整听完1.5表示内容匹配最终用户对某首歌曲的评分就是这些行为分值的累加。设计这个权重时我有两个原则一是主动行为比被动行为更有价值二是防止刷播放量导致的数据失真。有一点值得注意评分矩阵的阈值不要设置得太死。如果用户播放三次就累加3分而收藏一次也是3分这会让模型误认为播放三次和收藏一次等价。所以我给行为次数做了上限同一个用户对同一首歌的播放次数超过5次之后就不继续累加避免狂热用户主导推荐结果。2.2 相似度计算的两个关键公式协同过滤的灵魂就是相似度计算。在用户协同过滤里我们要算的是用户之间的相似度在物品协同过滤里算的是歌曲之间的相似度。项目里我同时实现了几种计算方法最常用的是皮尔逊相关系数和余弦相似度。皮尔逊相关系数的公式是sim(u, v) Σ(u_i - μ_u)(v_i - μ_v) / sqrt(Σ(u_i - μ_u)² * Σ(v_i - μ_v)²)其中 μ_u 表示用户u对所有歌曲评分的平均值。这个公式的好处是它扣除了用户自身打分尺度的影响。比如一个用户习惯给高分另一个习惯给低分只要他们的偏好趋势一致相关性还是会比较高。余弦相似度公式则是sim(u, v) (u · v) / (|u| * |v|)两种方式各有适用场景。如果评分矩阵比较稠密皮尔逊更好如果评分矩阵很稀疏余弦相似度对缺失值的容忍度更高。我这个项目里的评分矩阵确实非常稀疏大部分用户只听了几十首歌所以实际测试下来余弦相似度在数据稀疏时更稳定。2.3 评分预测与Top-N推荐生成算完相似度之后就到了预测评分环节。对于一个目标用户u和一首他未听过的歌曲i预测分数的公式是pred(u, i) μ_u Σ( sim(u, v) * (r_vi - μ_v) ) / Σ( |sim(u, v)| )r_vi 表示相似用户v对歌曲i的真实评分。这个公式的本质是找到和目标用户最像的一群用户看他们对这首歌的评分然后做加权平均同时用评分均值消除不同用户打分尺度差异。预测完成后把所有未听过的歌曲按预测分排序取前N个就是Top-N推荐列表。这里的N在系统里是可以配置的默认取20。代码实现时我做了两件额外的事。一是把用户已经听过的歌曲强制过滤掉不能让推荐列表出现用户听过的歌。二是给推荐结果附上“推荐理由”比如“因为你和用户A的相似度为0.76所以为你推荐这首歌”。这个细节答辩时非常加分它体现了你对推荐系统可解释性的理解。2.4 数据库表结构设计实战数据库是整个项目的地基我设计了五张核心表用户表、歌曲表、用户行为表、相似度缓存表、推荐结果表。用户表字段包括用户ID、用户名、密码、昵称、注册时间。歌曲表字段包括歌曲ID、歌名、歌手、专辑、时长、歌曲URL。行为表是算法最重要的数据来源字段包括行为ID、用户ID、歌曲ID、行为类型、行为分值、行为时间。相似度缓存表用来存储计算好的用户相似度结果避免每次推荐都重新跑一遍全量计算。推荐结果表则保存每个用户最近一次的Top-N推荐列表这样前端打开页面时不用实时请求算法接口直接读表即可响应速度能快一个量级。这个设计的核心逻辑是“离线计算、在线读取”。推荐系统不能每次请求都临时算一遍因为用户多了以后相似矩阵会非常大。把计算过程离线化结果落库在线只做查询和展示这是工程上非常实用的做法。3. Spring Boot项目落地从零实现推荐功能3.1 创建工程与依赖配置技术选型上我用了Spring Boot 2.7作为后端框架MyBatis-Plus操作数据库MySQL 8.0存储数据前端用Thymeleaf模板引擎加BootstrapJDK版本1.8。这套组合足够稳定也是目前毕设项目里最主流的搭配。创建工程的依赖配置核心是pom.xml我贴一下关键部分dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency /dependencies配置文件里最需要注意的是数据库连接的参数。我当时调试了大半天发现MySQL 8.0的驱动名是com.mysql.cj.jdbc.Driver不是老版本的com.mysql.jdbc.Driver同时需要指定时区参数serverTimezoneAsia/Shanghai否则启动时会报时区错误。这里有个小建议如果你的MySQL版本是5.7驱动名要改回老的如果你是8.0以上就用新的驱动。这个错误在毕设阶段非常常见问题不大但很影响心情。3.2 相似度计算模块代码实现推荐算法的核心我单独封装在一个RecommendService里其中计算用户相似度这一步代码是这样写的public MapInteger, Double calcUserSimilarity(int userId) { // 1. 获取该用户的评分数据 MapInteger, Double userRatings ratingMapper.selectByUserId(userId); // 2. 获取所有其他用户的评分数据 ListRating allRatings ratingMapper.selectAll(); MapInteger, MapInteger, Double allUserRatings new HashMap(); for (Rating r : allRatings) { allUserRatings .computeIfAbsent(r.getUserId(), k - new HashMap()) .put(r.getSongId(), r.getScore()); } // 3. 逐个计算目标用户与其他用户的余弦相似度 MapInteger, Double simMap new HashMap(); for (Map.EntryInteger, MapInteger, Double entry : allUserRatings.entrySet()) { int otherUserId entry.getKey(); if (otherUserId userId) continue; double sim cosineSimilarity(userRatings, entry.getValue()); if (sim 0) simMap.put(otherUserId, sim); } // 4. 按相似度降序排列取前K个相似用户 return simMap.entrySet().stream() .sorted(Map.Entry.Integer, DoublecomparingByValue().reversed()) .limit(10) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); }注意第4步的取前K个相似用户这是一个非常关键的细节。如果不限制相似用户数量所有相似度大于0的用户都会参与评分预测那些相似度只有0.01的用户会引入大量噪声拉低推荐质量。限制前K个一般K10到20既减少计算量又提升推荐精度。余弦相似度的具体实现也不复杂private double cosineSimilarity(MapInteger, Double a, MapInteger, Double b) { double dotProduct 0.0; double normA 0.0; double normB 0.0; // 找到两个用户都评过分的歌曲 SetInteger commonSongs new HashSet(a.keySet()); commonSongs.retainAll(b.keySet()); if (commonSongs.isEmpty()) return 0.0; for (Integer songId : commonSongs) { dotProduct a.get(songId) * b.get(songId); } for (double v : a.values()) normA v * v; for (double v : b.values()) normB v * v; if (normA 0 || normB 0) return 0.0; return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); }两个用户共同评分过的歌曲越多这个相似度就越可信。如果共同歌曲只有一个即使相似度很高也不能轻信。所以我做了一层保护共同歌曲数少于2时强制认为相似度为0。3.3 推荐接口与前端联动算法层写好后就是标准的三层架构Controller接收请求、Service处理业务、Mapper读写数据库。推荐接口的Controller代码大致长这样RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/{userId}) public Result getRecommendList(PathVariable Integer userId) { ListRecommendItem list recommendService.getTopNRecommend(userId, 20); return Result.success(list); } }前端页面通过Ajax调用这个接口拿到推荐列表渲染到页面上。为了让演示效果更好推荐模块分成两块展示一块叫“猜你喜欢”这是基于UserCF算出来的结果另一块叫“相似歌曲推荐”这是基于ItemCF算出来的点进任意歌曲详情页就可以看到。整个流程串起来后效果是新用户注册完登录首页系统先展示热度榜单作为冷启动推荐用户听了十几首歌之后再刷新首页“猜你喜欢”列表就会发生变化。这个“从无到有、从有到准”的变化过程非常适合现场演示。3.4 离线评测怎么证明推荐有效毕设答辩最怕评委问“你怎么证明你的推荐是有效的”。所以我给项目加了一个离线评测模块用精确率Precision和召回率Recall来做量化评估。具体做法是把用户的历史行为按时间分成训练集和测试集前80%的行为用来计算相似度、生成推荐后20%的行为用来验证。如果推荐列表里的歌曲命中测试集中的歌曲就算一次有效推荐。评估指标计算公式Precision 命中数量 / 推荐列表长度 Recall 命中数量 / 测试集歌曲数量我做了三组对比实验纯UserCF、纯ItemCF、混合推荐。混合推荐策略是取两个算法的结果按比例合并比如60%的用户协同过滤结果加上40%的物品协同过滤结果。实测下来在Demo数据集上混合推荐的效果略好于单一算法虽然差距不大但值得写进论文作为实验分析。4. 毕设阶段的常见问题与排查实录4.1 冷启动新用户和新歌曲怎么办这是推荐系统里最经典的问题也是答辩时几乎必被问到的问题。一个新用户刚注册没有任何行为数据协同过滤拿什么去算相似度拿不到因为行为矩阵里他那一行全是空的。我的处理方案是分级推荐。对新用户系统使用基于热度榜的推荐策略就是推荐全局播放量最高、收藏数最多的歌曲相当于“新人先看排行榜”。等用户产生了一定量的行为数据我设置阈值是15条记录系统才切换到协同过滤算法。对于新入库的歌曲也是一样的道理没有任何用户行为协同过滤无法为它打分。我把这部分歌曲暂时归入“最新上架”模块展示积累一定行为数据后再进入推荐候选池。这样做虽然简单但能让系统的推荐逻辑在冷启动状态下也说得通。4.2 数据稀疏导致推荐结果“全热门”项目实测过程中我发现一个很让人头疼的现象用户量少、行为数据稀疏的时候算出来的推荐列表基本全是热门歌曲个性化程度很差。原因也很简单大家共同评分的歌就那么几首热门的算出来相似用户全都因为这些热门歌而聚在一起。针对这个问题我做了三个改进。第一把热门歌曲的评分做降权处理公式是score / (1 log(1 playCount))让热门歌曲的分值不会因为曝光量过高而碾压小众歌曲。第二对行为矩阵做了平滑处理给未观测到的行为填上很小的置信度值减小矩阵的稀疏性。第三在生成推荐结果时人为加入一定比例的“长尾歌曲”保证推荐列表的多样性。这三个改进效果很明显。加入长尾策略之前推荐列表里前五名永远都是全网最火的几首歌加入之后用户能看到一些他可能真的感兴趣但平时不会被推荐的小众歌曲。这个点写进毕业论文里是很加分的创新点。4.3 性能优化实时计算的瓶颈与缓存方案刚开始实现的时候推荐接口是在用户请求时实时计算的。用户量小的时候没问题但当我把模拟数据量扩大到100个用户、500首歌曲、将近6万条行为记录之后接口响应时间从几百毫秒飙升到几秒甚至十几秒。问题出在相似度计算每次都要全表扫描用户行为数据。解决思路就是前面提到的“离线计算、在线读取”我写了一个定时任务每30分钟跑一次全量推荐计算把每个用户的Top-N推荐结果写入推荐结果表同时把用户相似矩阵缓存到Redis中。在线请求只做查询不再触发计算。改造之后接口响应时间稳定在100毫秒以内。这个优化思路其实也是工业界推荐系统的标准做法写进论文里会显得你的项目不只是一个课程作业而是有工程深度。4.4 常见运行问题速查表我在实际跑通这个项目的过程中整理了下面这些高频问题按出现概率排序问题现象根本原因解决方法启动时报数据库连接错误驱动名写错或时区参数缺失检查JDBC URL的serverTimezone参数登录后推荐列表为空用户行为数据不足触发冷启动保护先播放/收藏几首歌再刷新页面相似度全是0用户间没有共同评分歌曲增加模拟行为数据或改用ItemCF推荐结果全是热门歌长尾策略未生效检查热门降权代码是否被注释掉定时任务不执行Scheduled 注解所在类未被Spring扫描确认类放在主包或其子包下页面加载卡顿查询推荐结果表没有走索引给 user_id 字段加普通索引这里面最容易被忽略的是索引问题。数据量小的时候全表扫描感觉不出来一旦行为数据达到几万条不带索引的查询就会变成性能瓶颈。我当时给用户行为表加了一个联合索引(user_id, song_id)查询速度提升非常明显。5. 项目扩展方向与我的实操心得5.1 从毕设到小产品还能加什么如果做完基础功能后还有时间我的建议是优先做两个扩展方向。第一个是加入基于物品的协同过滤融合策略让系统同时支持“猜你喜欢”和“相似歌曲推荐”两个模块这在答辩时是一个很好的演示亮点。第二个是增加用户画像模块可以根据用户常听的曲风、歌手、年代等属性生成简单标签展示在个人中心页面上。这个功能看似简单但它让系统看起来更像一个真正的商业产品而不只是一个算法Demo。5.2 答辩时容易被追问的问题提前准备答辩问题比到时候现想靠谱得多。这个项目里我预判了五个高频追问方向为什么选协同过滤而不是深度学习冷启动怎么解决评分矩阵稀疏怎么办推荐结果怎么评估算法复杂度高时怎么优化。这几个问题的答案在前面的章节里其实都已经详细展开过了只要你能用自己的话把原理讲清楚评委基本不会再为难你。5.3 我踩过最值得分享的几个坑最后讲点实在的。整个项目做下来最浪费时间的一个坑是过分纠结算法的“最佳参数”。我当时花了很多天调整相似用户数量K和评分权重试图让推荐效果达到所谓“最优”。后来才明白毕设项目里算法的参数只要在合理范围内即可真正决定项目评价高低的是整个系统的完整度和逻辑自洽性。另一个坑是要尽早把“定时任务离线计算结果”的架构想清楚。一开始图省事直接在Controller里调用算法结果演示现场数据量一大页面转圈圈转了半天场面一度非常尴尬。后来改成离线计算加定时刷新演示就顺畅多了。如果你正准备做类似的毕设我的建议是先死磕算法原理再把工程链路跑通最后留两周时间专门做演示优化和答辩准备。不要一上来就埋头写代码先把整个推荐链条画清楚后面会顺畅很多。这个项目的扩展性很强改成电影推荐、文章推荐、课程推荐都很容易核心的协同过滤逻辑完全不用动只需替换数据模型和页面展示层就好。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询