ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+Spring实现电影周边商城:协同过滤推荐算法实战与毕设指南

Java+Spring实现电影周边商城:协同过滤推荐算法实战与毕设指南 做 Java 毕设最怕什么怕题选得太“普通”答辩时被导师一句“这套系统网上开源太多了”问住。今天要聊的这套电影周边商城系统主技术栈是 Java Spring核心算法挂了协同过滤推荐恰好是那种既有完整电商业务闭环、又有算法亮点的选题。它要解决的核心问题很明确用户在琳琅满目的电影 IP 周边里不知道该买什么平台通过“猜你喜欢”把候选商品推到用户面前同时把购买、评分、收藏这些行为数据反哺成下一轮推荐依据形成一个越用越准的闭环。这篇文章适合三类人看打算用 Java 做毕设的学生、想给自己小商城项目增加推荐功能的后端开发者以及准备面试时想拿“协同过滤”当实战谈资的求职者。1. 项目到底做了什么功能架构与选题逻辑1.1 选题逻辑为什么是“电影周边”而不是普通商城很多同学做毕设第一个想法就是“做个商城系统”但普通商城有两个问题一是商品是通用商品没有用户画像上的天然聚类推荐算法很难产出“看起来合理”的结果二是项目同质化严重答辩时很难展示差异化亮点。换成电影周边商城就不一样了。电影周边商品天然带 IP 属性看同一部电影的观众在周边偏好上存在高度相似性比如喜欢《流浪地球》的人往往对同 IP 的模型、徽章、主题 T 恤都感兴趣。这种业务特性天然契合协同过滤算法的假设——相似偏好的人会喜欢相似商品。另一个实际好处是数据规模可控。毕设项目不需要海量数据几千条用户评分、几十个周边商品就能让推荐算法跑出肉眼可见的效果。要知道推荐算法最怕数据稀得像沙漠而电影周边这种强 IP 归属的商品域用户行为相对集中算法效果容易在演示环节呈现出来。1.2 技术栈选型Spring 生态怎么搭主流毕设要求的是 SSMSpring Spring MVC MyBatis或者 Spring Boot 二选一。如果你学校有明确框架要求就用 Spring MVC MyBatis 那套经典分层如果没指定我建议直接用 Spring Boot开发效率高出一个量级省下的时间全部用来打磨推荐模块。我自己做类似项目时推荐的一套配套是这样的JDK1.8 或 11兼容性最好别一上来就上 17某些老 MyBatis 版本会出幺蛾子框架Spring Boot 2.x Spring MVC MyBatis / MyBatis-Plus数据库MySQL 5.7 或 8.08.0 注意驱动版本要对应前端服务端渲染用 Thymeleaf或者前后端分离用 Vue 接口对接这个看个人熟悉程度权限登录拦截自己写 HandlerInterceptor 就够了没必要上 Spring Security答辩时反而难解释Spring 的 IOC 容器、AOP、事务管理这些老生常谈面试又总爱追着三级缓存问但放在毕设项目里更重要的其实是把这些基础能力真正用起来——事务保证订单和扣库存一致AOP 统一记录操作日志拦截器做登录校验这些才是评判一个 Java 项目“扎实不扎实”的核心。1.3 功能模块的两大作战面整套系统从使用层面分成前台商城和后台管理两大块。前台商城面向普通用户核心流程是这个链路注册登录 → 浏览商品 / 搜索 / 按分类筛选 → 查看商品详情与评分 → 加入购物车 → 生成订单并支付模拟 → 收货后对商品评分。用户所有和商品发生交互的行为都会被记录到行为表和评分表里这些数据就是推荐算法的“原料”。后台管理面向管理员能力边界也比较清楚商品分类管理、商品上下架与库存维护、订单状态流转管理、用户列表查询、以及一个专门用来演示算法效果的“推荐数据查看”模块。最后这个模块相当加分答辩时可以直接打开页面展示某个用户的历史行为和他看到的推荐结果之间的关联比口头讲算法生动得多。2. 协同过滤推荐算法落地实操2.1 UserCF 和 ItemCF毕设项目该怎么选协同过滤有两个主流分支基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。UserCF 的思路是找“和我口味相近的人”把他们买过而我没买过的商品推荐给我。ItemCF 的思路则是“我之前喜欢的东西有没有相似款”找到与历史偏好商品相似度高的其他商品推给我。电影周边商城我强烈建议选 ItemCF。原因有三条第一商品数量比用户数量稳定得多物品相似度矩阵可以离线算好在线请求时直接查结果性能压力小。第二电商场景下 ItemCF 的推荐理由好解释“你收藏了《流浪地球》的运载车模型所以推荐同 IP 的 MOSS 摆件”这种话术一眼就能看懂。第三用户的短期兴趣波动在商城场景里比较明显ItemCF 对实时行为更敏感比如用户刚浏览了某部电影的海报立刻就能补一批同 IP 周边。学术上这两类算法没有优劣之分但放到一个要上线演示的电商项目里ItemCF 的工程友好度要高太多了。2.2 行为数据转换成评分的几种姿势协同过滤的输入是“用户对物品的评分矩阵”。但电商系统里并不是每个用户都会老老实实打分所以得把显式行为和隐式行为统一换算成评分。我实际项目里采用过这样一张映射表行为类型转化评分说明5 星评价5.0用户主动给高分权重最高4 星评价4.0正常好评购买4.5愿意花钱是最强信号加入购物车4.0购买意向明确收藏3.0兴趣信号偏中等浏览详情1.5弱信号避免噪声过大这个映射关系可以写在枚举类里集中管理将来想调权重只改一处。另外强调一点用户对同一商品重复产生的行为取最高分一次不能让刷浏览把评分刷上去要在写入时做去重处理。2.3 核心代码余弦相似度与推荐生成物品相似度计算最常用的是余弦相似度。可以把每个物品被所有用户打过分数的向量看成高维空间里的一个点两个物品越“同向”就越相似。工程实现上有个经典优化不要直接双层循环遍历所有商品对而是用“用户-商品”的倒排索引先找出每个用户评分过的商品集合再对集合内的商品两两组合累加点积。这样相似度矩阵只包含真正存在共同用户的商品对计算量小一个数量级。/** * 计算两个商品评分向量的余弦相似度 * key 为 userIdvalue 为用户对该商品的评分 */ public double cosineSimilarity(MapLong, Double itemA, MapLong, Double itemB) { double dotProduct 0.0; double normA 0.0; double normB 0.0; for (double score : itemA.values()) { normA score * score; } for (double score : itemB.values()) { normB score * score; } for (Map.EntryLong, Double entry : itemA.entrySet()) { Double scoreB itemB.get(entry.getKey()); if (scoreB ! null) { dotProduct entry.getValue() * scoreB; } } if (normA 0.0 || normB 0.0) { return 0.0; } return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); }拿到相似度矩阵后推荐生成的核心逻辑就是“加权求和 归一化 过滤已购”public ListProduct recommendByItemCF(Long userId, int topN) { // 1. 当前用户的历史评分 MapLong, Double userRatings ratingDao.selectRatingsByUserId(userId); if (userRatings null || userRatings.isEmpty()) { // 冷启动用户直接走热门商品兜底 return productDao.selectHotProducts(topN); } // 2. 物品相似度矩阵实际项目中由定时任务离线构建好 MapLong, MapLong, Double itemSimMatrix itemSimService.getSimilarityMatrix(); // 3. 对候选商品累计加权得分 MapLong, Double scoreMap new HashMap(); MapLong, Double simSumMap new HashMap(); for (Map.EntryLong, Double rating : userRatings.entrySet()) { Long ratedItem rating.getKey(); double rateScore rating.getValue(); MapLong, Double sims itemSimMatrix.get(ratedItem); if (sims null) { continue; } for (Map.EntryLong, Double simEntry : sims.entrySet()) { Long candidateItem simEntry.getKey(); // 排除用户已经打过分、买过的商品 if (userRatings.containsKey(candidateItem)) { continue; } double sim simEntry.getValue(); scoreMap.merge(candidateItem, sim * rateScore, Double::sum); simSumMap.merge(candidateItem, sim, Double::sum); } } // 4. 归一化排序取 Top-N return scoreMap.entrySet().stream() .sorted((a, b) - Double.compare( b.getValue() / simSumMap.get(b.getKey()), a.getValue() / simSumMap.get(a.getKey()))) .limit(topN) .map(entry - productDao.selectById(entry.getKey())) .collect(Collectors.toList()); }这段代码把算法主干封装在recommendByItemCF一个方法里Controller 层只需要收到 userId返回 List 渲染到页面。考题如果深问推荐原理你能从“倒排索引”“归一化”“过滤已购”三个词展开讲基本就是加分回答。要注意这里是简化版余弦相似度实战里更稳的是“修正余弦相似度”——先对每个用户减去他的平均评分消除部分用户天生爱打 5 分、有些人只打 3 分的尺度差异。改法不复杂在向量里先做中心化再调余弦即可。2.4 冷启动与数据稀疏的兜底策略推荐系统绕不开冷启动。新用户没有行为数据ItemCF 直接返回空列表页面难看得要命。我采用的策略是分三档新用户查商品表的sales字段按销量倒序再按上架时间加权推热门商品和新品。新商品给商品打上分类标签用户在浏览某分类时把同类目商品随机插入推荐列表的缝隙里让新商品获得曝光机会积累首批评分。老用户但行为极少只取他最近一次浏览或收藏的商品查相似矩阵至少凑够 6 个候选商品如果还凑不够就用热门商品填充到 6 个。这套“算法推荐 热门兜底 分类填充”的混合策略是线上电商都在用的工程方案写进毕设论文里也算实打实的业务理解。3. 数据库设计与核心模块实现细节3.1 表结构设计让推荐算法好读数据数据库设计决定了推荐模块写起来是享受还是折磨。我的核心原则是用户行为单独建表订单和订单项分离评分表加唯一索引。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, nickname varchar(50) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id bigint(20) DEFAULT NULL, name varchar(100) NOT NULL, description text, price decimal(10,2) NOT NULL, stock int(11) DEFAULT 0, cover varchar(255) DEFAULT NULL, sales int(11) DEFAULT 0, status tinyint(4) DEFAULT 1, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE rating ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, product_id bigint(20) NOT NULL, score double NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;rating表单独拆出来的原因很直接推荐算法每次要按用户把所有评分一次性捞进内存如果评分埋在订单表里查询逻辑要绕好几层算法代码也会被业务代码污染。建独立行为表behavior记录浏览、收藏、加购也是同一个道理一表一职责算法模块读起来毫不费劲。3.2 用户端核心链路购物车与订单购物车和订单模块是电商系统的“脸面”代码结构上要稳住。购物车设计成cart表字段是 user_id、product_id、quantity、checked 状态。生成订单时要同步做三件事检查库存、扣减库存、创建订单记录。这三步必须包在同一个事务里不然会出现“订单生成了库存没扣成功”的脏数据。订单表我建议带上一个order_no唯一业务单号用时间戳加随机数生成别用自增主键直接当订单号给用户看一是暴露真实业务量二是丑。订单状态用 tinyint 存0 待付款、1 已付款、2 已发货、3 已完成、4 已取消界面展示时再用枚举映射成中文状态。3.3 后台管理模块与推荐数据可视化后台管理不复杂但也不能只做“增删改查”。我给后台多加了两个和推荐相关的页面第一个是“用户行为查询”输入 userId 就能看到该用户的浏览、收藏、评分、购买流水。第二个是“推荐结果对比”同一用户分别查看“算法推荐结果”和“热门商品榜单”两列列表页面截图放进论文对比章节简直利器。这种设计让评委一眼看到推荐算法确实在起作用而不是光靠嘴上讲。4. 实操过程中的坑与排错实录4.1 推荐结果为空或不准从哪排查这个问题在我调试阶段出现过太多次排查顺序很重要先看评分数据再看相似度矩阵最后看过滤条件。先确认用户行为数据真的写进表里了。很多页面埋点漏了“浏览详情”行为或者评分按钮没接到 Dao 层导致rating表里只有一条测试数据。算法拿一条评分数据是算不出相似物品的。再看相似度矩阵。我用一个临时接口把矩阵串成 JSON 打到控制台检查热门商品之间有没有非零相似度。如果热门 MOSS 摆件和 FF14 周边完全不共现说明这两个商品在用户行为上没有交集算法无米下锅。最后检查过滤逻辑。最容易出的问题是userRatings.containsKey(candidateItem)没用上导致推荐结果把用户已经买过的东西再推一遍看着像 bug 实际是逻辑漏了。4.2 性能优化相似度矩阵不能现场算刚开始我图省事每次请求都现场构建相似度矩阵数据量小还好数据一旦过千条商品、上万条评分页面响应直接掉进 3 秒大关体验直接崩盘。正确做法是写一个 Spring 定时任务凌晨 2 点用Scheduled(cron 0 0 2 * * ?)离线构建矩阵放到服务内存里。白天用户请求时只查内存 Map响应时间压到 100ms 以内。另一个优化是稀疏矩阵存储哈希表只存非零项。商品对没有共同评分用户就不会进入矩阵10 万个商品实际共现的商品对可能只有几千个内存完全能扛住。答辩被问到“数据量大怎么办”可以说后续引入 Redis 缓存矩阵、计算层下沉到离线数据仓库这些点都能体现思考深度。4.3 答辩高频问题清单与答法毕设答辩问到推荐模块来来回回就是这几个问题提前把答案理顺为什么用协同过滤不用基于内容的推荐答基于内容推荐要人工维护大量商品特征标签电影周边的描述字段不规范加工成本高协同过滤纯靠用户行为学习偏好冷启动靠热门兜底更适合用户行为数据丰富的电商场景。冷启动怎么做答三层兜底新用户推热门和新品新商品通过分类插入获得曝光行为极少的用户用最近行为相似商品补足推荐位。推荐效果怎么评估答离线阶段用留一法每次留出一条用户真实行为当测试集算 Top-N 精确率和召回率线上阶段看推荐位商品点击率做一个简单的 A/B 对比。一个加分的收尾承认系统的局限是算法只用了显式评分和简单行为映射后续可以接入 ALS 矩阵分解、引入用户画像或者做实时特征管道。不要说自己系统完美能清晰说出下一步优化方向反而是稳妥的答辩策略。4.4 版本与部署的翻车点最后聊几个环境相关的坑。Spring Boot 2.x 默认搭配 MyBatis 没问题但如果你用 3.x需要 JDK 17 起步且部分老版本 mybatis-spring-boot-starter 不兼容直接 NoClassDefFoundError 教你做人。Maven 依赖冲突也是高频问题关键是用mvn dependency:tree查看版本树锁定统一的 Spring Boot 版本别手动塞一堆版本号。部署到 Tomcat 时注意打包方式要改成 war 并重写启动类如果直接用 jar 方式部署到外部 Tomcat 会报找不到主类。数据库连接池建议用 HikariCPSpring Boot 默认就是它别换成 DBCP参数调起来温度完全不同。写在最后的一点经验这项目我自己从零走了一遍最深的一个体会是一定要先把推荐闭环单独跑通再去铺商城其他模块。最简单的验证方式往 rating 表手动插入两个用户对同一批商品的评分调接口看推荐结果里有没有出现预期的相似商品打印出相似度矩阵核对一遍数值对不对。这一步通了后面所有模块都是锦上添花。磨刀不误砍柴工推荐模块是这套系统的灵魂宁可商城少两个页面也要保证算法能跑出让评委眼前一亮的推荐结果。还有一个小技巧给推荐接口写一个测试类用 JUnit 跑一个固定数据集的断言保证后面改代码不会顺手把推荐逻辑改坏。这个习惯放到真实工作里一样受用自动化测试兜底改代码不慌。
返回列表