SSM电商平台个性化推荐实战:协同过滤ItemCF项目全解析
Java Web 课设选了个“电商购物平台”不算新鲜但加上“个性化推荐”这五个字含金量立刻不一样。我最近完整过了一遍这个基于 SSM 的商城项目源码从 IDEA 导入到推荐逻辑落地再到前后台联调算是把整条链路都跑通了。这篇文章就把拆解过程和实操经验写出来给同样选了”电商购物平台个性化推荐“这个组合的读者做个参考不管你是要做课程设计、毕业设计还是想找一份 SSM 方向的源码练手都能用得上。1. 项目整体设计与技术选型思路1.1 为什么是 SSM 这个“老组合”先聊一个很多人纠结的问题都 2025 年了为什么这种项目还在用 Spring SpringMVC MyBatis而不是直接用 Spring Boot我的看法是这类选题之所以长期存在是因为 SSM 是很多学校的教学主线和面试考察重点。Spring Boot 确实把配置简化了一大截但它自动配置的黑盒有时反而让新手更迷惑——出了问题不知道去哪里排查。SSM 不一样每一个请求怎么从 Controller 到 Service 再到 Mapper每一个 Bean 怎么被 Spring 容器管理都是明文配置捋一遍源码能让你对 Java Web 的底层流转有更扎实的认识。从实用角度讲SSM 版本在 IDEA 里跑起来并不比 Spring Boot 复杂多少。难点主要在三件事一是 Spring 和 MyBatis 的 XML 配置容易出错比如路径写错、Mapper 接口没扫描到二是各种 jar 包版本容易冲突三是 Tomcat 部署步骤比内嵌容器多。这三件事后面我会一个个拆开说实际上都是有固定套路的。对比下来我是这么想的如果你答辩时能被老师问清楚 DispatcherServlet 的工作原理、Mapper 代理的实现机制那这一趟 SSM 折腾下来收获比直接上手 Spring Boot 大得多。1.2 功能模块与分层架构怎么拆这个项目的整体架构是经典的前后端不分离模式后端按三层结构组织代码Controller 层负责接口接收请求Service 层处理业务Mapper 层操作数据库。页面用 JSP JSTL 渲染静态资源由 SpringMVC 统一放行。功能模块上前台商城和后台管理是完整分开的这一点很多做课程设计的同学容易忽略。光有用户买东西的页面没有管理端商品数据只能靠人手动插数据库演示起来非常别扭。前台部分包含这些模块用户模块注册、登录、个人信息修改、收货地址管理。商品模块分类展示、关键词搜索、商品详情页、商品列表排序。购物车模块添加商品、修改购买数量、删除、勾选结算。订单模块提交订单、订单列表、订单状态跟踪。推荐模块首页“猜你喜欢”、详情页“看了又看”、购物车的搭配推荐。个人中心我的订单、我的收藏、浏览记录。后台管理端包含商品管理上架、下架、编辑、库存调整。分类管理商品分类的新增与修改。订单管理查看订单、修改订单状态。用户管理用户查询、禁用启用。数据统计订单数量、销售额、热销商品排行。我强烈建议你在跑通源码之后把后台管理相关的代码从头到尾看一遍。因为在实际答辩中老师很可能会问“商品数据是怎么进来的”如果你答不上来那整个项目的可信度就大打折扣了。2. 个性化推荐模块核心算法与工程落地2.1 三种推荐思路的选型对比这个项目的灵魂在“个性化推荐”但推荐算法有很多种选法。我在分析源码时发现真正适合这个 SSM 项目落地的方案主要有三条路线第一种是基于规则的推荐最常见的做法就是按销量排行、按新品上架时间排序、按价格区间筛选。这种方案本质上不是个性化只是把热门商品露出来。优点是实现成本几乎为零缺点是每个用户看到的都是同一批商品谈不上“个性”。第二种是基于内容的推荐比如用户频繁浏览某个分类下的商品就给他推荐同分类的商品。这种方案实现成本也不高只需要在用户浏览商品时记录分类偏好然后做一个分类匹配就行。第三种就是协同过滤分为基于用户的 UserCF 和基于物品的 ItemCF。这个项目最有技术含量的部分就集中在这里。我的选型建议是如果你想让项目有真正的“推荐”味道又不想在答辩时被算法细节问倒优先考虑 ItemCF——基于物品的协同过滤。理由有三第一商品数量通常远小于用户数量商品相似度矩阵的计算成本更可控第二推荐结果的解释性比 UserCF 更强简化版实现很好讲第三电商场景中用户行为稀疏ItemCF 的推荐稳定性比 UserCF 更好。2.2 基于物品协同过滤的相似度计算ItemCF 的核心是计算商品之间的相似度。经典计算方法是余弦相似度公式是similarity(i, j) |N(i) ∩ N(j)| / sqrt(|N(i)| × |N(j)|)其中 N(i) 是喜欢商品 i 的用户集合N(j) 是喜欢商品 j 的用户集合分子是两个集合的交集大小也就是同时喜欢这两个商品的人数。我举个例子假设有 100 个用户喜欢《Java 编程思想》80 个用户喜欢《Spring 实战》其中有 40 个用户两本书都喜欢。那这两本书的相似度就是 40 / sqrt(100 × 80)算出来约等于 0.45。站在推荐的角度如果你看过《Java 编程思想》系统就可以把《Spring 实战》推给你因为这两本书的相似度最高。这里的“喜欢”在电商场景中要展开定义。我这个项目里会给用户行为分权重浏览算 1 分收藏算 2 分加入购物车算 3 分购买算 4 分。用户对商品的最终偏好值取这些行为的加权和超过某个阈值就认为用户“喜欢”这个商品。这样处理的好处是一个只看不买的用户和一个真金白银买过的用户对推荐结果的贡献是完全不同的。2.3 推荐引擎的运行流程与代码落地这个项目的推荐模块不是每次请求都现场计算相似度矩阵而是采用“离线计算在线查询”的模式。流程是这样的用户浏览商品、收藏、加购、下单时在 Controller 里埋点把行为记录写入 user_behavior 表。系统每天凌晨通过 Spring Task 定时任务跑一次相似度计算把计算结果存到专门的相似度表中。当用户访问首页时推荐模块只做三件事从行为表查用户最近浏览或购买的商品拿这些商品去相似度表查 TopN 最相似的商品再过滤掉用户已经购买或浏览过的商品输出成“猜你喜欢”。伪代码长这样public ListProduct recommendForUser(Integer userId, int topN) { // 1. 找用户最近感兴趣的N个商品 ListInteger seedProductIds behaviorService.findRecentProductIds(userId, 5); if (seedProductIds.isEmpty()) { // 2. 冷启动无行为数据按销量推荐 return productService.findHotProducts(topN); } // 3. 根据种子商品查相似商品按相似度倒序 ListProduct candidates similarityService.findSimilarProducts(seedProductIds); // 4. 过滤掉已购、已浏览商品 ListInteger excludedIds behaviorService.findExcludedProductIds(userId); return candidates.stream() .filter(p - !excludedIds.contains(p.getId())) .limit(topN) .collect(Collectors.toList()); }对应的 Mapper 核心 SQL 也很直观SELECT similar_product_id, score FROM product_similarity WHERE product_id IN ( SELECT product_id FROM user_behavior WHERE user_id #{userId} ORDER BY create_time DESC LIMIT 5 ) ORDER BY score DESC LIMIT #{topN}在离线计算阶段核心代码是遍历所有商品对计算交集和余弦相似度然后批量写入 product_similarity 表。Service public class SimilarityCalculateTask { Resource private BehaviorMapper behaviorMapper; Resource private SimilarityMapper similarityMapper; Scheduled(cron 0 0 3 * * ?) public void calculateAllSimilarities() { // 1. 查出每个商品对应的喜欢用户ID集合 MapInteger, SetInteger productUserMap behaviorMapper.selectProductUserMap(); ListInteger productIds new ArrayList(productUserMap.keySet()); // 2. 两两计算余弦相似度 for (int i 0; i productIds.size(); i) { for (int j i 1; j productIds.size(); j) { int p1 productIds.get(i); int p2 productIds.get(j); SetInteger users1 productUserMap.get(p1); SetInteger users2 productUserMap.get(p2); SetInteger intersection new HashSet(users1); intersection.retainAll(users2); if (intersection.isEmpty()) { continue; } double score intersection.size() / Math.sqrt(users1.size() * users2.size()); if (score 0.05) { similarityMapper.insert(p1, p2, score); similarityMapper.insert(p2, p1, score); } } } } }这里有几个实操细节值得关注。第一为什么是凌晨 3 点跑因为这个时候网站访问量最低避免计算任务影响线上用户体验。第二为什么相似度小于 0.05 的就不写库这是为了缩小数据量商品几千上万时两两组合会非常多只保留高相似度的记录能显著降低存储和查询压力。2.4 冷启动与多推荐位的实战处理“冷启动”是这个项目答辩时的高频问题。所谓冷启动就是新用户刚注册系统里一条行为数据都没有拿什么给他推荐项目的处理方式是分层回退第一步判断当前用户有没有行为数据没有就按热门商品推荐即销量最高的商品列表第二步如果用户有浏览但没有购买就以浏览数据为种子做 ItemCF第三步如果用户有购买记录优先用购买记录作为种子因为购买行为的置信度远高于浏览。这套逻辑在代码里就是 if-else 的嵌套但要在答辩时把它讲清楚这就是一套完整的推荐策略。另外一个好的个性化推荐系统通常不会只有一个推荐位。我在实践这个项目时做了三个不同的推荐位首页的“猜你喜欢”以用户最近行为为种子输出综合相似商品位置放在页面最显眼的地方。商品详情页的“看了又看”以当前展示商品为种子输出相似度最高的同品类商品。购物车页的“搭配推荐”以购物车中的商品为种子输出关联度较高的配件或互补品。每个推荐位的数据来源不同、展示逻辑不同但底层都复用同一套相似度表。这样设计的好处是代码复用率高推荐位的扩展也特别方便以后想加一个“热门推荐”位只需要把查询条件改一下就行。3. 电商核心链路表结构设计与业务流程3.1 数据库整体设计与关键表字段说明个性化推荐是加分项但电商平台本身的功能链路才是底座。我翻了这个项目的建表 SQL发现整体设计是比较成熟的核心表有 8 张左右。我挑几张重点讲解一下用户表 userCREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(32) NOT NULL COMMENT 用户名, password VARCHAR(64) NOT NULL COMMENT MD5加密密码, nickname VARCHAR(32) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, email VARCHAR(64) DEFAULT NULL, status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码字段设计值得说一句。很多课程设计里密码直接明文存储这在大厂面试时是个扣分项。这个项目至少做了 MD5 存储虽然 MD5 现在不算安全但比明文强很多。如果你想让项目更亮眼可以把 MD5 升级为加盐的 SHA-256或者引入 Spring Security 的 BCryptPasswordEncoder。商品表 productCREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(128) NOT NULL, subtitle VARCHAR(256) DEFAULT NULL, main_image VARCHAR(256) DEFAULT NULL, sub_images TEXT, detail TEXT, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在售 0下架, sales INT NOT NULL DEFAULT 0 COMMENT 冗余销量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;商品表里的 sales 字段很多人会忽略但它特别有用。无论是热销推荐、商家排序还是后台数据看板的“Top10 热销商品”都可以直接用这个字段排序省去每次实时 count 订单表的开销。这就是典型的“以空间换时间”的冗余设计思路在答辩时如果能主动讲出这个设计动机会是很不错的加分点。订单表和处理表拆分开是另一个值得讲的设计点。orders 表存储订单主信息比如订单号、用户 ID、总金额、收货地址快照、支付状态、创建时间order_item 表存储每个订单下的具体商品明细包含商品名称、单价、数量、小计。拆开的好处是一个订单可以有多个商品按订单粒度查主信息很快按商品维度做数据统计也很方便。3.2 购物车与订单流程中的防超卖设计购物车的设计在这个项目里是持久化到 cart 表的而不是存 Session。这样做的好处是用户换台电脑、换浏览器登录后购物车还在体验是一致的。购物车表核心字段是 user_id、product_id、quantity、checked。提交订单的业务流程是这样的拿到购物车勾选的商品列表先生成订单主记录再写入 order_item 明细然后扣减商品库存最后清空购物车中对应的勾选商品。整个流程要放在一个事务里执行任何一个环节失败都必须回滚。这里最大的坑是库存超卖问题。简单写“先查库存再 update”是不安全的——两个用户同时下单都查到库存还剩 1 件都去扣减库存就变成 -1 了。正确的做法是使用条件更新把库存判断放到 update 语句的条件里UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}这条 SQL 执行后如果影响行数为 0说明库存不足直接抛出异常终止下单。这个细节在很多课程设计里都不会处理但它是电商系统并发场景下非常经典的问题如果能在项目里体现出来面试的时候会很加分。3.3 后台管理面板与统计功能的 SQL 实践后台管理端是这个项目比较容易被忽视但很重要的部分。商品管理、分类管理、订单管理、用户管理都是常规 CRUD真正出彩的是数据统计部分。比如统计今天的订单数和销售额可以用这样的 SQLSELECT COUNT(*), COALESCE(SUM(total_price), 0) FROM orders WHERE create_time CURDATE() AND status IN (1, 2, 3); -- 已付款、已发货、已完成再比如统计近 7 天的每日销售额SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS day, SUM(total_price) AS amount FROM orders WHERE create_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) AND status ! 0 GROUP BY day ORDER BY day;热销商品 Top10 则可以从 order_item 表按商品聚合SELECT product_id, product_name, SUM(quantity) AS total_sales FROM order_item GROUP BY product_id, product_name ORDER BY total_sales DESC LIMIT 10;后台统计功能的使用场景是运营人员每天打开后台看订单趋势而这些数据恰好也可以喂给推荐系统的热门商品模块形成两条业务线的联动。4. IDEA 本地运行与源码调试全流程4.1 环境版本匹配原则拿到源码后先别急着启动第一步是检查环境。我见过太多人卡在环境问题上有的折腾一两天最后发现是 JDK 版本不对。这个项目的推荐环境如下组件推荐版本说明JDK1.8项目基于 JDK 8 编译不要用 17 或 21否则会有兼容性问题Maven3.6.33.8 也可以主要看 IDEA 内置的 Maven 是否兼容Tomcat8.5 或 9.0Tomcat 10 是 Jakarta EE 规范不能用在这个 SSM 项目上MySQL5.7 或 8.08.0 需要在 JDBC 连接串里加时区参数IDEA2020.3 及以上社区版也可以但需要额外配置 Maven 的 tomcat 插件这里最需要注意的是 Tomcat 版本。Tomcat 10 开始把 javax 包换成了 jakarta 包而 Spring 5.x 和 MyBatis 等老版本框架用的还是 javax.servlet直接部署会直接启动失败。很多人环境检查半天没发现问题最后才意识到是 Tomcat 版本闹的。还有一个我个人的实操体会如果你是 Mac 用户直接用 IDEA 自带的 Tomcat 集成功能有时候会出现权限问题这个时候改用 Maven 的 tomcat7-maven-plugin 插件运行反而更省心。Windows 用户踩这个坑的概率小一些。4.2 IDEA 导入与 Tomcat 部署的完整步骤我整理一份可以直接照着做的操作清单第一步解压源码确认目录结构。标准 SSM 项目外层有 pom.xml里面有 src 目录其中 main/java 下按 com.xxx.controller、com.xxx.service、com.xxx.mapper 等分包。确认好结构再导入避免导入了一个空壳。第二步用 IDEA 打开项目。选择 File - Open选中项目根目录IDEA 会自动识别 pom.xml 并作为 Maven 项目导入。这个过程会触发 Maven 下载依赖如果网速慢通常需要 5 到 15 分钟。第三步配置本地 Maven。这里建议先在 IDEA 的 Settings - Maven 中把 Maven home path 指向你自己的本地 Maven而不是 IDEA 自带的那个。同时把 settings.xml 里的本地仓库路径确认好再把阿里云镜像加上mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共镜像/name urlhttps://maven.aliyun.com/repository/central/url /mirror这一步能显著提升依赖下载速度尤其是国内网络环境下不加镜像很容易卡在依赖拉取环节。第四步修改数据库配置。在 src/main/resources 下找到 jdbc.properties 或 db.properties把 url、username、password 改成自己本机的数据库连接信息。MySQL 8.0 驱动的 url 要显式加时区jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的密码第五步初始化数据库。用 Navicat 或命令行执行项目附带的 shop.sql 脚本。连接库名必须和 jdbc.properties 里一致否则运行时会报找不到表。执行完以后建议查看一下表是否都建成功尤其是 behavior 和 product_similarity 这两张推荐相关的表如果缺失推荐模块会直接报错。第六步配置 Tomcat。点击 Run - Edit Configurations左上角加号找到 Tomcat Server - Local。如果这个选项不存在说明你的 IDEA 是社区版或者没有安装 Tomcat 集成插件。解决办法是换用 Maven 插件方式运行在 pom.xml 里加一段配置plugin groupIdorg.apache.tomcat.maven/groupId artifactIdtomcat7-maven-plugin/artifactId version2.2/version configuration port8080/port path//path uriEncodingUTF-8/uriEncoding /configuration /plugin配置好后在 IDEA 右侧 Maven 面板找到 tomcat7:run双击即可启动。第七步在 Tomcat 配置的 Deployment 标签页点击加号选择 Artifact把项目的 war 包加进去Application context 建议填 /shop。然后启动浏览器访问 http://localhost:8080/shop 或 http://localhost:8080取决于 path 配置看到首页说明部署成功。4.3 高频问题排查与解决方案我在跑通这个项目的过程中遇到的坑不少挑几个概率最高的整理成表格你直接对照排查问题现象可能原因解决办法启动报错 UnsupportedClassVersionErrorCompiled JDK 版本和运行时 JDK 不一致在 Project Structure 的 Project SDK 和 Java Compiler 里都设为 1.8Maven 依赖下载慢或失败未配置国内镜像在 settings.xml 中配置阿里云镜像数据库连接失败 CommunicationsExceptionMySQL 8.0 驱动连接串缺少时区参数url 加上 serverTimezoneAsia/Shanghai页面中文乱码JSP 编码、数据库编码、连接串编码不一致统一使用 UTF-8建库时指定 utf8mb48080 端口被占用本地其他进程占用了端口修改 Tomcat 端口或在命令行中结束占用进程Mapper 注入失败提示找不到 Beanspring-mybatis.xml 中 mapper-locations 路径不对检查 dao 层 XML 路径通常为 classpath*:mapping/*.xml页面 404Application context 路径不对访问地址要加上部署时的 context path推荐位空白product_similarity 表无数据或 user_behavior 无数据先造行为数据再手动触发相似度计算任务这里我要特别提一个容易忽略的问题这个项目里包含的 SQL 脚本有可能是在 MySQL 5.7 环境下写的如果你用的是 MySQL 8.0要注意排序规则和时区设置。最典型的坑是 MySQL 8.0 默认情况下 JDBC 连接串不加 serverTimezone 就报时区错误这个我在前面已经说过了但确实是最多人遇见的。4.4 推荐效果验证与数据构造技巧把系统跑起来以后很多人会发现推荐位是空的。为什么因为没有用户行为数据相似度表也是空的。这时候需要自己造数据来验证推荐效果。我最推荐的做法是准备一个简单的 SQL 脚本手动往 user_behavior 表里插入几个用户的行为记录。比如造两个用户用户 A 频繁浏览图书分类下的《Java 编程思想》和《Java 并发编程实战》用户 B 频繁浏览食品分类下的咖啡豆和坚果。然后手动触发一次相似度计算再分别用这两个用户登录系统看首页“猜你喜欢”。如果 A 看到的是图书类推荐B 看到的是食品类推荐说明推荐系统已经能区分用户兴趣。这一步非常关键因为很多人在答辩的时候只演示了“能注册、能下单”但推荐模块没有数据展示不出来整个项目的差异化优势就没了。我建议至少准备 3 到 5 个不同兴趣偏好的测试账号每个账号录入不同的行为数据推荐效果会非常直观。另外我看了一下项目里可能已经内置了管理员账号比如 admin/admin后台入口一般在首页底部或登录页切换。如果你拿到源码后不知道管理员密码可以直接在 SQL 脚本里搜一下 user 表的 INSERT 语句通常都是明文或简单 MD5复制出来用就行。5. 项目答辩与面试的几个必问点最后从我的实际经验出发聊几个这个项目在答辩或面试时几乎必被问到的问题建议提前准备第一个问题是“为什么推荐算法选 ItemCF 不选 UserCF”。这个要抓重点讲电商用户数通常远大于商品数计算用户相似度矩阵的代价更高而且用户兴趣漂移快UserCF 维护起来麻烦。同时 ItemCF 在解释性上有天然优势可以说“因为你浏览过 A而 A 和 B 相似度最高所以推荐了 B”。第二个问题是“数据量大了怎么办”。推荐模块目前是单机 MySQL 存储相似度表数据量大了以后要考虑引入 Redis 做缓存把相似度表加载到内存里离线计算部分可以考虑用 Spark 等分布式计算框架替换。回答这类问题的关键是要体现出你思考过扩展性。第三个问题是“冷启动怎么解决的”。答案就是我前面说的三层回退策略无行为数据按热门推荐有浏览按浏览推荐有购买优先按购买推荐。如果老师继续追问新商品冷启动可以说可以给新品加权或者打标签用基于内容的推荐过渡。第四个问题是 SSM 框架层面的“SpringMVC 的请求流程是什么”“MyBatis 里 #{} 和 ${} 的区别”。这两个是 Java 后端面试的经典问题建议把这个项目的实际请求链路对应到面试题的答案中比如用户点“加入购物车”后前端发起 POST 请求DispatcherServlet 怎么找到对应的 ControllerService 怎么处理事务Mapper 怎么通过代理执行 SQL你能把这条链路讲得越细越能证明这个项目是你自己跑通的。我自己在实操这个项目的过程中最大的体会是个性化推荐模块单独拿出来并不复杂真正花时间的是把用户行为埋点、离线计算、在线查询这条数据链路捋顺再加上电商系统的订单事务、库存扣减这些基础功能都要做得稳整个项目才完整。如果你在导入或运行阶段卡在哪一步大概率就是版本问题或者数据库连接配置问题按照我上面给的排查表逐项检查一遍就能解决。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →