公司动态

SpringBoot鲜花商城实战:从用户行为到协同过滤推荐系统

📅 2026/8/31 18:23:40
SpringBoot鲜花商城实战:从用户行为到协同过滤推荐系统
简介这是一套面向Java全栈开发学习者与毕业设计实践者的鲜花电商推荐系统完整工程基于SpringBootVue实现前后端分离架构融合协同过滤算法用户相似度推荐解决个性化商品分发问题适用于课程设计、毕设选题及中小电商项目原型开发。资源包共818个文件9.48MB涵盖81个Java后端核心类、84个JS前端逻辑、39个CSS样式文件含Layui、icomoon等UI组件、111个XML配置及1个SQL建库脚本结构清晰模块化程度高。已有41人学习下载可直接导入IDEA与VSCode运行包含买家含猜你喜欢、购物车、订单全流程、卖家商品管理、订单发货、管理员多维数据统计、商户与用户全生命周期管理三端完整功能协同过滤推荐模块已集成至首页与商品详情页配套样式资源丰富支持快速二次开发与算法调优。1. 项目一开始是怎么想的为什么要做这样一个鲜花商城先说结论这个项目就是一套前端商城页面 SpringBoot 后端接口 MySQL 数据库 协同过滤推荐引擎的组合体。它不是一个只用来演示增删改查的玩具项目而是把“推荐系统”真正塞进了电商闭环里用户能注册登录、浏览鲜花、加购下单也能在首页看到“猜你喜欢”而猜你喜欢背后的逻辑就是协同过滤算法在实时算。我最初做这个项目的时候目标群体很明确Java 后端学习者、SpringBoot 刚入门想找个完整实战的人、还有做毕业设计/课程设计的学生。这类群体最怕的是什么怕项目太简单没亮点也怕项目太复杂啃不动。鲜花商城恰好是一个折中的选择业务模型足够经典——用户、商品、订单、购物车、评价这套模型放在任何电商项目里都能复用推荐系统又是一个非常好的差异化亮点很多人做商城项目就做到订单结束完全没有算法层面的东西加上协同过滤之后整个项目的技术含量立刻就不一样了。再说说为什么是鲜花。其实商品换成书、零食、数码产品逻辑完全相同但鲜花的品类特征对推荐算法特别友好SKU 不算多、价格梯度明显、用户重复购买率高、且有明显的节日属性。你想想2月14号之前搜索玫瑰的人和平时买绿植的人行为特征是有明显差异的这种差异恰恰是协同过滤能捕捉到的。如果换成数码产品用户可能三年才买一次行为数据稀疏得没法算。这个项目整体跑起来大概是这样的前端页面调后端的 RESTful 接口获取数据后端 SpringBoot 负责业务逻辑和推荐计算MySQL 存储用户、商品、订单和用户行为数据推荐模块定时或者实时计算出每个用户的 TopN 商品列表存储到推荐结果表里用户访问首页时直接读取。代码结构上用的是经典的分层架构Controller → Service → Mapper配合 MyBatis 操作数据库算法部分单独拆了一个 recommendation 包方便后续替换算法实现。2. 核心业务模块与功能拆解2.1 用户中心与权限控制用户模块看着简单但其实是整个推荐系统的地基。为什么这么说因为协同过滤算法的输入是“用户对物品的行为矩阵”如果用户模块做得潦草后面算法根本没法算。这个项目里用户模块做了三层注册登录、个人信息维护、管理员与普通用户角色区分。注册时用了 Spring Security 的 BCrypt 密码加密存储密码字段在数据库里不是明文这个习惯建议所有做 SpringBoot 项目的人都养成。BCrypt 的好处是每次加密的盐值不同同样的密码存到数据库里是两个不同的字符串就算数据库泄露了彩虹表也解不开。登录成功之后后端签发 JWT Token前端把 Token 存到 localStorage之后每次请求都在 Header 里带上Authorization: Bearer token。我用了一个拦截器统一做 Token 校验白名单路径只放行/api/user/login、/api/user/register、/api/flowers/list这几个接口。注意一点首页的商品列表必须放行不然用户没登录连商品都看不了推荐功能还怎么做用户行为数据的埋点是用户模块里最容易被忽略、但实际最重要的部分。用户在什么时间点、浏览了哪个商品、停留了多久、是否加入购物车、是否下单这些行为都要有对应的接口去记录落库到行为表。我项目中设计了三种行为类型浏览、加购、购买权重分别是 1、3、5。这个权重不是随便拍的浏览只能代表用户“看到了”加购代表“感兴趣”购买代表“有真实需求”后续算法计算偏好分时这三种行为的分值会乘以不同的权重系数。2.2 商品管理与分类体系商品模块做的是标准的花店商品管理商品名称、图片、分类、价格、库存、销量、上架状态。分类体系建议做两级大类鲜切花、绿植盆栽、永生花、花束礼盒和小类玫瑰、百合、康乃馨等。两级分类的主要目的是让推荐算法在冷启动阶段有东西可以用——后面细讲。商品列表接口我做了三个维度的查询条件关键词模糊搜索、分类筛选、价格区间排序。SQL 写法上用 MyBatis 动态 SQL注意防止 SQL 注入不能用字符串拼接要用if标签配合#{}参数占位。商品详情页额外返回两个字段recommendCount这个商品被推荐了多少次和clickCount点击量这两个字段不需要实时统计每天晚上用定时任务跑一次前一天的数据汇总更新即可别在详情接口里做 count 查询性能扛不住。还有一点容易被忽略商品上下架状态。推荐算法在计算时一定要过滤掉下架商品不然用户看到推荐结果点进去是404体验很差。我在 SQL 里统一加了status 1的条件所有查询商品的地方都带上包括推荐模块。2.3 购物车与订单流程购物车和订单属于电商系统的标准模块。购物车表设计时我用了user_id flower_id做联合唯一索引防止同一个用户对同一个商品插入多条购物车记录。如果用户重复点击“加入购物车”走的是更新数量的逻辑而不是新增记录。订单流程是这个项目里事务控制最核心的地方。用户下单涉及的操作包括创建订单记录、创建订单明细、扣减库存、清空购物车。这四个操作必须放在同一个事务里任何一个失败都要全部回滚。SpringBoot 里用Transactional注解就搞定了但要特别注意rollbackFor Exception.class不然运行时异常能被捕获但编译期异常不会触发回滚这是个经典坑。订单状态我设计了五种待付款、待发货、已发货、已完成、已取消。用户确认收货后订单变成已完成状态这时候会触发一个行为回调把这次购买行为写入用户行为表作为推荐算法的输入。这个闭环很重要它让推荐系统能持续学习用户的最新偏好。2.4 后台管理模块后台管理我是给管理员角色预留的功能包含商品上架下架、编辑库存价格、查看订单列表、查看用户列表。后台接口全部加了角色校验非管理员账号直接返回 403。前端页面我用了独立的 admin 路由和用户端商城页面分开界面虽然简单但功能是齐全的。后台还有个很重要的功能推荐算法的“手动触发重算”按钮。算法虽然做了定时任务但管理员在后台调整了商品分类或者下了某些商品后可以手动触发一次全量重算避免定时任务还没到时间导致推荐结果还是旧数据。这个按钮在调试算法参数的时候也非常有用后面会细说。3. 协同过滤推荐模块核心亮点与难点3.1 什么是协同过滤以及这个项目为什么选它协同过滤Collaborative Filtering的核心思想就一句话物以类聚人以群分。具体分两种路线基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。UserCF 的逻辑是找到和你兴趣相似的一群人把这些人喜欢的、但你没买过的商品推荐给你。比如用户A买了红玫瑰和白百合用户B买了红玫瑰和向日葵那系统就会把向日葵推荐给用户A因为 A 和 B 的偏好相似度很高。ItemCF 的逻辑是找到和你买过的商品相似的商品推荐给你。比如买过红玫瑰的人大概率也买过粉色满天星那就给所有买过红玫瑰的用户推荐满天星。这个项目我选择的是 ItemCF 为主、UserCF 为辅的混合策略。原因很现实鲜花商城的用户数量级不会很大但每个用户的购买行为可能相对集中ItemCF 适合这种场景而且 ItemCF 的推荐结果解释性强用户可以理解“因为你买过红玫瑰所以给你推荐了粉色满天星”这种可解释性对商城转化率是有帮助的。另一个考虑是计算效率。UserCF 需要在线计算用户之间的相似度矩阵用户量一上来这个矩阵是 O(n^2) 的复杂度非常吃内存。ItemCF 的商品数量远小于用户数量离线把商品相似度矩阵算好存起来在线推荐时直接查表聚合性能好得多。商品数量就算有一万种商品相似度矩阵也就是一万乘一万这个量级在内存里完全没问题。3.2 ItemCF 算法的完整计算流程整个 ItemCF 的计算分成三步构建用户行为评分矩阵、计算物品相似度矩阵、生成推荐列表。第一步构建评分矩阵。从数据库里把用户行为表查出来把每个用户对每个物品的评分算出来。评分规则我上面提到过浏览 1 分加购 3 分购买 5 分。如果一个用户既浏览过又加购过又购买了同一个商品分数就直接累加用user_id flower_id做分组SUM(score)得出最终评分值。第二步计算物品之间的相似度。相似度算法我用的余弦相似度公式长这样similarity(i, j) (所有同时评价过物品i和物品j的用户评分乘积之和) / (sqrt(物品i的评分平方和) * sqrt(物品j的评分平方和))你可能觉得数学公式看着头疼我用人话解释一下两个物品的相似度取决于同时喜欢这两个物品的人有多少、喜欢的程度有多深。同时喜欢的人越多这两个物品就越相似。玫瑰和百合可能经常被同一个人买走玫瑰和仙人掌就很少同框出现。第三步生成推荐列表。对于目标用户已经有过行为的物品集合逐个找出最相似的 N 个物品累加相似度作为推荐分数去掉用户已经买过的取分数最高的 TopK 个返回。这一步完全可以在内存里算因为商品相似度矩阵在项目启动时就已经加载到内存了。3.3 Java 代码实现要点算法核心代码我放在了com.flower.shop.recommendation包下。矩阵数据用了HashMapInteger, HashMapInteger, Double双重 Map 结构外层 key 是用户ID内层 key 是商品IDvalue 是评分。计算物品相似度时先遍历所有用户的评分记录找出所有共同评分过的物品对累加它们的乘积再除以各自的模长。伪代码大概是这样// 1. 构建用户-物品评分矩阵 MapInteger, MapInteger, Double userItemMatrix buildUserItemMatrix(); // 2. 计算每个物品的模长向量长度 MapInteger, Double itemNormMap new HashMap(); for (MapInteger, Double items : userItemMatrix.values()) { for (Map.EntryInteger, Double entry : items.entrySet()) { itemNormMap.merge(entry.getKey(), entry.getValue() * entry.getValue(), Double::sum); } } // 对每个值开根号得到向量模长 // 3. 计算共同评分矩阵 MapInteger, MapInteger, Double coRateMatrix new HashMap(); for (MapInteger, Double items : userItemMatrix.values()) { ListInteger itemIds new ArrayList(items.keySet()); for (int i 0; i itemIds.size(); i) { for (int j i 1; j itemIds.size(); j) { int itemI itemIds.get(i); int itemJ itemIds.get(j); double score items.get(itemI) * items.get(itemJ); coRateMatrix.computeIfAbsent(itemI, k - new HashMap()) .merge(itemJ, score, Double::sum); coRateMatrix.computeIfAbsent(itemJ, k - new HashMap()) .merge(itemI, score, Double::sum); } } } // 4. 计算余弦相似度 // similarity(i, j) coRateMatrix[i][j] / (norm[i] * norm[j])生成推荐列表时传入目标用户ID拿到该用户的所有行为物品遍历这些物品的相似物品列表加权累加得到每个候选物品的推荐得分最后过滤掉用户已经产生过行为的商品按分数倒序取 Top10。这段代码我用了一个PostConstruct注解的方法在项目启动时执行一次全量计算把相似度矩阵放到一个静态 Map 里缓存起来。另外写了一个Scheduled(cron 0 0 2 * * ?)的定时任务每天凌晨两点重算一次因为凌晨用户在睡觉重算期间即使有短暂的数据不一致也无所谓。3.4 冷启动问题是怎么处理的协同过滤最怕冷启动新用户没有行为数据算不出相似度推荐列表为空新商品没有被任何人买过永远不会被推荐出去。这是个纯算法解决不了的工程问题我在项目里用了两个土办法。第一个办法基于商品内容的热门兜底。新用户没有任何行为数据时系统直接返回全局热销商品 TopN按销量和浏览量综合排序。这个不需要协同过滤参与一句 SQL 就能搞定。用户一旦产生了浏览行为立刻切回协同过滤推荐。第二个办法基于分类的 ItemCF 变体。新商品刚上架没有行为数据但它的分类和已有的其他商品是相同的。我预先给每个分类设定了一些热门种子商品新商品进入推荐候选集时先跟同分类的热门种子商品绑定相似度初始相似度设为一个较小的固定值 0.1随着真实行为数据的积累真实计算的相似度慢慢替代掉这个初始值。这个方法不完美但至少让新商品不会永远沉底。提示冷启动问题不可能完全消除但“热门兜底 分类种子”的方案在中小型项目里完全够用不用为了冷启动引入复杂的多臂老虎机模型那是需要流量和数据规模支撑的。4. 数据库设计与核心表结构4.1 六张核心表的关联关系数据库是这个项目的另一大交付物我提供了完整的flower_shop.sql建表脚本里面包含建表语句和演示数据导入 MySQL 就能直接用。核心表一共六张用户表、商品表、分类表、购物车表、订单表、订单明细表外加两张辅助表用户行为表和推荐结果表。用户表和订单表是一对多关系一个用户能下多笔订单。订单表和订单明细表是一对多关系一笔订单包含多个商品。商品表属于分类表通过category_id关联。用户行为表从属于用户和商品是推荐算法的核心输入。这里重点说一下订单明细表为什么要单独建。很多初学者喜欢在订单表里放一个flower_ids字段用逗号分隔存多个商品ID这是非常糟糕的设计。订单的地址信息、金额信息是订单维度的而每个商品买了几个、单价多少是明细维度的混在一起会导致后续统计“哪些商品卖得最好”这种需求变得很难写SQL。订单表和明细表拆分之后查询某个商品的销量只需要SELECT flower_id, SUM(quantity) FROM order_items GROUP BY flower_id效率高且逻辑清晰。用户行为表是推荐算法的核心数据源字段我这样设计字段名类型说明idbigint主键自增user_idbigint用户IDflower_idbigint商品IDbehavior_typetinyint行为类型 1浏览 2加购 3购买scoreint分值浏览1 加购3 购买5create_timedatetime行为发生时间每次用户浏览商品详情页、点击加购、下单后端都会往这张表里插一条记录。定时任务扫描这张表聚合出用户评分矩阵再跑协同过滤算法。4.2 推荐结果表为什么单独存在推荐结果表recommend_result的用途是缓存每个用户的最新推荐列表字段包括user_id、flower_id、recommend_score、recommend_time。每次算法重算之后先清空这张表再批量插入最新的推荐结果。你可能要问为什么不直接在用户请求推荐接口时实时计算因为实时计算需要把相似度矩阵和用户评分矩阵全部加载到内存每次请求都遍历一遍矩阵做几百次乘法运算虽然数据量小的时候不算慢但接口响应时间会从几十毫秒膨胀到几百毫秒而且并发一高CPU 会持续空转。用预计算 缓存表的方案推荐接口只需要一条SELECT flower_id FROM recommend_result WHERE user_id ? ORDER BY recommend_score DESC LIMIT 10数据库索引命中毫秒级返回这才是生产环境应该有的做法。推荐结果表还有一个好处它可以记录“推荐时间”如果需要做推荐系统的效果评估比如统计某天的推荐转化率直接按recommend_time分组join 订单表就能算出来。4.3 数据库字段设计里的一些细节习惯数据库设计是我的老本行多说几句细节。字符集我统一用utf8mb4而不是utf8因为utf8在 MySQL 里最多支持三个字节的字符而部分特殊表情符号需要四个字节虽然鲜花商城里不一定会出现奇葩表情但统一用 utf8mb4 是标准做法避免用户签名里带个 emoji 导致插入报错。所有表的id主键用BIGINT自增不推荐用VARCHAR存雪花ID因为项目规模没到分布式级别自增主键在 InnoDB 引擎下插入性能最好B树不需要频繁分裂页。金额字段坚决不用FLOAT或DOUBLE用DECIMAL(10, 2)。浮点数在二进制里没法精确表示比如 0.1 0.2 等于 0.30000000000000004金额计算差一分钱在电商项目里是要出事的。DECIMAL是字符串存储的定点数不会存在精度问题。时间字段用DATETIME而不是TIMESTAMP虽然 TIMESTAMP 占用的字节更少但它只有 1970 到 2038 年的范围而且有时区转换的坑。DATETIME 没有这些限制语义也更明确。所有表都加create_time和update_time两个字段虽然是习惯性操作但排查数据问题的时候就知道多重要了。5. SpringBoot 核心配置与项目跑通指南5.1 如何把项目跑起来拿到源代码之后第一步不是急着打开 IDEA而是把环境准备好。这个项目要求 JDK 1.8 以上、Maven 3.6 以上、MySQL 5.7 以上。用 IDEA 打开项目后Maven 会自动下载依赖这一步可能比较久因为 SpringBoot 全家桶加 MyBatis 加 Spring Security 的依赖链很长国内网络如果下载慢建议配一下阿里云 Maven 镜像。数据库配置在application.yml文件里你需要改这几个地方数据库地址、数据库名、用户名、密码。我用flower_shop作为数据库名导入flower_shop.sql脚本后直接就能用。Redis 这个版本没用到Session 和验证码都用的本地内存方案不需要额外安装 Redis对新手友好。spring: datasource: url: jdbc:mysql://localhost:3306/flower_shop?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver这里有一个容易踩的坑serverTimezoneAsia/Shanghai不带会报时区错误characterEncodingutf8mb4不带中文可能乱码。useSSLfalse也很重要本地开发环境不需要 SSL 加密否则 MySQL 8.0 版本会输出一堆 SSL 警告。启动类直接运行FlowerShopApplication.java控制台出现Started FlowerShopApplication in xx.xxx seconds就说明启动成功了。默认端口是 8080访问http://localhost:8080就能看到商城首页。5.2 项目目录结构与代码分层拿到源码不要急着一股脑全读先看目录结构理解分层思想。项目包名我用的com.flower.shop下面是controller接收前端请求参数校验返回统一响应体service业务逻辑层事务控制调用 Mapper 和推荐模块mapperMyBatis 的 Mapper 接口entity数据库表对应的实体类dto前端请求和响应对象避免直接把实体类暴露给前端recommendation推荐算法模块协同过滤的实现都在这里configSpringBoot 配置类拦截器、CORS 配置common统一响应体、异常处理、工具类resources/mapperMyBatis 的 XML 映射文件分层的好处是职责清晰Controller 只负责参数字段校验和路由转发不写业务逻辑Service 负责业务编排和事务管理Mapper 只做数据存取。这样拆开之后后面想换推荐算法只需要改动 recommendation 包其他层不需要动。统一响应体我用了ResultT泛型类包含code、message、data三个字段。成功返回 200业务异常返回自定义错误码未登录返回 401无权限返回 403。前端页面统一拦截响应体根据code字段做全局提示。这个设计避免了每个接口各自定义返回值格式的问题前后端联调的时候省了很多事。5.3 前端页面怎么对接后端接口前端用的是 Thymeleaf 服务端渲染 原生 JavaScript没有用 Vue 或 React。选择 Thymeleaf 的原因只有一个这个项目面向的是 Java 学习者前端技术栈应该尽量简单。Thymeleaf 是 SpringBoot 官方推荐的模板引擎直接写在 resources/templates 目录下由后端渲染完返回 HTML前端 JS 通过 Ajax 调用后端的 JSON 接口。首页推荐位的数据由RecommendController提供接口路径是GET /api/recommend/{userId}?limit8。前端页面加载时先判断用户是否登录查看 localStorage 里有没有 token登录了就调用推荐接口没登录就调用热销接口GET /api/flowers/hot。这两个接口返回的数据结构完全一致前端拿到之后渲染逻辑不用改非常友好。商城首页、商品列表页、商品详情页、购物车页、结算页、订单列表页这些页面都和鲜花电商的业务一一对应。后台页面我单独放在templates/admin目录下管理员登录后可以看到商品管理和订单管理页面。6. 从零开始复现推荐算法的完整步骤6.1 离线计算物品相似度矩阵项目启动后推荐模块经历两次计算阶段。第一次是项目启动时由PostConstruct触发全量重算。这段逻辑我封装在RecommendService的reloadRecommendData()方法里具体步骤如下查询user_behavior表全部记录按用户分组构建user - 该用户对每个物品的评分 Map的数据结构第一次遍历所有用户的评分记录计算每个物品的评分平方和再开根号得到物品向量模长第二次遍历时对每个用户的不重复物品对组合累加共同评分乘积用共同评分乘积除以两个物品模长的乘积得到最终的物品相似度把相似度矩阵存到内存中的静态变量里同时按相似度倒序保留每个物品最相似的 Top20存入item_similarity内存表遍历所有用户为每个用户计算 Top10 推荐列表写入recommend_result表整个计算过程在商品数一万、行为记录十万条的量级下耗时大约 10 到 20 秒。这个耗时完全能接受因为只有项目启动和每天凌晨的定时任务才会触发用户平时访问推荐接口不会感受到延迟。6.2 在线推荐接口的查询逻辑当用户请求GET /api/recommend/{userId}时后端并不会去重新计算推荐结果而是直接查询recommend_result表。这里有一个非常关键的逻辑判断如果推荐结果表里没有这个用户的记录说明用户没有行为数据此时需要走冷启动策略返回热门商品列表。代码是这么写的public ListFlower getRecommendList(Integer userId, Integer limit) { // 1. 先查推荐结果表有结果直接返回 ListRecommendResult results recommendResultMapper.selectByUserId(userId); if (results ! null !results.isEmpty()) { return flowerMapper.selectByIds(results.stream() .map(RecommendResult::getFlowerId) .collect(Collectors.toList())); } // 2. 没结果说明该用户没有行为数据走冷启动 return flowerMapper.selectHotFlowers(limit); }注意一个细节推荐结果表里存的只是用户ID和商品ID真正返回给前端时还要查商品表关联出商品名称、图片、价格信息并且要过滤掉已经下架的商品。这个 join 操作我建议在 Mapper 里一次性查出不要遍历推荐结果一条一条查商品那样会产生 N1 查询问题。6.3 参数调优与效果观察协同过滤算法里有几个参数是可以调的直接影响推荐效果的好与坏。第一个是相似物品邻居数 K。K 值表示在给用户生成推荐列表时每个用户行为物品最多取多少个相似物品参与累加。K 值太小推荐结果碎片化可能推荐一些冷门但不够精准的商品K 值太大会把大量不太相似的商品也卷进来推荐精度被稀释。根据鲜花品类的特点K 取 10 到 20 之间效果比较好。第二个是推荐列表长度 TopN。首页推荐位一般是 8 个商品详情页的“相关推荐”是 4 个。TopN 越大用户的选择越多但决策成本也越高TopN 太小用户可能没有满意的商品就跳出页面。以鲜花商城这个项目来说首页 8 个、详情页 4 个是经过实践验证的比较合适的值。第三个是行为权重。我目前用的是浏览 1、加购 3、购买 5但如果你想让推荐结果更偏向高转化商品可以把购买行为的权重调高到 10。不过不建议权重差距过大否则浏览行为几乎不起作用新用户的兴趣捕捉会变慢。调参的过程中最直观的观察方式是注册一个新账号先浏览几个玫瑰花相关的商品然后回到首页看推荐结果如果推荐出了百合、康乃馨这类同分类商品说明算法逻辑是对的如果推荐出了仙人掌那就要检查相似度矩阵是不是算错了。7. 实际开发中踩过的坑与排查技巧7.1 MySQL 版本号引起的 JDBC 驱动问题这个坑我印象非常深刻。最初项目用的是 MySQL 5.7驱动依赖写的是com.mysql.jdbc.Driver一切正常。后来换了 MySQL 8.0 测试启动时直接报ClassNotFoundException: com.mysql.jdbc.Driver。原因是 MySQL 8.0 之后驱动类名改成了com.mysql.cj.jdbc.Driver依赖包也从mysql-connector-java换成了mysql-connector-j。解决方式很简单在pom.xml里显式指定 MySQL 驱动的版本不要用 SpringBoot 父依赖自动管理的版本因为自动管理的版本不一定和你的 MySQL 服务端版本匹配。我在项目里用的是 8.0.33 版本的驱动配合 SpringBoot 2.7.x 版本非常稳定。7.2 推荐结果为空一步步排查的思路用户第一次登录之后发现首页推荐位是空的这是最常见的 bug。排查顺序我建议这样走先看这个用户是否有行为数据。在user_behavior表里按user_id查一下如果一条记录都没有说明行为数据的写入环节出了问题前端浏览事件可能没调后端接口如果有行为数据再查recommend_result表里有没有这个用户的记录。没有说明定时任务没有跑成功或者算法执行时异常了去看控制台日志有没有报错如果推荐结果表有记录但接口返回空检查推荐的商品是否都是下架状态或者商品查询时status 1条件过滤太狠最容易被忽略的是用户行为数据写入接口的鉴权问题。如果浏览记录接口的 URL 被拦截器拦截了前端请求返回 401行为数据根本写不进去推荐结果自然是空的。排查这类问题最好的办法是打开浏览器开发者工具看 Network 面板看看行为上报请求到底返回了什么状态码。7.3 SpringBoot 版本过高导致的问题这个项目最初我用的 SpringBoot 2.7.x后面作者自己在测试时升级到了 SpringBoot 3.x结果发现 Thymeleaf 的语法兼容性出了问题而且javax.*的包名全部迁移成了jakarta.*大量代码需要改动。如果不熟悉 jakarta 命名空间的迁移建议稳定用 SpringBoot 2.7.x别追求最新版本。还有个相关的问题SpringBoot 版本和 JDK 版本强相关。SpringBoot 2.7.x 可以跑在 JDK 8 上但 SpringBoot 3.x 要求 JDK 17 以上。如果你的电脑装的是 JDK 8直接上 SpringBoot 3.x 会启动失败。在 pom.xml 里加上下面这段配置可以让 Maven 编译时检查 JDK 版本properties java.version1.8/java.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties7.4 数据库连接超时与连接池配置开发环境下数据库连接超时可能不明显但项目跑久了会发现某个接口偶尔报Connection is not available, request timed out。这是 HikariCP 连接池的连接被数据库服务端断开后没有及时清理导致的。MySQL 8.0 默认的wait_timeout是 8 小时连接闲了 8 小时会被服务端切断连接池不知道还把旧连接拿出来用就会报错。解决方式是在数据源配置里加上连接存活检查spring: datasource: hikari: connection-test-query: SELECT 1 idle-timeout: 30000 max-lifetime: 1800000 minimum-idle: 5 maximum-pool-size: 20connection-test-query: SELECT 1让连接池在每次拿连接前先执行一个探活 SQL如果连接已经断了就重新创建。max-lifetime设置为 30 分钟远小于 MySQL 的 8 小时超时时间从根源上避免连接被服务端切断。7.5 前后端联调时的跨域与数据格式问题前端如果用了独立端口比如 Vite 起的 3000 端口后端在 8080 端口就会出现跨域。我项目里通过 WebMvcConfigurer 配置了全局 CORS放行了所有来源和所有请求头。生产环境你会想收紧 CORS只允许自己的前端域名访问开发环境就无所谓了。数据格式的问题也值得一提。前端用 Ajax 提交application/json数据后端用RequestBody接收但如果前端用的是表单提交后端就要用RequestParam。我之前调试过一个 bug前端提交的是 JSON后端却用RequestParam接收导致每个字段都是 null查了好久。统一用 JSON RequestBody是最省心的方案。8. 我的个人经验与后续扩展建议这套系统我已经完整跑通过很多次整体稳定性是没问题的。要说最值得夸的部分不是 SpringBoot 怎么用、页面写得有多华丽而是协同过滤推荐模块真的在产生效果。我做过一个测试新建一个账号浏览几款红色系玫瑰再去看推荐位出来的几乎都是同色系或同分类的花。这种“懂你”的感觉比什么订单管理、购物车这些标配功能更能打动评审老师或者面试官。如果拿到源码之后想自己做二次开发我建议按照以下顺序来扩展第一优先级把推荐算法的效果可视化做一个后台页面。展示每个用户推荐列表里的商品、相似度分数、推荐理由。这个功能最能体现你对算法的理解深度也是答辩或演示时最有说服力的加分项。第二优先级引入用户画像标签体系。给每个用户打上“喜欢红玫瑰”、“偏好简约花束”、“价格敏感型”这类标签推荐时结合标签过滤和加权推荐精度会显著提升。做用户画像需要的数据在项目里已经存在不需要额外采集。第三优先级把 ItemCF 替换成更现代的算法。比如引入 Redis 做实时行为流缓存或者用 Spark MLlib 的 ALS 算法做矩阵分解。当数据量再上一个量级之后内存态的物品相似度矩阵会变得非常臃肿分布式计算是必然选择。最后再说一句数据库的事这套系统的 SQL 脚本和源代码是配套的数据库里内置了演示数据——包含 10 个测试用户、50 个商品、几百条订单记录和几千条行为记录。这些数据不是随便生成的行为数据的分布遵循了二八原则少部分热门商品拥有大部分行为记录符合真实电商场景。你拿到手可以直接开始测试推荐效果不需要先造数据。另外补充一个小技巧在调试推荐算法的时候不要每次手动去数据库插行为数据。我建议在后台页面上做一个“模拟用户行为”的按钮可以随机生成一批浏览和购买记录。这样就能快速制造出一个有行为历史的测试账号验证推荐结果是否符合预期。这个小工具在开发调试时能帮你节省大量时间。我实测下来这个项目最大的价值不在于商城本身——商城只是壳子协同过滤才是灵魂。把推荐系统真正跑通并且在页面上看到推荐结果随着自己的行为数据发生变化那种感觉是看多少遍教程都体会不到的。希望这份源码和数据库能帮你迈过推荐系统从理论到实践这道坎。本文还有配套的精品资源点击获取