毕业设计选题年年都有新花样,但“基于协同过滤的化妆品推荐系统”这个题目,在 Spring Boot 技术栈的加持下,确实是近年市面上出现频率很高的一个方向。它不只是一个普通的 CRUD 管理系统,核心价值在于“推荐算法”的落地:怎么把用户的历史行为、商品属性这些数据,变成一套真正能猜中用户心思的智能导购逻辑。这篇文章我就以实际开发者的视角,把标题里隐含的设计思路、算法选型、双维度协同过滤的实现细节,以及实际开发中踩过的坑,拆开揉碎了讲清楚。无论你是正在选毕业设计题目的在校生,还是想转行做推荐系统方向的后端开发,这篇内容都能让你少走不少弯路。
1. 项目整体设计与思路拆解
1.1 为什么选 Spring Boot 作为推荐系统的载体
很多人一听到“推荐系统”,第一反应是 Python、机器学习框架这类技术栈。但作为一个后端开发方向的毕业设计,Spring Boot 反而是更合适的载体。Spring Boot 的核心优势在于快速搭建稳定的 Web 服务:内置 Tomcat、自动配置、生态成熟,拿来做一个前后端分离的推荐系统后端,效率比想象中高很多。
我在实际开发中体会到,推荐系统本质上是由三个部分组成:数据层(用户行为数据、商品数据)、算法层(协同过滤计算相似度、生成推荐列表)、应用层(暴露 RESTful 接口给前端调用)。Spring Boot 在这三个层面都能很好地履行职责:数据层用 JPA 或 MyBatis 操作 MySQL,算法层用 Java 编写协同过滤工具类,应用层用 Controller 封装接口。整个项目结构清晰,答辩的时候也能把系统架构讲得很明白。
标题里提到的“化妆品推荐系统”,其实是个非常有代表性的业务场景。化妆品的 SKU 众多,品类跨度大(护肤、彩妆、香水、工具),用户的选择成本高,推荐系统的价值空间大。而且化妆品的用户行为数据有明显的长尾效应:少数爆款商品贡献大部分销量,但长尾商品也需要通过推荐触达用户。这种场景下,协同过滤算法比单纯的基于内容的推荐更能挖掘用户潜在偏好。
1.2 协同过滤 vs 基于内容的推荐:为什么选前者
在做技术选型时,我认真对比过基于内容的推荐(Content-Based)和协同过滤(Collaborative Filtering)两条路线。基于内容的思路是分析商品本身的属性标签,把“口红”推荐给浏览过“口红”的用户。逻辑简单,但没有惊喜度,推荐来推荐去都是同质化商品。
协同过滤的思路则完全不同:它不分析商品本身是什么,而是通过用户的行为数据建立关联。用户A和用户B都买过同一支口红,那么系统就会认为A喜欢的其他化妆品,B也可能喜欢。这种方式能挖掘出用户自己都意识不到的潜在偏好。举个例子,很多用户会在购买粉底液的同时购买美妆蛋,这两者在商品属性上关联不大,但对用户行为数据来说,相关性极高。协同过滤能捕捉到这种跨品类的关联规则,这是基于内容推荐做不到的。
结合标题里“协同过滤”这个关键词,我确定了核心算法路线:UserCF(基于用户的协同过滤)和 ItemCF(基于物品的协同过滤)双轨并行,再通过融合策略输出最终推荐结果。不过在实际开发中,单一的协同过滤算法会面临冷启动、数据稀疏这些问题,所以标题里提到“改进协同过滤”“双维度协同过滤”——这是整篇设计的灵魂所在,后续章节我会详细拆解。
1.3 双维度协同过滤:这个“双维”到底指什么
标题里“商品双维度协同过滤”这个表述,我在最初设计时做了两种解读:一种是“用户维度 + 商品维度”的双维度协同过滤,另一种是“商品属性维度 + 用户行为维度”的双维度相似度计算。最终项目中,我两种思路都用了。
第一种“用户 + 商品双维度”,是指系统同时运行 UserCF 和 ItemCF 两个算法,各自生成一个推荐列表,最后通过加权融合输出最终结果。UserCF 更适合用户量小、兴趣变化快的场景;ItemCF 更适合商品数量大、用户稳定的场景。化妆品推荐系统的特征决定了这两个算法都不能舍弃,但两者的权重配比需要调参。
第二种“商品属性 + 用户行为”的双维度,是指在计算商品相似度时,不仅仅依赖“购买过A也购买过B”的共现次数,还结合商品的品类、功效、适用肤质这些属性特征。这个改进的意义在后文会有详细说明,这里先提一个概念:纯行为协同过滤在面对新品时,由于没有行为数据,相似度矩阵里全是零,整个推荐就崩了。引入商品属性维度后,新品也能通过属性相似度进入推荐列表。
2. 协同过滤算法核心解析:原理与改进方向
2.1 UserCF 与 ItemCF 的数学表达
先过一遍基础原理。基于用户的协同过滤(UserCF)核心逻辑是:对目标用户 u,找出与他行为最相似的 K 个用户,把这 K 个用户喜欢过但 u 没有交互过的商品,按相似度权重排序推荐给 u。
用户相似度通常用余弦相似度或皮尔逊相关系数计算。余弦相似度的公式是:
设用户 u 和用户 v 的评分向量分别为 R_u 和 R_v,相似度 sim(u, v) = (R_u · R_v) / (|R_u| × |R_v|)。
在化妆品场景里,向量维度是商品,取值可以简化为 0/1(买过或没买过),也可以用评分(1-5星)。但实际问题在于,用户和商品数量都很多时,向量高维稀疏,余弦相似度的分母可能为0。我采用的改进策略是加入共同评分商品数量的惩罚项:只有共同交互过的商品数量超过阈值时,才计算相似度,否则直接设为0,避免大量无意义的计算。
ItemCF 的逻辑则相反:先计算商品两两之间的相似度,然后根据用户历史偏好的商品,去推荐“与这些商品相似”的其他商品。商品 i 和商品 j 的相似度,最经典的计算方式是:
sim(i, j) = |N(i) ∩ N(j)| / sqrt(|N(i)| × |N(j)|)
其中 N(i) 是购买过商品 i 的用户集合。这是 Jaccard 相似度的一个变体,能有效避免爆款商品过度主导相似度结果。
2.2 冷启动问题的三种解法
冷启动是协同过滤的头号痛点。新用户没有任何行为数据,UserCF 根本找不到相似用户;新商品没有交互记录,ItemCF 的相似度矩阵里它是孤立点。我在项目中设计了三个层次的解法:
第一层:新用户冷启动。注册时引导用户选择关注的化妆品品类、肤质类型、价格区间。这些偏好标签不在协同过滤算法里用,而是先走基于内容的推荐,给用户推送对应品类的热门商品。等用户产生足够行为数据(点击、收藏、加入购物车、购买)后,再切换为主要走协同过滤。
第二层:新商品冷启动。新商品上架时,后台录入商品属性标签(品类、功效、适用肤质、品牌定位、价格带),通过属性相似度找到最相似的几个老商品,先把新商品插入到 ItemCF 的相似度矩阵里,让它在推荐列表中拥有初始曝光机会。
第三层:混合推荐策略。API 层做熔断和降级:如果协同过滤模块计算出来的推荐列表为空(极端稀疏场景),自动切换到“热门商品 + 品类偏好”兜底策略,保证接口永远有数据返回。
2.3 改进点:融合商品属性的相似度计算
对于 ItemCF,我做了核心改进。传统的商品相似度只基于用户行为共现,对于“SKU 多、上新快”的化妆品行业来说不太够用。我引入了属性相似度的计算:每个商品用一个 one-hot 编码的属性向量表示,比如一款产品可以表示为:
[品类=精华, 功效=保湿, 功效=美白, 肤质=干性, 价格带=200-400]
商品 i 和商品 j 的属性相似度 attr_sim(i, j) = 共同命中的属性数 / 总属性数。
最终的相似度矩阵采用线性融合:
final_sim(i, j) = α × behavior_sim(i, j) + (1 - α) × attr_sim(i, j)
调参过程中,α 取 0.7 左右效果最好。太高了,新品依然出不来;太低了,行为数据的信息没有被充分利用。这个 α 不能拍脑袋定死,我在后台做了一个动态配置接口,方便在测试阶段反复调整。
2.4 用户相似度计算的工程优化
实际落地 UserCF 时,会遇到一个很现实的问题:用户量上了几百上千以后,两两计算相似度的复杂度是 O(n²),这是不可接受的。我在项目中采用了“倒排索引 + 共现矩阵”的优化手段:先从行为表中找出每个用户交互过的商品集合,然后遍历每个商品,把购买过该商品的用户两两配对,累加共现次数。这样只需要遍历商品数量级别的数据,而不是用户数量的平方。
还有一个过滤策略:在计算用户相似度之前,先进行一次粗筛选,只保留“共同购买过至少2个商品”的用户对进入细粒度相似度计算,其余直接舍弃。这个阈值让计算量大幅下降,而且推荐精度反而提高了——因为只共同买过1个商品的用户,相似度信号太弱,引入后大多是噪声。
3. Spring Boot 系统架构与数据层设计
3.1 整体技术选型与分层架构
推荐系统的后端采用经典的分层架构:Controller 层负责接收前端请求,Service 层承载业务逻辑和推荐算法调用,Repository 层操作数据库。技术选型上,Spring Boot 2.7.x + MyBatis-Plus + MySQL 8.0 + Redis,前端配合 Vue + Element UI 做管理后台,用户端走微信小程序或 H5。
Redis 在整个系统中扮演的角色比想象中重要。用户-商品交互矩阵和商品相似度矩阵,如果每次请求都实时从 MySQL 读出来计算,响应时间会爆炸。我的做法是:离线计算任务定期把相似度矩阵写入 Redis,线上推荐接口直接从 Redis 读取,缓存命中后推荐响应时间能压到 50ms 以内。
用户行为数据写入采用异步方式。用户每次点击商品、收藏、加购,前端调用行为上报接口,后端 Controller 接住请求后直接放入线程池或消息队列,立即返回成功,由异步任务批量写入 MySQL。这样保证了推荐系统的主链路不受行为数据写入的拖累。
3.2 数据库表结构设计要点
化妆品推荐系统的数据表比普通管理系统多了几张核心表。用户表、商品表、品类表这些基础表不赘述了,重点说几张与推荐算法直接相关的表:
用户行为记录表(user_behavior):字段包括用户ID、商品ID、行为类型(浏览/收藏/加购/购买)、行为分值、创建时间。推荐算法读取这张表生成协同过滤的用户-商品交互矩阵。
商品属性表(product_attr):这张表的存在是“改进协同过滤”的基石。商品ID、属性类型(品类/功效/肤质/价格带/品牌)、属性值。每个商品可以有多条属性记录,属性向量由这张表组装生成。
相似度结果表(similarity_result):商品ID_A、商品ID_B、相似度值、维度类型(行为相似或属性相似)。这张表是算法计算后的成品,离线任务算完后写入,推荐接口直接查。
这张表设计的关键点是:以“行为类型 + 行为分值”的方式归一化用户行为强度。我采用的规则是:浏览记1分、收藏记3分、加购记5分、购买记10分。这个分值不是拍脑袋定的,而是参考了电商行业的通用经验:购买行为的权重至少是浏览的5倍以上,否则推荐结果会被“随便看看”的噪声数据带偏。
3.3 核心接口设计规范
推荐系统对外暴露三类接口。第一类是行为上报接口,前端在用户浏览商品详情页、点击收藏、提交订单等时机异步上报行为数据。第二类是推荐查询接口,传入用户ID、场景类型(首页推荐、相似商品推荐、购物车关联推荐)和返回数量,后端返回商品列表带推荐理由。第三类是后台管理接口,用于配置算法参数(相似度融合权重、推荐列表商品数量上限)。
推荐接口的响应数据结构我做了统一约定:返回商品ID、商品名称、主图URL、价格、推荐分数、推荐理由。推荐理由字段是加分项,前端展示“因为你看过XX,推荐这款XX”之类的文案,能明显提升点击率。
4. 双维度协同过滤的落地实现
4.1 第一维度:基于用户行为的协同过滤实现
现在进入核心代码落地环节。第一维度是用户行为维度的协同过滤,我拆成三个步骤来实现。
第一步,从数据库加载用户行为数据,构建原始交互矩阵。使用一个 Map 结构,键是用户ID,值是另一个 Map,记录该用户对所有商品的行为分值。这里的关键是:只加载行为分值大于0的记录,避免把负反馈(比如用户明确不喜欢的商品)混入矩阵。
第二步,计算用户之间的相似度矩阵。用倒排索引的方式,先遍历商品的用户列表,对每个商品的用户列表进行两两配对,累加共现次数和共现得分。由于 UserCF 需要的是“相似用户集”,所以这步产出的结果集是每个用户对应的 Top N 相似用户列表。
第三步,为目标用户生成推荐列表。遍历相似用户的交互商品,排除目标用户已经交互过的商品,按“相似度权重 × 行为分值”加权累加,取总分最高的前 K 个商品。K 的取值我一般设 20,返回给前端时再根据业务规则过滤掉售罄商品和下架商品。
4.2 第二维度:基于商品属性与行为的融合相似度
第二维度融合了行为相似度和属性相似度,核心在于属性向量的构建和融合相似度计算。商品属性 from product_attr 表,加载后转成属性集合的二进制编码或 one-hot 表示。
属性向量空间的设计要控制维度,不能把每个属性值都当作独立一维,否则维度爆炸。我把属性划分为五个维度:品类(眼影/口红/精华/面霜等)、功效(保湿/美白/抗皱/控油等)、适用肤质(干皮/油皮/混油/敏感肌)、价格带(0-100/100-300/300-500/500+)、适用场景(日常通勤/约会派对/敏感期修复)。
每个维度内部做 one-hot 编码,但维度之间不交叉。最终属性向量是一个固定长度的稀疏向量,通过计算两个向量的余弦相似度或杰卡德相似度得到属性相似度。
然后执行加权融合:final_sim(i, j) = α × behavior_sim(i, j) + (1 - α) × attr_sim(i, j)。其中 behavior_sim 直接采用 ItemCF 算出的行为相似度。
这里要注意,行为相似度矩阵的稀疏度远高于属性相似度矩阵(因为每个商品都有属性,但很多商品没有共同购买记录),所以融合时需要对缺失值做处理——如果两个商品没有行为共现记录,behavior_sim 记为0,但最终相似度仍可能由属性维度支撑起来。这正好解决了冷启动商品的问题。
4.3 实时推荐与离线计算的职责划分
推荐系统工程落地时,最常见的一个误区是纠结于算法复杂度,却忽略了架构层面的设计。我做了一个清晰的职责划分:
离线计算部分:每天晚上凌晨跑定时任务,从 MySQL 增量读取当天的用户行为数据,更新用户相似度矩阵和商品相似度矩阵,计算双维度融合相似度,把最终的 Top N 推荐列表写入 Redis。为什么算 Top N 而不是全量矩阵?因为全量矩阵在用户量达到几万级别时存储量巨大,而真正用到的只是每个用户的前 N 个推荐结果。
实时计算部分:用户白天产生行为时,通过行为上报接口同步更新用户近期行为数据,生成一个“实时兴趣向量”,与 Redis 中的离线推荐列表做简单融合:实时兴趣命中的商品排序置顶,未命中但相似度高的商品排在后面。这种“离线为主 + 实时为辅”的架构,既保证了响应速度,又能捕捉用户当下的兴趣变化。
4.4 推荐理由的生成策略
为了让推荐结果看起来不“硬”,我在接口中加入了推荐理由。推荐理由不是算法硬性输出的,而是根据推荐类型做模板映射。如果推荐商品来自 ItemCF 的相似度匹配,理由就是“与你浏览过的XX相似”;如果来自 UserCF 的相似用户偏好,理由就是“和你品位相似的用户也喜欢XX”;如果来自热门兜底,理由就是“最近大家都在买XX”。
这个细节在毕业设计中非常加分。答辩时老师通常会问“推荐结果的解释性如何”,推荐理由功能恰好回答了这个痛点——推荐系统不只是黑盒输出,还要能让用户理解“为什么推荐这个”。
5. 常见问题与排查技巧实录
5.1 相似度矩阵偏斜:爆款商品绑架推荐结果
我测试时发现一个很典型的问题:某款口红是爆款,购买记录非常多,ItemCF 计算出来,几乎所有热门商品和它都有较高的相似度。推荐结果里,爆款商品的相似商品霸占了大部分位置,用户真正需要的小众商品反而排不上来。
排查思路是打印出爆款商品与其他商品的相似度分布,发现行为相似度在爆款场景下已经失真。解决办法有两个:一是做相似度归一化,将每行相似度除以该行最大值,压制过热商品的扩散效应;二是在最终推荐打分时加入商品热度惩罚因子,推荐分数乘以一个递减的权重函数,避免高热度商品无限挤压长尾商品。两个策略叠加后,推荐列表的多样性明显提升。
5.2 用户行为稀疏:交互矩阵大量为零
毕业设计的数据量级通常不大,学生用户往往只有几千条模拟数据。在这种稀疏场景下,协同过滤的效果可能远不如预期,大量用户的推荐结果基本是“热门商品集合”,失去了个性化。
排查后发现,关键在于行为数据本身不够丰富。解决办法是增加模拟数据的生成逻辑:不光是随机购买记录,而是按照用户的注册标签(品类偏好、价格带)来生成行为数据,保证每个用户有10条以上的有效交互记录。同时算法层面降低相似用户的 K 值(从20降到10),因为数据稀疏时,K值太大会引入大量弱关联用户。
5.3 推荐接口响应过慢:实时计算阻塞主线程
早期版本,推荐接口在处理请求时需要实时从 MySQL 加载用户行为数据做计算,用户量稍涨后接口响应时间直接从 50ms 飙升到 3 秒。排查用 Arthas 的 trace 命令追踪调用链路,发现瓶颈在数据库查询和相似度计算的 CPU 密集型操作上。
解决方案分两步:一是在 Redis 中缓存相似用户列表和相似商品列表,推荐接口直接从缓存取数,不穿透到数据库;二是引入异步线程池,行为上报接口不再同步处理数据,而是放入队列延迟处理,让主接口只做轻量级读写。改造后,推荐接口99%的请求在80ms内完成,稳定性和响应速度都达标了。
5.4 双维度权重敏感:α 参数调优的经验值
融合相似度的权重 α,我在测试中发现它对推荐结果影响极大。α = 1.0 时,系统退化为纯行为协同过滤,新品完全无法推荐;α = 0.3 时,属性维度占据主导,推荐结果面向所有用户高度类似,个性化程度下降。
最终采用的自适应策略是:按商品类型的“行为数据丰富度”动态调整 α。对新品和长尾商品,行为数据少,α 自动降低到 0.5;对爆款商品,行为数据丰富,α 拉高到 0.85。动态调整后,整体推荐效果提升明显。
5.5 冷启动用户推荐空列表的兜底策略
测试场景中有大量新注册用户,行为记录为空,协同过滤推荐结果为空。早期版本直接返回空列表,前端展示为空白,体验极差。
解决思路是构建三层兜底:第一层,按用户注册时填写的肤质、品类偏好推荐对应属性相似的商品;第二层,推荐全站近7日热度最高的20款商品;第三层,如果前面两层都空(极少见),则按商品ID排序返回默认列表。兜底策略让推荐接口永远不会返回空数据。
6. 毕业设计答辩与项目扩展经验
6.1 答辩时最容易被打动的技术亮点
毕业设计答辩,老师通常更关注你自己动手解决的实际问题,而不是背了多少概念。我复盘过自己项目里最有价值的几个技术亮点,值得在答辩中展开:
第一个亮点是双维度融合相似度的设计。我从“纯行为协同过滤无法处理新品冷启动”这个痛点出发,引入属性相似度做补充,并且设计了可动态调节的融合权重,这属于对经典算法的改进和创新,比单纯实现一个协同过滤更有说服力。
第二个亮点是离线 + 实时双通道推荐架构。离线定时任务计算相似度矩阵,实时行为流更新用户兴趣,两者通过 Redis 衔接。这个设计让推荐系统具备了生产级别的性能思维,是很好的加分项。
第三个亮点是推荐理由生成策略。推荐理由让算法结果从黑盒变成可解释的,在系统演示环节能直观地让用户看到推荐系统的“思考过程”,非常有说服力。
6.2 项目未来可扩展的方向
一个项目做完了,还能怎么扩展,也是答辩时老师常问的开放式问题。我的规划是三个方向:
第一,加入深度学习排序模型,用用户行为序列作为输入,做点击率预估,替换现在的“推荐分数加权排序”。第二,引入知识图谱,把化妆品的成分、功效、适用肤质的关系建图,实现解释性推荐和基于知识路径的推荐。第三,做多目标优化,不只考虑点击率,还要关注推荐列表的多样性和新颖度,避免信息茧房。
6.3 给即将动手的同学几点避坑建议
最后分享几条实操层面最关键的避坑建议:
数据坑:不要等到代码写完才开始造数据。推荐算法非常依赖数据质量,建议第一天就开始用脚本生成模拟的“用户画像 + 行为序列”,并让数据在分布上尽量贴近真实场景——热门商品集中、长尾商品稀疏、用户兴趣有聚类特征。
算法坑:推荐算法代码建议单独写一个模块,不要和 Controller、Service 混在一起。毕业设计的代码后期要反复调试算法参数,独立模块方便使用单元测试和 JUnit 测试单独评估算法效果。
性能坑:即使只有几百个用户,Resis 缓存和离线计算的架构也要做出来,因为这是你答辩时说“性能优化”的底气。等答辩完再补这个架构,成本会大得多。
我的经验是,这类项目的核心不在 Spring Boot 本身,而在于你能不能把算法和工程有条理地结合,让推荐结果既“有道理”又“有响应速度”。抓住双维度协同过滤这个核心创新点,加上合理的系统架构和充分的测试数据,完成度和答辩效果都不会差。