
简介基于SpringBoot和Vue.js构建的智能推荐系统项目是一份面向Java开发者、适合学习前后端分离架构与推荐算法落地的完整实战源码。资源共4个文件包含两段演示视频、一份SQL数据库脚本和一个RAR源码压缩包整体大小约58.36MB。演示视频展示系统部署、后端接口调试和前端交互流程SQL脚本可直接初始化项目所需的数据库表RAR源码中覆盖用户管理、商品内容管理、用户行为记录、推荐算法、结果展示与系统配置等核心模块算法部分侧重协同过滤与基于内容推荐。目前已有86人学习下载整体完成度较高。研读源码时可对照视频操作理解Spring Boot数据访问与接口设计、Vue组件化与状态管理以及“用户行为输入—算法计算—推荐结果输出”的完整链路也能复用其中的权限控制和统计设计作为毕业设计或个性化推荐业务开发的有力参考。1. 用 Spring Boot Vue 搭智能推荐系统难点不在框架在推荐策略前后端分离的架子谁都会搭真正的门槛是推荐逻辑能不能落地。市面上“基于 springbootvue 智能推荐系统”这类毕设和练手项目很多但大多数把精力花在 CRUD 和页面渲染上算法部分只用一句“基于协同过滤”带过。实际做下来你会发现推荐系统的工程量 60% 在数据清洗和特征加工上20% 在相似度计算和候选集召回的工程化实现剩下 20% 才是 Vue 页面把结果呈现出来。本文面向已经能独立写 Spring Boot 接口、能跑通 Vue 路由的开发者讲清楚一个可运行的智能推荐系统从算法选型、数据库建模到前后端联调的完整路径重点落实在 ItemCF 的落地实现、冷启动处理和推荐结果的验证方法上。2. 智能推荐系统的算法选型从协同过滤到混合推荐的取舍2.1 三种基础推荐算法在一个系统里的分工智能推荐系统里最常被提起的是协同过滤Collaborative Filtering但实际工程中不会只用一种算法。基于用户的协同过滤UserCF适合社交属性强的场景比如同一公司的人看的资料、同专业的学生选的课基于物品的协同过滤ItemCF适合电商、资讯、视频这类物品变化慢但用户兴趣相对稳定的场景基于内容的推荐Content-based则依赖物品自身的标签、分类、文本特征冷启动阶段最依赖它。一个典型的 springbootvue 推荐系统常见做法是冷启动阶段用基于内容的推荐兜底用户行为数据积累到阈值后切换成 ItemCF 为主、UserCF 为辅的混合策略最后用加权融合的方式输出 Top-N 结果。这样设计的好处是既照顾了新用户没有行为数据的问题又能在数据量上来后提升推荐的个性化程度。2.2 数据模型设计用户、物品、行为三张核心表推荐系统的数据模型不像业务系统那样复杂核心只有三张表用户表、物品表、用户行为表。用户表存放用户基本信息和注册渠道物品表存放被推荐对象的内容信息行为表记录用户的浏览、收藏、评分等动作。建表 SQL 如下-- 用户行为表核心中的核心 CREATE TABLE user_behavior ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 用户ID, item_id BIGINT NOT NULL COMMENT 物品ID, behavior_type TINYINT NOT NULL COMMENT 1-浏览 2-收藏 3-评分 4-购买, score DECIMAL(3,1) DEFAULT NULL COMMENT 评分值仅behavior_type3时有值, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_item (user_id, item_id), KEY idx_item (item_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户行为记录表;字段类型说明behavior_typeTINYINT行为类型浏览权重最低购买权重最高scoreDECIMAL(3,1)评分值1.0 ~ 5.0用于协同过滤的评分矩阵行为表的权重映射通常在代码层定义浏览记 1 分、收藏记 3 分、评分直接用原始分值、购买记 5 分。这样设计的好处是评分矩阵不需要额外建表直接从行为表聚合就能得到用户对物品的兴趣度矩阵。2.3 相似度计算余弦相似度与皮尔逊系数的参数含义相似度计算是协同过滤的核心步骤。余弦相似度适合处理稀疏评分矩阵皮尔逊相关系数则会对用户评分习惯做均值中心化处理。实际项目里我会优先用余弦相似度因为它计算简单、效果稳定而且对稀疏数据不像皮尔逊那样敏感。// 计算两个物品的余弦相似度 public double cosineSimilarity(MapLong, Double itemVector1, MapLong, Double itemVector2) { SetLong commonUsers new HashSet(itemVector1.keySet()); commonUsers.retainAll(itemVector2.keySet()); if (commonUsers.size() 2) { return 0.0; // 共同评分用户太少相似度不可信 } double dotProduct 0.0; double norm1 0.0; double norm2 0.0; for (Long userId : commonUsers) { double score1 itemVector1.get(userId); double score2 itemVector2.get(userId); dotProduct score1 * score2; } for (double score : itemVector1.values()) { norm1 score * score; } for (double score : itemVector2.values()) { norm2 score * score; } return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); }注意commonUsers.size() 2这个阈值的含义如果两个物品只有一个共同评分的用户算出来的相似度很可能是巧合不具备统计意义。这个参数在工程上叫“最小共同评分用户数”推荐系统的面试题里也经常问一般取 2 到 5 之间取太小会引入噪声取太大则会让相似物品矩阵过于稀疏。3. Spring Boot 后端实现从项目分层到 ItemCF 落地的完整路径3.1 项目分层与 Spring Boot 3 的依赖配置后端工程按照controller - service - mapper三层结构划分推荐相关的逻辑单独放在recommend包下。Spring Boot 版本建议直接用 3.xJDK 要求 17 及以上。很多人在 springboot 版本太高的问题上踩坑其实关键点是确认 spring-boot-starter-parent 的版本和 JDK 版本匹配Spring Boot 3.2 搭配 JDK 21 完全没有问题。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativeView/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency /dependencies这里引入 Redis 不是点缀而是推荐系统的刚需。ItemCF 计算出的物品相似度矩阵可以提前缓存到 Redis用户请求推荐时直接读取不必每次实时计算全量相似度。缓存的 key 设计为item_sim:{itemId}value 用 JSON 数组存储与该物品最相似的 N 个物品 ID 和相似度值。3.2 ItemCF 核心服务相似度矩阵计算与 Top-N 物品召回ItemCF 的完整流程分三步先从行为表聚合出用户对物品的评分向量然后计算物品两两之间的相似度最后根据用户历史行为物品的相似物品列表做加权汇总。下面是一个可以直接跑通的最小实现Service public class RecommendService { Autowired private UserBehaviorMapper behaviorMapper; Autowired private RedisTemplateString, String redisTemplate; private static final String ITEM_SIM_KEY item_sim:; // 为用户生成 Top-N 推荐列表 public ListLong recommendForUser(Long userId, int topN) { ListUserBehavior behaviors behaviorMapper.selectList( new LambdaQueryWrapperUserBehavior() .eq(UserBehavior::getUserId, userId) .orderByDesc(UserBehavior::getCreateTime) .last(LIMIT 50) // 只看最近50条行为避免早期兴趣干扰 ); if (behaviors.isEmpty()) { return contentBasedRecommend(userId, topN); // 冷启动走内容推荐 } MapLong, Double scoreMap new HashMap(); for (UserBehavior behavior : behaviors) { Long itemId behavior.getItemId(); String simJson redisTemplate.opsForValue().get(ITEM_SIM_KEY itemId); if (simJson null) { continue; } // 解析相似物品列表加权累加推荐分数 ListSimilarItem simItems JSON.parseArray(simJson, SimilarItem.class); for (SimilarItem sim : simItems) { scoreMap.merge(sim.getItemId(), sim.getSimilarity() * behaviorWeight(behavior), Double::sum); } } // 过滤掉用户已经买过的物品按分数排序返回 return scoreMap.entrySet().stream() .filter(e - !behaviors.stream().anyMatch(b - b.getItemId().equals(e.getKey()))) .sorted(Map.Entry.Long, DoublecomparingByValue().reversed()) .limit(topN) .map(Map.Entry::getKey) .collect(Collectors.toList()); } private double behaviorWeight(UserBehavior behavior) { return switch (behavior.getBehaviorType()) { case 1 - 1.0; // 浏览 case 2 - 3.0; // 收藏 case 3 - behavior.getScore() ! null ? behavior.getScore() : 3.0; // 评分 case 4 - 5.0; // 购买 default - 1.0; }; } }LIMIT 50这个参数值得展开说限制用户行为数量有两个目的一是防止行为很多的老用户被早期行为主导推荐结果二是控制计算开销。推荐系统的时效性很重要用户上周看的书和上周买的东西对当下的兴趣指示作用完全不同所以按时间倒序取最近行为是常见工程手段。3.3 推荐接口设计与 Vue 前端的调用约定后端接口的设计要够简洁让前端拿到就能直接用。推荐接口返回的 JSON 结构约定为{ code: 0, data: [{ itemId, itemName, coverUrl, score }] }。控制层代码如下RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/{userId}) public ResultListRecommendItemVO getRecommendList( PathVariable Long userId, RequestParam(defaultValue 10) Integer size) { ListLong itemIds recommendService.recommendForUser(userId, size); ListRecommendItemVO items itemIds.stream() .map(itemId - recommendService.buildRecommendItem(itemId)) .collect(Collectors.toList()); return Result.success(items); } }size参数用defaultValue 10是为了防止前端漏传导致接口异常。实际项目中这个接口还会有第二个版本即分页加载更多推荐参数变成page和size底层通过记录上次返回的最后一个物品 ID 做游标分页避免深分页的性能问题。3.4 定时刷新相似度矩阵与数据库配置加密相似度矩阵不能只算一次就完事行为数据每天都在增长通常的做法是用Scheduled定时任务每天凌晨跑一次全量相似度计算白天只做增量更新。Component public class RecommendScheduleTask { Autowired private ItemSimilarityService similarityService; // 每天凌晨 2 点刷新全量相似度矩阵 Scheduled(cron 0 0 2 * * ?) public void refreshItemSimilarity() { ListItem allItems itemService.list(); for (Item item : allItems) { MapLong, Double simMap similarityService.calcSimilarItems(item.getId()); String json JSON.toJSONString(simMap); redisTemplate.opsForValue().set(ITEM_SIM_KEY item.getId(), json, 24, TimeUnit.HOURS); } log.info(ItemCF similarity matrix refreshed, total items: {}, allItems.size()); } }数据库连接配置建议用 Jasypt 做密文处理springboot yml 密文配置是这几年面试和生产环境里都绕不开的话题。jasypt.encryptor.password通过环境变量注入yml 里只保留 ENC 开头的密文这样源码泄露也不会直接暴露数据库账号密码。这不是安全洁癖而是推荐系统一旦上线行为数据就是敏感数据数据库口令明文写在配置文件里属于重大事故隐患。4. Vue 前端实现推荐结果展示、交互反馈与播放集成4.1 Vite 创建工程与项目结构设计Vue 部分推荐直接用 Vite 创建 Vue 3 工程vue 安装及环境配置的坑主要在 Node 版本上Vite 5 要求 Node 18建议直接用 nvm 管 Node 版本。创建命令npm create vitelatest recommend-frontend -- --template vue cd recommend-frontend npm install npm install axios vue-router pinia element-plus项目结构按视图和组件拆分src/api放 axios 请求封装src/router放路由配置src/views放页面级组件src/components放推荐卡片、评分弹窗等复用组件。这样的目录在 vue 框架项目里是惯例方便后续维护也方便答辩讲解。4.2 Axios 请求封装与 Vue 路由参数传递axios 封装的核心是统一处理 baseURL、token 和错误码。推荐系统涉及的接口只有少数几个但 token 拦截、请求超时这些必须一开始就配好不然后面加功能很痛苦。// src/api/request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( response { const res response.data if (res.code ! 0) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request路由传参在推荐系统的场景里很典型首页推荐位点击物品后跳转到详情页需要把 itemId 传过去。vue 路由参数有两种说法query 方式和 params 方式query 参数会出现在 URL 里适合分享场景params 参数不暴露在 URL适合内部跳转。// 推荐卡片点击跳转 const goDetail (itemId) { router.push({ name: ItemDetail, params: { id: itemId } }) } // 路由配置 { path: /item/:id, name: ItemDetail, component: () import(/views/ItemDetail.vue) }4.3 推荐结果轮播与评分反馈组件推荐结果展示的常见形式是横向轮播卡片卡片上展示物品封面、名称和推荐分。轮播用 Element Plus 的el-carousel实现评分反馈则用el-rate组件。反馈数据要实时提交到后端作为下一轮推荐的输入。template div classrecommend-section h3为你推荐/h3 el-carousel :interval4000 typecard height240px el-carousel-item v-foritem in recommendList :keyitem.itemId div classrecommend-card clickgoDetail(item.itemId) img :srcitem.coverUrl :altitem.itemName / span classitem-name{{ item.itemName }}/span el-rate v-modeltempScore changesubmitScore(item.itemId)/el-rate /div /el-carousel-item /el-carousel /div /template script setup import { ref, onMounted } from vue import { getRecommendList, submitScore } from /api/recommend const recommendList ref([]) const tempScore ref(0) onMounted(async () { const userId localStorage.getItem(userId) recommendList.value await getRecommendList(userId, 10) }) const submitScore async (itemId) { await submitScore({ userId: localStorage.getItem(userId), itemId, score: tempScore.value, behaviorType: 3 }) } /scriptel-rate的change事件在用户点击星标后触发提交后建议给一个轻提示而不是弹窗打断感太强会降低用户反馈意愿。评分数据进入 user_behavior 表后下一次定时任务刷新相似度矩阵时就会产生作用形成推荐闭环。4.4 推荐物品为视频资源时的 m3u8 播放兼容如果推荐系统推荐的物品是视频课程或影视内容前端的详情页通常要支持直播流或点播流播放。vue 播放 m3u8 的常见方案是 video.js 搭配 videojs-contrib-hls 插件或者用 hls.js 自己封装播放器。推荐系统项目里直接引入 video.js 最省事它内部集成了 HLS 解析能力。import videojs from video.js import video.js/dist/video-js.css // 在 ItemDetail.vue 的 onMounted 中初始化 const player videojs(play-container, { autoplay: false, controls: true, sources: [{ src: videoUrl.value, type: application/x-mpegURL }] })type: application/x-mpegURL是 m3u8 播放的关键配置项少了它 video.js 不知道如何解析流文件。这个能力可以放在推荐系统里作为扩展功能毕竟很多推荐系统的演示视频里用的就是视频类素材。5. 冷启动与推荐效果验证上线前必须处理的两个问题5.1 冷启动的三个处理点冷启动是智能推荐系统里被问得最多的问题java 面试题里也常出现。冷启动分三种用户冷启动、物品冷启动、系统冷启动。用户冷启动的常见做法是注册时让用户勾选感兴趣的分类标签后端根据标签做基于内容的推荐等行为数据攒够 20 条后再切换到协同过滤。物品冷启动则依赖内容特征比如文章的关键词、视频的标题和简介、商品的属性和类目为新物品打上内容标签后就能进入推荐池。系统冷启动最简单粗暴直接用热度榜兜底按行为表里所有用户的行为总量排序保证新系统上线时用户有东西可看。这三层策略要同时启用不能只做一层。很多推荐系统项目只做了协同过滤用户第一次打开什么都没有这在前端体验上是灾难性的。5.2 用离线评测指标验证推荐效果推荐效果不能靠肉眼感觉需要跑离线评测。最常用的是准确率和召回率但推荐系统里更实用的是 PrecisionN 和 RecallN。把用户行为数据按时间切分前 70% 做训练集后 30% 做测试集用训练集生成推荐列表看测试集里的物品有多少被推荐出来了。// 计算 PrecisionN public double calcPrecision(MapLong, ListLong recommendMap, MapLong, ListLong testMap, int N) { int hitCount 0; int totalCount 0; for (Long userId : recommendMap.keySet()) { ListLong recList recommendMap.get(userId).stream().limit(N).toList(); ListLong testList testMap.getOrDefault(userId, Collections.emptyList()); for (Long itemId : recList) { if (testList.contains(itemId)) { hitCount; } totalCount; } } return totalCount 0 ? 0.0 : (double) hitCount / totalCount; }指标计算方式含义PrecisionN命中推荐数 / 推荐总数推荐列表里有用的占比RecallN命中推荐数 / 测试集物品数用户真实感兴趣的物品被推荐出来的比例Coverage推荐出去的物品数 / 总物品数推荐系统的多样性覆盖率太低说明只会推热门这三个指标在生产环境的监控面板上每天看一次指标下降说明推荐算法在退化需要重新训练或调整权重参数。很多时候推荐的采集链路出问题表现也是指标下降所以这三个指标也是排查数据链路是否正常的信号。5.3 前后端联调的五个必查配置第一Spring Boot 的跨域配置Vue 开发服务器在 5173 端口后端在 8080 端口跨域不配好前端永远调不通。第二axios 的 baseURL 要区分开发环境和生产环境开发环境用 vite 的 proxy 代理生产环境用 Nginx 反向代理到后端端口。第三Redis 的序列化方式要统一用GenericJackson2JsonRedisSerializer否则存进去的 java 对象取出来反序列化会报类型异常。第四定时任务在生产环境要加分布式锁多个实例部署时避免重复计算相似度矩阵。第五用户 ID 的传递要在登录接口拿到 token 后统一从 token 解析不要前端传什么就信什么。这五个配置点看着琐碎但任何一个出了问题推荐列表要么永远加载不出来要么报 500 错误要么返回的数据前端解析不了。debug 的时候不要怀疑推荐算法写错了先排查这五个点能省下大半天的排查时间。本文还有配套的精品资源点击获取