ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot+Vue旅游推荐系统:协同过滤与内容推荐双路实现

2026/9/13 6:00:05 拓冰建站 浏览量
Spring Boot+Vue旅游推荐系统:协同过滤与内容推荐双路实现 简介本资源是一套基于Spring Boot后端与Vue前端技术栈构建的旅游推荐系统完整实现面向计算机专业本科生、毕业设计学生及Java/前端初学者解决课程设计、期末大作业与毕业项目选题难、代码质量低、文档缺失等实际痛点。压缩包为ZIP格式大小27.1MB包含可直接运行的前后端源码、详细部署说明文档及全量中文注释核心文件涵盖Spring Boot主模块Controller/Service/Entity、Vue组件页面、数据库SQL脚本及API接口文档结构清晰、模块解耦便于理解推荐逻辑与前后端交互流程。已有330人学习下载项目为作者手打高分实践成果评分98分获导师高度认可代码规范、注释详尽、部署简易——下载解压后按文档配置MySQL与Node环境即可本地启动并体验用户注册、景点检索、个性化推荐等核心功能是快速掌握全栈开发与推荐算法落地的优质学习范例。1. 这不是又一个“旅游网站”而是一个能跑通协同过滤内容推荐双路逻辑的 Spring Boot Vue 全栈可验证系统你手头可能已经下载过几十个标着“旅游推荐”的毕设源码解压后发现只有静态页面、硬编码景点列表、连登录都用 localStorage 模拟——这种项目在答辩现场被问一句“推荐算法怎么实现的”当场就卡壳。但这次不一样它内置了基于用户行为日志的协同过滤UserCF和基于景点标签/描述的 TF-IDF 余弦相似度双路推荐引擎后端用 Spring Boot 2.7.x 实现推荐服务接口前端 Vue 3Composition API完成动态权重切换与实时推荐结果渲染。整个系统从 MySQL 存储用户点击流、景点元数据、标签体系到 Nginx 反向代理部署全部可本地复现。适合正在做课程设计、毕业设计或想补全“推荐系统落地链路”知识断点的 Java/前端开发者——尤其当你需要向导师证明你不仅会搭框架还能让推荐结果有据可依、参数可调、效果可测。2. 推荐模块技术选型与核心算法实现解析2.1 为什么不用纯规则或热门榜协同过滤与内容推荐的边界在哪里很多初学者误以为“推荐查热门景点”但真实场景中热门景点对新用户无效冷启动、对小众偏好用户失准长尾缺失。本系统明确划分两类推荐触发路径协同过滤UserCF适用于已有行为数据的用户如点击、收藏、评分通过计算用户间行为相似度Jaccard 或余弦找出“相似用户喜欢但当前用户未接触过的景点”内容推荐Content-Based适用于新用户或行为稀疏用户基于景点标题、描述、标签如“古镇”“亲子”“摄影”提取 TF-IDF 特征向量再用余弦相似度匹配用户历史偏好向量。提示系统默认启用双路融合策略加权平均权重alpha0.6表示协同过滤占主导该值可在application.yml中动态调整无需重启服务。2.2 后端推荐服务层代码结构与关键实现Spring Boot 后端推荐模块位于com.example.tour.recommend包下核心类为RecommendService其方法签名与逻辑如下Service public class RecommendService { Autowired private UserBehaviorMapper userBehaviorMapper; // MyBatis Mapper查用户行为日志 Autowired private ScenicSpotMapper scenicSpotMapper; // 查景点基础信息与标签 Value(${recommend.alpha:0.6}) private double alpha; // 协同过滤权重YAML 可配 public ListScenicSpot getRecommendations(Long userId, int limit) { // Step 1: 获取用户历史行为点击/收藏/评分 ListUserBehavior behaviors userBehaviorMapper.findByUserId(userId); if (behaviors.isEmpty()) { return getContentRecommendations(limit); // 冷启动走内容推荐 } // Step 2: 计算协同过滤推荐UserCF ListScenicSpot cfRecs calculateUserCF(userId, limit); // Step 3: 计算内容推荐基于用户历史景点标签聚合 ListScenicSpot cbRecs calculateContentBased(behaviors, limit); // Step 4: 加权融合去重 排序 return mergeAndRank(cfRecs, cbRecs, limit); } }2.2.1 协同过滤UserCF实现细节calculateUserCF()方法不依赖 Spark 或 Mahout而是采用内存级相似度计算适用于中小规模数据10 万用户用户相似度使用修正余弦相似度Adjusted Cosine消除用户打分偏置相似用户 Top-K 取 K5避免长尾噪声候选景点仅限“相似用户有过行为但当前用户未交互”的集合通过LEFT JOINIS NULL在 SQL 层完成预过滤降低 Java 层计算压力。对应 SQL 片段UserBehaviorMapper.xmlselect idfindSimilarUsers resultTypemap SELECT ub2.user_id AS similar_user_id, COUNT(*) AS common_items FROM user_behavior ub1 INNER JOIN user_behavior ub2 ON ub1.scenic_spot_id ub2.scenic_spot_id WHERE ub1.user_id #{userId} AND ub2.user_id ! #{userId} GROUP BY ub2.user_id ORDER BY common_items DESC LIMIT 5 /select2.2.2 内容推荐TF-IDF 余弦实现要点内容特征向量构建在ContentRecommender.java中完成景点标签scenic_spot.tag与描述scenic_spot.description拼接为文本使用org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer分词已集成至pom.xmlTF-IDF 权重由TfIdfVectorizer类在线计算词典缓存在ConcurrentHashMap中支持增量更新用户偏好向量 所有历史景点向量的加权平均权重为行为类型点击1收藏2评分3。关键参数说明参数位置默认值作用maxFeaturesTfIdfVectorizer构造参数5000限制词典大小防止稀疏爆炸ngramRange分词后滑动窗口(1,2)支持“江南水乡”等二元词组minDfTfIdfVectorizer2过滤低频词出现少于2次的词丢弃3. Vue 3 前端推荐结果动态渲染与交互逻辑3.1 推荐结果卡片组件的响应式设计与状态管理Vue 前端推荐页src/views/RecommendView.vue采用 Composition API Pinia 管理推荐状态。核心逻辑不在mounted中一次性拉取而是按需触发script setup import { ref, onMounted, watch } from vue import { useRecommendStore } from /stores/recommend import { getRecommendations } from /api/recommend const recommendStore useRecommendStore() const loading ref(false) const error ref() // 初始化首次加载默认推荐userId1 onMounted(async () { await loadRecommendations(1) }) // 监听用户切换如登录态变更 watch(() recommendStore.userId, async (newId) { if (newId) await loadRecommendations(newId) }) const loadRecommendations async (userId) { loading.value true error.value try { const res await getRecommendations(userId, 8) // 每页8条 recommendStore.setRecommendations(res.data) } catch (e) { error.value e.response?.data?.message || 推荐加载失败 } finally { loading.value false } } /script3.1.1 推荐来源标识与权重切换 UI每个推荐卡片右上角显示来源徽章Badge typecfCF/Badge或Badge typecbCB/Badge并支持手动切换推荐策略!-- src/components/RecommendSourceSwitcher.vue -- template div classswitcher button :class{ active: mode cf } clicksetMode(cf) 协同过滤/button button :class{ active: mode cb } clicksetMode(cb) 内容推荐/button button :class{ active: mode hybrid } clicksetMode(hybrid) 混合推荐/button /div /template script setup import { useRecommendStore } from /stores/recommend const recommendStore useRecommendStore() const mode computed({ get: () recommendStore.recommendMode, set: (v) recommendStore.setMode(v) }) /script该组件绑定recommendStore.recommendMode当用户点击“内容推荐”时前端自动向/api/recommend/content?userId1limit8发起请求绕过混合逻辑直连内容推荐接口。3.2 推荐结果排序与多样性保障机制为避免推荐结果同质化如全是“古镇”类景点前端在渲染前执行二次重排多样性打散Diversity Re-ranking按景点所属省份、标签一级分类tag_level1进行哈希分桶每桶最多取2条新鲜度加权对created_time距今 30 天的景点分数 × 1.2用户反馈闭环卡片底部“不感兴趣”按钮触发POST /api/user/feedback记录userId,scenicSpotId,typedislike该数据将进入下一轮协同过滤的负样本池。对应重排逻辑utils/recommendUtils.jsexport function diversityRerank(spots, maxPerBucket 2) { const buckets new Map() spots.forEach(spot { const bucketKey ${spot.province}_${spot.tag_level1} // 如 江苏_古镇 if (!buckets.has(bucketKey)) buckets.set(bucketKey, []) buckets.get(bucketKey).push(spot) }) const result [] for (const [key, bucket] of buckets) { result.push(...bucket.slice(0, maxPerBucket)) } return result.slice(0, 8) // 截断至8条 }4. 本地快速部署与关键配置项详解4.1 五步完成本地运行Windows/macOS/Linux 通用系统已预置 H2 内存数据库脚本无需额外安装 MySQL 即可启动验证。完整流程如下步骤 1确认 JDK 11 与 Node.js 16 已安装java -version # 应输出 11.0.x 或 17.0.x node -v # 应输出 v16.20.x 或 v18.17.x步骤 2启动后端Spring Bootcd backend mvn clean package -DskipTests java -jar target/tour-recommend-0.0.1-SNAPSHOT.jar注意首次启动会自动执行schema-h2.sql和>cd frontend npm install npm run dev浏览器访问http://localhost:5173默认进入推荐页用户 ID 1 已预置登录态。步骤 4验证推荐接口可用性curl http://localhost:8080/api/recommend/1?limit5 # 返回 JSON 数组含 scenicSpotId、name、score、source 字段步骤 5修改数据库为 MySQL生产就绪编辑backend/src/main/resources/application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/tour_db?useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password jpa: hibernate: ddl-auto: validate # 生产环境禁用 create-drop执行backend/src/main/resources/sql/mysql-init.sql初始化表结构。4.2 关键配置项与调优建议配置项文件位置默认值修改建议影响范围recommend.alphaapplication.yml0.6新用户多时调至0.3行为数据丰富时升至0.7混合推荐权重recommend.cf.kRecommendService.java5数据量 5 万用户时建议降至3防相似用户噪声UserCF 性能与精度tfidf.maxFeaturesTfIdfVectorizer.java5000标签体系超 200 个时可增至8000内容推荐覆盖度server.portapplication.yml8080若端口冲突改为此值并同步修改frontend/vite.config.js中proxy目标前后端联调提示所有配置项均支持运行时热更新需启用 Spring Boot Actuator Config Server但本项目为轻量部署默认关闭。如需热更新添加spring-boot-starter-actuator依赖并开放/actuator/refresh端点。5. 推荐效果验证与 AB 测试快速接入方案5.1 本地验证推荐逻辑是否生效的三类检查点不要只看页面是否渲染要验证推荐是否“真有效”。以下是三个必查环节检查点 1协同过滤是否识别出相似用户登录用户 ID2已预置行为点击“乌镇”、收藏“西塘”调用接口curl http://localhost:8080/api/recommend/cf/2?limit3预期返回应包含“南浔古镇”因乌镇/西塘/南浔同属浙江古镇且用户 3、4 也点击过南浔若返回“三亚亚龙湾”则说明相似度计算逻辑有误地域/标签维度未加权。检查点 2内容推荐是否响应标签变化修改景点 ID5“稻城亚丁”的tag字段为高原,摄影,徒步重启后对新用户ID999调用curl http://localhost:8080/api/recommend/content?userId999limit3应出现“冈仁波齐”“四姑娘山”等含“高原”“徒步”标签的景点而非“鼓浪屿”“外滩”。检查点 3混合推荐是否真正融合对比以下三组请求结果/api/recommend/1?modecf→ 记录前3条 ID/api/recommend/1?modecb→ 记录前3条 ID/api/recommend/1?modehybrid→ 检查返回 ID 是否为前两组的加权并集非简单拼接且排序分值介于两者之间。5.2 快速接入 AB 测试的最小改造方案若需向导师演示“不同推荐策略对点击率的影响”无需引入复杂平台只需两处轻量改造改造 1后端增加策略路由开关在RecommendController.java中新增GetMapping(/ab/{strategy}) public ResultListScenicSpot getABRecommend(PathVariable String strategy, RequestParam Long userId, RequestParam(defaultValue 8) int limit) { if (a.equals(strategy)) { return Result.success(recommendService.getCFRecommendations(userId, limit)); } else if (b.equals(strategy)) { return Result.success(recommendService.getContentRecommendations(userId, limit)); } throw new IllegalArgumentException(Unknown strategy: strategy); }改造 2前端埋点统计点击来源在RecommendCard.vue的click事件中加入handleClick() { // 上报点击事件用户ID、景点ID、推荐策略A/B、曝光位置 axios.post(/api/track/click, { userId: this.userId, scenicSpotId: this.spot.id, strategy: this.$route.params.strategy || hybrid, position: this.index }) window.open(/scenic/${this.spot.id}, _blank) }注意/api/track/click接口只需简单写入 MySQLclick_log表字段id, user_id, scenic_spot_id, strategy, position, created_time后续用 Excel 或 Python pandas 即可统计 A/B 组点击率差异。这是课程设计答辩中最易讲清、最易复现的“数据验证”环节。本文还有配套的精品资源点击获取