ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot协同过滤鲜花商城推荐系统设计与实现

Spring Boot协同过滤鲜花商城推荐系统设计与实现 简介这是一套面向Java全栈开发初学者与课程设计者的鲜花电商推荐系统实战项目基于SpringBootVue实现前后端分离架构聚焦协同过滤算法在真实业务场景中的落地应用。资源包含完整可运行源码、MySQL建库脚本及详细功能模块覆盖买家含个性化推荐、购物车、订单全流程、卖家商品管理、订单处理与管理员多维度数据统计、商户与用户管控三类角色适用于毕业设计、实训项目或推荐算法实践学习。压缩包共818个文件9.48MB其中Java后端逻辑81个.java、Vue前端页面42个.html39个.css84个.js、Layui组件样式含多个.css与图标字体文件及数据库配置1个.sql构成核心交付内容。已有41人学习下载提供开箱即用的工程结构、清晰的角色权限划分与真实电商推荐闭环便于快速理解协同过滤在用户行为数据上的建模与集成逻辑。 接手这个项目的时候我第一反应是“又是一个电商CRUD”。但真正把数据库表设计完、把协同过滤算法跑通之后我才意识到这个项目的价值点根本不在增删改查而在推荐链路和交易闭环的衔接方式。市面上能跑的Spring Boot商城很多但大多数压根没有推荐逻辑或者是写死的“猜你喜欢”——也就是按销量倒序排个列表那不能叫推荐系统。这个项目——“基于Spring Boot协同过滤鲜花商城推荐系统源代码数据库”——解决的核心问题很明确在一个真实的B2C鲜花交易场景里如何用协同过滤算法根据用户的历史行为数据生成个性化的商品推荐列表并且和商品、购物车、订单体系完整打通。它是典型的课程设计/毕业设计级别的完整工程但完整度比一般demo高不少。适合三类人看一是要交数据库课程设计或毕业设计的在校生二是想学习Spring Boot 推荐算法落地的Java开发三是想给自家小商城加推荐模块但不知道怎么下手的人。我先说结论这项目能跑通算法不是玩具数据库设计是亮点但有几个坑你必须提前知道。下面我按从整体到细节的顺序把这个项目的技术构成、表结构设计、算法实现、代码组织、部署踩坑全拆一遍。1. 项目定位与技术选型为什么是Spring Boot为什么是协同过滤先把这个项目的技术选择逻辑讲透不然你照着代码敲完问自己“为什么用这个不用那个”答不上来面试或答辩时很容易被问住。1.1 Spring Boot在电商demo中的统治力这个项目采用Spring Boot作为后端基础框架不是偶然。Spring Boot的自动配置和起步依赖机制能让你在极短时间内把Web层、数据访问层、事务管理、参数校验这些基础能力全部搭建起来。对于这种“商城 推荐系统”的工程来说Spring Boot能让你把精力集中在核心业务逻辑——也就是协同过滤这部分而不是浪费在配置XML、管理Bean生命周期这些重复劳动上。具体到版本选择项目用的是Spring Boot 2.x系列。这里我要特别提一句如果你现在自己从零搭不要无脑选3.x。Spring Boot 3.x要求JDK 17而且javax命名空间换成了jakarta很多老教程和老代码会直接报编译错误。这个项目基于2.x对应的JDK 8/11都能跑兼容性最好。我实测过JDK 8 Spring Boot 2.7.x MyBatis-Plus 3.5.x这套组合稳定性很高基本不会出幺蛾子。1.2 鲜花场景为什么需要协同过滤很多人会问一个商城而已为什么非要上协同过滤我直接按销量排序不行吗问得好。如果你卖的是标品——比如螺丝钉、A4纸——那按销量排序完全够用用户要什么自己搜就行。但鲜花不一样它有非常强的非标属性和场景属性用户经常是“不知道买什么但知道送给谁”——送妈妈、送女朋友、送老师、探病、乔迁。这个需求特征是搜索框解决不了的因为用户根本不知道怎么描述自己要的花束。这时候推荐系统的价值就出来了基于“和你相似的用户买了什么”来推荐本质上是把其他用户在类似场景下的选择经验复制给当前用户。这就是协同过滤的用武之地。它不依赖商品内容分析比如颜色、花材种类只依赖用户行为数据谁买了什么、谁给什么商品打了高分实现起来相对简单且在用户行为数据积累到一定量之后效果相当不错。1.3 整体技术架构一览整个项目采用前后端不分离的经典单体架构技术栈如下层次技术选型作用前端Thymeleaf Bootstrap HTML/CSS/JS页面渲染商城界面后端Spring Boot 2.x整体框架、IoC、Web MVC持久层MyBatis-PlusORM简化数据库操作数据库MySQL 5.7 / 8.0数据存储核心数据源推荐算法自研协同过滤基于用户UserCF为主生成个性化推荐构建工具Maven依赖管理与构建这个架构的好处是上手门槛低。没有微服务、没有消息队列、没有复杂的分布式事务一个普通的Java开发者拿到源代码后两小时内就能把项目跑起来。而推荐算法部分虽然实现不算复杂但算法逻辑是完整的、可解释的不是那种糊弄人的假推荐。2. 数据库表设计8张表如何支撑起一套推荐交易闭环对于这个项目来说数据库设计质量决定了协同过滤算法能不能落地。我见过太多课程设计项目的表结构要么只有用户表和商品表两张表要么一张表塞了几十个字段完全没法用。这个项目的表设计真正做到了“最小但完整”。2.1 核心业务表用户、商品、分类首先是用户表user包含用户ID、用户名、密码MD5加密、昵称、性别、手机号、注册时间等字段。这套用户体系很常规但足以支撑用户维度的协同过滤。商品表flower是灵魂表之一字段设计上覆盖了推荐算法需要的核心信息id商品IDname花束名称type_id商品分类ID关联flower_type表price价格image图片地址description详情描述sales销量stock库存商品分类表flower_type则非常简单就是维护花束的分类信息比如玫瑰、百合、向日葵、混搭花束、绿植盆栽这些。2.2 交易链路表购物车、订单、订单项交易闭环靠三张表完成购物车表cart_item记录用户加入购物车的商品字段包括用户ID、商品ID、数量、加入时间。这张表有唯一约束用户ID 商品ID避免同一商品重复加购。订单表orders记录订单主信息包括订单号、用户ID、总金额、订单状态待付款/已付款/已发货/已完成/已取消、下单时间、收货信息等。订单项表order_item记录订单中包含的具体商品包括订单ID、商品ID、商品名称快照、商品图片快照、单价、数量。这里有一个细节值得注意订单项表冗余了商品名称和图片快照。为什么因为商品信息是可能变动的改名、换图、下架但订单历史不能跟着变否则用户查看历史订单时会看到混乱的信息。这是电商系统设计的常见做法但这个级别的课程设计项目里很多同学根本不会考虑这个点。2.3 推荐核心表评分表和推荐结果缓存表这两张表是整个项目最关键的创新点。评分表rating记录用户对商品的评分行为字段包括用户ID、商品ID、评分值1-5分、评分时间。它加了一个唯一约束(user_id, flower_id)保证一个用户对一个商品只能有一条评分记录后续重复评分就走更新逻辑。这张表就是协同过滤算法计算的数据源。推荐结果缓存表recommendation专门存算法算出来的推荐结果字段包括用户ID、推荐商品ID、推荐分数、推荐时间。为什么需要这张表因为协同过滤算法涉及全量用户之间的相似度计算计算开销不小不可能每次用户请求推荐位时都实时算一遍。所以项目采用离线计算 在线读取的模式用一个定时任务定期跑算法把每个用户的Top-N推荐结果写入这张表当用户访问首页或商品详情页时直接从这张表按用户ID查询速度快得多。这个设计思路非常聪明理解了这个你就理解了工业级推荐系统的核心缓存策略。3. 协同过滤核心实现从相似度计算到Top-N推荐算法部分是整个项目最有含金量的地方。我把它拆成几个环节每个环节配合代码和计算过程来讲解。3.1 UserCF还是ItemCF为什么在这个项目里选UserCF协同过滤分两大类基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。UserCF找到和你兴趣相似的用户把这些用户喜欢的、而你没买过的商品推荐给你。核心是用户相似度。ItemCF找到和你买过的商品相似的其他商品推荐给你。核心是物品相似度。这个项目最终以UserCF为主我认为这个决策是符合业务逻辑的。原因有二。第一鲜花消费有明显的社交属性和群体偏好迁移给女朋友买花的人和给老婆买花的人行为模式高度相似跨用户推荐能挖出这种迁移关系。第二项目初期商品数量远小于用户数量计算物品相似度矩阵的复杂度和空间占用都不高但问题在于同类花束比如红玫瑰和粉玫瑰相似度太高容易出现“你买过红玫瑰我再推荐粉玫瑰”这种低价值推荐。不过项目也不是完全不用ItemCF——在详情页的“看了又看”模块可以采用简单的同分类推荐作为补充。主推UserCF辅以ItemCF思路两者结合效果最理想。3.2 算法核心步骤拆解UserCF的完整计算流程如下第一步构建评分矩阵。从rating表查出所有用户的评分记录构造成一个Map结构userId - (flowerId - rating)。实际上项目用的就是这个结构。第二步计算用户相似度。项目采用余弦相似度公式为sim(u, v) Σ(rui × rvi) / (√Σ(rui²) × √Σ(rvi²))其中rui是用户u对商品i的评分。举个实际例子假设用户A对商品1评分4分、对商品2评分5分用户B对商品1评分5分、对商品3评分3分。那么分子 4×5 5×0 20 分母 √(4²5²) × √(5²3²) √41 × √34 ≈ 6.40 × 5.83 ≈ 37.31所以 sim(A, B) ≈ 20/37.31 ≈ 0.536。分数越接近1说明两个用户口味越一致。项目代码里用了一个双重循环遍历用户对计算每对用户之间的相似度然后存储到Map里。第三步选取K个最近邻居。对每个用户按相似度从高到低排序取前K个相似用户作为邻居。K值默认取5这个参数是可配置的。K值太小推荐结果不稳定K值太大会引入很多相似度不高的用户拉低推荐精度。我实测下来K5到K10之间效果都还行数据量小的时候K5更合适。第四步生成推荐候选集并打分。对于当前用户u遍历他的K个邻居用户v找出v评分过、但u没有行为记录的商品。然后计算加权预测分score(u, i) Σ(sim(u, v) × rvi) / Σ|sim(u, v)|这个公式的本质是看邻居们对商品i的评分用自己和邻居的相似度作为权重进行加权平均。最后按分数降序排列取Top-N默认取6个写入推荐结果缓存表。3.3 核心代码逻辑展示我简化一下项目里推荐算法核心类的关键代码让大家看看具体实现方式这是去掉了一些边界判断的精简版但核心逻辑完全一致Service public class RecommendService { Autowired private RatingMapper ratingMapper; Autowired private RecommendationMapper recommendationMapper; /** * 为用户生成Top-N推荐结果 */ public void generateRecommendations(int userId, int k, int topN) { // 1. 构建评分矩阵userId - (flowerId - rating) MapInteger, MapInteger, Double ratingMatrix loadRatingMatrix(); if (!ratingMatrix.containsKey(userId)) { return; // 该用户没有评分数据跳过冷启动 } MapFlower, Double recommendMap new HashMap(); // 2. 计算当前用户与其他所有用户的余弦相似度 MapInteger, Double similarityMap new HashMap(); MapInteger, Double currentUserRatings ratingMatrix.get(userId); for (Map.EntryInteger, MapInteger, Double entry : ratingMatrix.entrySet()) { int otherUserId entry.getKey(); if (otherUserId userId) continue; MapInteger, Double otherUserRatings entry.getValue(); double similarity cosineSimilarity(currentUserRatings, otherUserRatings); similarityMap.put(otherUserId, similarity); } // 3. 按相似度排序取前k个最近邻居 ListMap.EntryInteger, Double sortedNeighbors similarityMap.entrySet().stream() .sorted((e1, e2) - Double.compare(e2.getValue(), e1.getValue())) .limit(k) .collect(Collectors.toList()); // 4. 从邻居的评分中生成候选集计算加权预测分 for (Map.EntryInteger, Double neighbor : sortedNeighbors) { int neighborId neighbor.getKey(); double sim neighbor.getValue(); if (sim 0) continue; // 相似度为0或负数时无意义 MapInteger, Double neighborRatings ratingMatrix.get(neighborId); for (Map.EntryInteger, Double itemEntry : neighborRatings.entrySet()) { int flowerId itemEntry.getKey(); double rating itemEntry.getValue(); // 过滤掉当前用户已买过/评过分的商品 if (currentUserRatings.containsKey(flowerId)) continue; recommendMap.merge(flowerId, sim * rating, Double::sum); } } // 5. 归一化按分数排序取Top-N ListMap.EntryInteger, Double sortedRecommend recommendMap.entrySet().stream() .sorted((e1, e2) - Double.compare(e2.getValue(), e1.getValue())) .limit(topN) .collect(Collectors.toList()); // 6. 写入推荐结果缓存表 for (Map.EntryInteger, Double rec : sortedRecommend) { Recommendation recommendation new Recommendation(); recommendation.setUserId(userId); recommendation.setFlowerId(rec.getKey()); recommendation.setScore(rec.getValue()); recommendationMapper.insert(recommendation); } } /** * 余弦相似度计算 */ private double cosineSimilarity(MapInteger, Double vec1, MapInteger, Double vec2) { double dotProduct 0.0; double norm1 0.0; double norm2 0.0; // 遍历第一个向量累加点积和本身模长 for (Map.EntryInteger, Double entry : vec1.entrySet()) { double v1 entry.getValue(); norm1 v1 * v1; Double v2 vec2.get(entry.getKey()); if (v2 ! null) { dotProduct v1 * v2; } } // 计算第二个向量的模长 for (double v2 : vec2.values()) { norm2 v2 * v2; } // Math.sqrt(norm1) * Math.sqrt(norm2) 不为0时计算 return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } }这段代码我拆开讲一下几个细节ratingMatrix是核心数据结构所有用户对所有商品的评分都放在这个嵌套Map里。这里用Java的HashMap而不是数据库临时表是因为计算相似度涉及高频随机访问内存里操作比数据库join快几个数量级。过滤已评过分商品这一步不能省否则会把用户买过的商品又推荐一遍体验极差。归一化操作Double::sum累加后再除权重和在上面代码中为了简洁没写全但实际项目里记得要除以每个邻居相似度的总和否则评分高的用户对结果影响过大。3.4 定时任务调度离线计算与在线读取有了算法什么时候跑也是关键。项目里用的是Scheduled注解实现定时任务Component public class RecommendTask { Autowired private RecommendService recommendService; /** * 每天凌晨2点执行全量推荐计算 */ Scheduled(cron 0 0 2 * * ?) public void runAllRecommendations() { ListUser allUsers userMapper.selectList(null); for (User user : allUsers) { recommendService.generateRecommendations(user.getId(), 5, 6); } } }为什么凌晨2点跑因为商城系统白天订单和评分行为频繁如果算法和交易抢数据库资源会影响正常购物。凌晨流量低跑全量推荐最合适。跑完结果写进recommendation表用户访问时秒出结果。这里还要注意一个细节先清空再插入。每次跑推荐前要把该用户昨天的推荐记录删掉再插入今天的新结果。不然旧数据残留推荐位永远是过期内容。项目代码里如果没做这一步你拿到后需要自己补上。4. 商城主流程和推荐模块的衔接从商品列表到购物车订单推荐系统再强如果它是孤立的无法融入商城流程那也只是个演示算法的小工具不叫“系统”。这个项目里推荐模块和商城交易体系的衔接点我认为做得比较用心。4.1 商品浏览与分类检索商城首页提供商品列表、分类筛选、商品搜索。这些都是传统CRUD操作MyBatis-Plus的QueryWrapper就能搞定。比如按分类查询QueryWrapperFlower wrapper new QueryWrapper(); wrapper.eq(type_id, typeId); wrapper.orderByDesc(sales); ListFlower flowers flowerMapper.selectList(wrapper);但要注意用户走的每一步浏览行为都要留痕——只有留下行为数据推荐算法才有原料。项目里浏览记录会异步写入rating表的隐性评分浏览计为3分加入购物车计为4分购买计为5分。这块如果你拿到源码后没有建议自己补上否则推荐系统全靠用户手动打分太依赖用户自觉。4.2 首页推荐位“猜你喜欢”和详情页“看了又看”首页的“猜你喜欢”区域就是推荐结果的主要出口div classrecommend-section th:if${recommendations ! null !recommendations.isEmpty()} h3猜你喜欢/h3 div classrow div classcol-md-3 th:eachrec : ${recommendations} div classcard stylewidth: 18rem; img th:src${rec.image} classcard-img-top alt... div classcard-body h5 classcard-title th:text${rec.name}花束名称/h5 p classcard-text th:text${¥ rec.price}价格/p a th:href{/flower/detail/ ${rec.id}} classbtn btn-primary查看详情/a /div /div /div /div /divController层对应逻辑是先从recommendation表查出当前用户的推荐商品ID再查flower表补全商品信息然后传给前端渲染。如果用户没有推荐记录冷启动用户则按销量Top6兜底。商品详情页则可以放两个推荐模块一个是“看了又看”基于同分类的热门商品另一个是“买了还买”可以基于ItemCF思路查购买过该商品的用户还买过什么。这个属于锦上添花有这个模块答辩时多一个话术亮点。4.3 购物车与订单行为数据反哺推荐模型购物车功能依然常规加入购物车、修改数量、删除、批量结算。结算页面生成订单和订单项订单状态流转待付款 - 已付款 - 已发货 - 已完成。这个环节和推荐系统的关系在于订单完成之后一定要触发评分注入逻辑。用户购买某个商品说明对这个商品有明确偏好应该立即写入或更新rating表评分记录比如设为5分。这样下次跑推荐算法时新行为就能参与计算。如果不去反哺用户买再多次花推荐位永远是一潭死水。5. 评分体系与冷启动处理推荐系统的“燃料”从哪里来协同过滤推荐系统有一个很经典的说法Garbage in, garbage out。训练数据质量决定推荐效果上限。这个项目的评分数据来源设计值得单独拿出来讲。5.1 显式评分和隐式评分两种方式都要支持显式评分在商品详情页或订单完成后让用户主动打1-5星。代码上就是往rating表插入或更新一条记录。这个容易实现但现实里主动打分的用户比例很低所以不能只依赖这种方式。隐式评分根据用户的行为类型自动折算成不同的分数用户行为行为分值浏览商品详情页1分搜索后点击2分加入购物车3分收藏4分下单并完成支付5分实现要点当用户触发某个行为时调用一个RatingService.rate(userId, flowerId, behaviorType)方法方法内部根据行为类型映射成分值然后执行 upsert 操作——如果有记录就更新分值取最大没有就插入。注意是取最大值而不是覆盖因为用户可能从浏览一路到购买行为强度是递进的。5.2 冷启动问题怎么解决冷启动是协同过滤的天生短板新用户没有行为数据新商品没有评分记录。项目里的兜底策略挺实用新用户没有评分记录时推荐接口不查算法结果表改为查销量最高的6个商品。用热门来扛冷启动等用户产生了行为数据后推荐算法逐步介入。新商品没有评分记录的商品不会进入候选集短期靠编辑推荐或分类列表曝光。等有用户购买和评分之后才逐步进入推荐池。另外可以加一个默认评分方案给所有用户对热门商品统一设置一个基础分比如3分避免矩阵过于稀疏。这个方法效果比较大但要注意别把默认评分当成真实评分计算相似度否则算法会被污染。稳妥做法是单独设置基础分权重不参与相似度计算。5.3 当前项目在数据层的优化空间如果你拿到源代码检查一下rating表有没有创建联合索引(user_id, flower_id)。这个索引对算法性能影响很大因为算法每次全量计算都要扫描这张表并且要按用户分组装数据。没有索引的话MySQL会在几万条数据时就开始明显变慢。项目如果没这个索引你可以自己加上ALTER TABLE rating ADD UNIQUE INDEX idx_user_flower (user_id, flower_id);另外recommendation表也要针对user_id建索引否则在线读取推荐结果时会变成全表扫描影响首页响应速度。6. 完整部署运行指南拿到源代码后从0到1跑起来这个部分我按实操顺序写按这个顺序操作正常情况下两小时以内能把系统跑起来。6.1 环境准备版本选择是第一道坎软件推荐版本注意事项JDK1.8 或 11不要用17Spring Boot 2.x不兼容Maven3.6用过3.8/3.9均正常MySQL5.7 或 8.08.0需要调整驱动和时区配置IDEA2021版本无所谓能导入Maven项目就行这里单独说一下MySQL 8.0和5.7的差异坑。如果项目的application.yml里数据库驱动写的是com.mysql.jdbc.Driver那5.7没问题但8.0必须改成com.mysql.cj.jdbc.Driver并且URL要加时区参数spring: datasource: url: jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver6.2 数据库初始化项目自带flower_shop.sql脚本直接用Navicat或命令行执行mysql -u root -p flower_shop.sql执行完后检查一下表数量应该是8张表user、flower、flower_type、cart_item、orders、order_item、rating、recommendation。如果缺少某张表说明SQL脚本没执行完整重新导入一次。导入之后可以用这段SQL验证一下数据能否正常读取SELECT COUNT(*) FROM flower; SELECT COUNT(*) FROM rating;如果flower表里有几十条测试数据rating表里有若干条评分记录说明数据就位了。注意测试数据一定要有评分记录不然推荐算法算了个寂寞。6.3 启动项目与验证功能用IDEA导入Maven项目等待依赖下载完成。找到启动类一般是FlowerShopApplication.java右键运行。看到类似下面的日志就说明启动成功Tomcat started on port(s): 8080 (http) with context path 浏览器访问http://localhost:8080/应该能打开商城首页。用项目自带的测试账号登录一般在SQL脚本里有INSERT语句或者注册一个新账号。6.4 推荐链路验证这是最重要的功能验证分三步走第一步注册一个新用户给几个不同分类的商品打评分至少3条以上构造出明确的“用户画像”。第二步换一个用户给另外几个商品打评分尽量让两个用户的评分集合有交集即两个人对同一商品打过分这样算法才能计算相似度。第三步手动触发一次推荐计算。如果项目没有定时任务触发按钮可以在测试类里写一个临时代码调用RecommendService.generateRecommendations(userId, 5, 6)或者等凌晨2点定时任务跑完。然后查recommendation表SELECT * FROM recommendation WHERE user_id 你的用户ID;再回到商城首页看“猜你喜欢”区域正确的结果是推荐的商品不包含自己已经评分过的且推荐的商品是相似用户喜欢但你还没买的。6.5 验证时最容易出现的三个坑坑一所有用户的推荐结果都一样。这说明评分数据太少或者相似度计算没有过滤算法退化成热门推荐了。解决方法是给多个用户多造几条评分数据让评分矩阵丰富起来。坑二推荐结果包含用户买过的商品。这是过滤逻辑没生效——查询generateRecommendations方法里有没有判断currentUserRatings.containsKey(flowerId)没有的话加上。坑三启动时报java.sql.SQLException: Unknown initial character set index 255。这是MySQL连接URL没加characterEncodingutf8回6.1节把URL改对。7. 常见异常排查与进阶扩展思路项目跑通只是第一步你大概率会遇到下面这些问题。我把实际排查经验写出来省得你再折腾一晚上。7.1 Spring Boot版本过高带来的连锁反应很多同学不重视父POM版本新建项目时脚手架默认生成了Spring Boot 3.x然后导入源代码后各种报错javax.servlet不存在、spring.factories加载失败、WebMvcConfigurerAdapter已废弃。Spring Boot 3.x把Java EE标准从javax迁移到jakarta跟基于Spring Boot 2.x的旧项目完全不兼容。解决办法很简单把父POM里的Spring Boot版本改成2.7.18这是2.x的最终版本稳定且修复了大部分已知问题。改完后执行Maven的clean和reload project大部分编译错误会自动消失。7.2 MyBatis-Plus字段映射问题项目表字段如果有is_hot、is_deleted这种带下划线的字段MyBatis-Plus默认开启驼峰映射isHot属性能正常映射。但如果表字段叫type_id实体类属性叫typeId也OK。问题通常出在sales字段上——如果实体类属性名不小心写成了sale那查询出来的销量永远是0而且不报错。排查方法很简单打开SQL日志看实际查询的字段对比实体类属性。如果不对应在实体类字段上打TableField(sales)注解强制映射。7.3 推荐算法性能优化空间目前全量计算的算法复杂度是 O(N²·M)N是用户数M是商品数。用户量到几千时还能承受但到了几万就会明显变慢。优化思路有三个方向一是用余弦相似度 倒排索引加速相似度计算。先统计每个商品被哪些用户评分过再根据商品维度匹配用户对避免双重循环全量遍历。二是把用户相似度矩阵缓存到Redis而不是每次重新算。冷数据放MySQL热数据放Redis能极大提升在线查询速度。三是引入交替最小二乘ALS矩阵分解这是工业界主流方案把稀疏评分矩阵拆成用户隐因子特征矩阵和商品隐因子特征矩阵再通过特征向量内积预测缺失评分。Apache Spark MLlib里有现成实现但工程复杂度会上一个台阶。7.4 从课程设计到生产部署多走三小步如果你想把这个项目放到服务器上给真实用户用除了修bug之外最重要的三件事是第一密码不能明文存。现在项目用MD5这在生产环境是不够的至少升级到BCrypt加密每次校验时BCryptPasswordEncoder.matches()验证。第二加商品库存校验。下单时要检查库存扣减库存要用乐观锁或者UPDATE ... WHERE stock #{count}条件更新防止超卖。第三配置HTTPS。现在HTTP明文传输用户密码和订单信息有被截获风险。用Nginx做HTTPS反代是最简单的方案。7.5 如果让推荐效果再上一个台阶当前项目纯靠协同过滤而真实的推荐系统通常采用混合推荐策略。给你三个可落地的扩展方向加入基于内容Content-Based的推荐在flower表已有分类、描述的基础上用TF-IDF或简单的关键词标签提取商品特征计算商品相似度解决协同过滤的新商品冷启动问题。加入热门惩罚因子销量太高或评分太高的商品容易霸榜导致推荐结果趋同。可以在推荐分数上乘以一个惩罚系数比如score * (1 - log(1 sales) / 10)给长尾商品更多曝光机会。做简单的A/B测试把用户分成两组一组用协同过滤一组用销量排行对比两组的点击率和转化率。有了数据支撑你的答辩或者项目汇报会非常有说服力。我个人在实际部署这个项目时最大的体会是推荐系统真正难的不是算法公式而是用户行为数据的采集和灌入。项目评分数据如果没有打点采集机制算法再完美也跑不出效果。这个项目在源码里虽然带了评分表结构和算法实现但我建议你自己写一个简单的行为采集工具方法挂到用户浏览、加购、下单这些关键动作上凑够几个真实用户跑一轮再回来看推荐结果你会有一种“原来算法真的在起作用”的实感。数据库里多存几条真实的用户行为记录比任何花哨的算法优化都有用。想验证这个项目推荐效果的同学务必先往rating表多造几十条有区分度的数据——数据越丰富推荐结果越像样。本文还有配套的精品资源点击获取
返回列表