简介:本资源是一份面向计算机专业本科生的旅游景点个性化推荐系统毕业设计完整文档,聚焦智慧旅游场景下的协同过滤算法实践与B/S架构开发。内容涵盖系统需求分析、MVC三层架构设计、JSP+Servlet服务端实现、关系型数据库设计及旅游分类展示、用户标注、个性化推荐引擎等核心模块说明,附有摘要、关键词、目录、关键技术章节(含协同过滤原理与应用)及参考文献,具备教学示范性与工程参考价值。资源为1个1.35MB的docx文件,结构完整,可直接用于课程设计参考、毕设开题与技术方案借鉴。目前已有99人学习下载,适合Java Web初学者理解推荐系统落地流程,也便于快速掌握旅游类垂直应用中前台交互、后台逻辑与数据协同的设计要点。
1. 这不是又一个“毕设Demo”:它是一套能跑通协同过滤全流程的Java+JSP旅游推荐系统,含真实景点数据、可调试算法、完整MVC分层和青海本地化业务逻辑
你可能已经见过几十个标着“旅游推荐系统”的Java毕设压缩包——解压后只有3个JSP页面、一个空数据库、README里写着“算法未实现”。但这份资源不一样。它来自某高校真实本科毕设(20XX年12月定稿),完整实现了基于用户行为的协同过滤推荐引擎,不是伪代码,不是调用现成库,而是用纯Java手写相似度计算、邻居选取、加权评分预测三步核心逻辑;它不是抽象模型,而是扎根青海——茶卡盐湖、塔尔寺、青海湖等56个真实景点被建模为结构化数据,按“夏季旅游”“文化旅游”“高原精品线路”等5类垂直标签组织;它不是单页HTML,而是标准B/S三层架构:JSP做View、Servlet做Controller、JavaBean封装Model,所有请求走/servlet/*路径,数据库操作全部通过JDBC连接池完成。如果你正卡在“怎么把协同过滤从论文搬到JSP页面上”,或者需要一个有真实业务闭环、能改能调、带完整SQL建表语句和用户行为日志模拟逻辑的参考项目,这份资源就是你该停下来的那个。它不追求高并发或微服务,但每一步都经得起debug:从用户点击“喜欢”触发喜好表更新,到后台定时任务扫描user_tour_preference表生成推荐列表,再到首页JSP用<c:forEach>渲染TOP5推荐,链路清晰、变量可追踪、错误有堆栈。新手能照着部署跑起来,熟手能直接抠出推荐模块集成进自己的Spring Boot项目——毕竟,它的协同过滤核心类CollaborativeFilteringEngine.java只有287行,没任何框架黑盒。
2. 从零部署:环境配置、数据库初始化与JSP工程结构还原
2.1 环境要求与Tomcat适配要点
这不是一个能直接丢进IDEA里点运行的现代Spring Boot项目。它依赖经典Java EE技术栈,对运行环境有明确约束:
- JDK版本:必须为JDK 1.8(非11或17)。原文档明确要求“Windows10 + Apache2.0以上”,而Apache Tomcat 8.5是唯一经过实测兼容的容器。Tomcat 9+因Servlet 4.0规范变更,会导致
web.xml中<jsp-config>标签解析失败,页面报HTTP Status 500 – Internal Server Error且无有效日志。 - 数据库:MySQL 5.7(非8.0)。关键原因在于
user_tour_preference表的count字段类型为TEXT(非INT),而MySQL 8.0默认启用严格模式,插入空字符串会触发Data truncation异常。必须在MySQL配置文件my.cnf中添加sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION并重启服务。 - IDE工具链:推荐使用Eclipse Java EE版(非IntelliJ IDEA Community)。原因在于JSP编译器对
<%@ page import="java.util.*" %>这类静态导入的处理更稳定;若坚持用IDEA,需手动在Project Structure → Artifacts中勾选“Exploded”输出,并将WEB-INF/lib下的mysql-connector-java-5.1.47.jar加入Module Dependencies,否则启动时抛ClassNotFoundException: com.mysql.jdbc.Driver。
提示:不要试图用Maven自动管理依赖。该项目所有jar包(
javax.servlet-api-3.1.0.jar、jstl-1.2.jar、standard-1.1.2.jar)均放在WEB-INF/lib目录下,这是JSP 2.0时代标准做法。强行引入Maven会破坏web.xml中<taglib>路径映射,导致JSTL标签如<c:if>无法解析。
2.2 数据库建表与青海景点数据注入
数据库设计遵循最小必要原则,仅3张核心表支撑全部功能。执行以下SQL前,请确认已创建名为tourism_db的数据库(字符集utf8mb4,排序规则utf8mb4_unicode_ci):
-- 用户表:存储注册信息与权限标识 CREATE TABLE `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `uid` VARCHAR(50) NOT NULL COMMENT '用户标识(登录名)', `type` ENUM('super','normal') DEFAULT 'normal' COMMENT '用户类型', `name` VARCHAR(100) DEFAULT NULL COMMENT '姓名', `password` VARCHAR(100) NOT NULL COMMENT '密码(明文存储,毕设简化处理)', `tel` VARCHAR(20) DEFAULT NULL COMMENT '电话', `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`), UNIQUE KEY `uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 景点表:56个青海真实景点,含季节适配与分类标签 CREATE TABLE `tourist_spot` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(200) NOT NULL COMMENT '景点名称', `time` VARCHAR(50) DEFAULT NULL COMMENT '适合季节(如:夏季、全年)', `count` TEXT COMMENT '累计游客数(文本型,兼容“超100万”等描述)', `resouce` TEXT COMMENT '推荐理由', `place` VARCHAR(100) DEFAULT NULL COMMENT '所在地点(如:海西州、西宁市)', `resource` VARCHAR(50) DEFAULT NULL COMMENT '景点分类(夏季旅游/文化旅游/高原精品线路/亲子旅游/其他精品线路)', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用户-景点偏好表:记录用户对景点的交互行为 CREATE TABLE `user_tour_preference` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `userId` VARCHAR(50) NOT NULL COMMENT '关联user.uid', `tourId` INT(11) NOT NULL COMMENT '关联tourist_spot.id', `count` TEXT COMMENT '喜好次数(文本型,支持“多次”“强烈推荐”等非数值描述)', `rsrvStr1` VARCHAR(100) DEFAULT NULL COMMENT '预留字段1', `rsrvStr2` VARCHAR(100) DEFAULT NULL COMMENT '预留字段2', `rsrvStr3` VARCHAR(100) DEFAULT NULL COMMENT '预留字段3', PRIMARY KEY (`id`), KEY `idx_user_tour` (`userId`,`tourId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:
count字段设计为TEXT是刻意为之——它不用于数学计算,而是记录用户行为强度(如“点击3次”“收藏+评论”),避免整型溢出风险;resource字段值必须严格匹配5个分类字符串,否则推荐引擎的getCategoryTopSpots()方法会返回空集合;user_tour_preference表的联合索引idx_user_tour是性能关键,缺失会导致协同过滤计算时SELECT * FROM user_tour_preference WHERE userId=?全表扫描。
2.3 JSP工程结构还原与关键文件定位
项目采用标准Java Web目录结构,但存在两个易被忽略的细节:
/WEB-INF/web.xml是控制中枢:所有Servlet映射、JSP配置、欢迎文件均在此定义。特别注意<welcome-file-list>中指定index.jsp为首页,而<servlet-mapping>将/RecommendServlet路径绑定到com.tour.servlet.RecommendServlet.class——这是推荐引擎的入口。/src目录下Java类包结构决定运行逻辑:com.tour.bean.*:存放User.java、TouristSpot.java等JavaBean,属性名与数据库字段完全一致(如User.password对应user.password),这是JSP中<jsp:useBean>能自动绑定的前提。com.tour.servlet.*:LoginServlet.java处理登录验证,RecommendServlet.java执行协同过滤,其doPost()方法内调用CollaborativeFilteringEngine.generateRecommendations(userId)生成结果并存入request.setAttribute("recommendList", list)。com.tour.util.*:DBUtil.java封装JDBC连接池,使用BasicDataSource(Apache Commons DBCP),最大连接数设为10,超时时间30秒——此参数在高并发测试时需调整。
逻辑说明:当用户访问
http://localhost:8080/tourism/index.jsp,页面通过<jsp:include page="/servlet/RecommendServlet?userId=${sessionScope.user.uid}"/>动态包含推荐内容。这触发RecommendServlet执行:先查user_tour_preference获取该用户历史偏好,再扫描全表计算与其他用户的皮尔逊相关系数,选取Top5相似用户,最后加权聚合他们偏好的景点ID,去重后按热度排序返回。整个过程无缓存,每次请求实时计算——这是毕设级系统的典型特征,也是你调试算法时能看清每一步数据流的基础。
3. 协同过滤算法落地:手写Java实现、参数调优与冷启动应对策略
3.1CollaborativeFilteringEngine.java核心逻辑拆解
该类是整个系统的灵魂,287行代码覆盖协同过滤三大步骤。以下为关键片段及参数含义:
// 计算用户A与用户B的皮尔逊相关系数(Pearson Correlation) public double calculatePearsonCorrelation(String userA, String userB) { // 步骤1:获取两用户共同评价过的景点ID集合 List<Integer> commonSpots = getCommonSpots(userA, userB); if (commonSpots.size() < 2) return 0.0; // 共同景点少于2个,相关性无意义 // 步骤2:获取双方对这些景点的喜好强度(从user_tour_preference.count字段提取数值) List<Double> ratingsA = new ArrayList<>(); List<Double> ratingsB = new ArrayList<>(); for (Integer spotId : commonSpots) { double ratingA = extractRating(getPreference(userA, spotId)); // 自定义解析函数,将"强烈推荐"→3.0,"一般"→1.5 double ratingB = extractRating(getPreference(userB, spotId)); ratingsA.add(ratingA); ratingsB.add(ratingB); } // 步骤3:标准皮尔逊公式计算(省略均值、协方差计算细节) double numerator = 0.0, sumA = 0.0, sumB = 0.0; for (int i = 0; i < ratingsA.size(); i++) { double diffA = ratingsA.get(i) - getMean(ratingsA); double diffB = ratingsB.get(i) - getMean(ratingsB); numerator += diffA * diffB; sumA += diffA * diffA; sumB += diffB * diffB; } return numerator / Math.sqrt(sumA * sumB); // 返回[-1,1]区间相关系数 }参数说明:
extractRating()是业务关键——它将user_tour_preference.count中的文本描述映射为数值:"强烈推荐"→3.0,"推荐"→2.0,"一般"→1.0,"不感兴趣"→0.0。这个映射规则写死在代码中,若你的业务需要更精细分级,需修改此处;getCommonSpots()使用SQLINNER JOIN查询,效率依赖idx_user_tour索引;皮尔逊计算要求共同景点≥2个,否则返回0,避免噪声干扰。
3.2 邻居选取与推荐生成:generateRecommendations()全流程
该方法接收userId,输出List<TouristSpot>推荐列表:
public List<TouristSpot> generateRecommendations(String userId) { // Step 1: 获取当前用户已评价的景点ID集合 Set<Integer> userRatedSpots = getUserRatedSpots(userId); // Step 2: 扫描所有其他用户,计算与当前用户的相似度,取Top5 List<UserSimilarity> similarUsers = new ArrayList<>(); String sql = "SELECT DISTINCT userId FROM user_tour_preference WHERE userId != ?"; // ... 执行查询,对每个候选用户调用calculatePearsonCorrelation() ... similarUsers.sort((a, b) -> Double.compare(b.similarity, a.similarity)); // 降序排列 List<UserSimilarity> topNeighbors = similarUsers.subList(0, Math.min(5, similarUsers.size())); // Step 3: 对每个Top邻居,收集其未被当前用户评价过的景点,并加权评分 Map<Integer, Double> candidateScores = new HashMap<>(); for (UserSimilarity neighbor : topNeighbors) { Set<Integer> neighborRated = getUserRatedSpots(neighbor.userId); neighborRated.removeAll(userRatedSpots); // 排除当前用户已评景点 for (Integer spotId : neighborRated) { double rating = extractRating(getPreference(neighbor.userId, spotId)); double weightedScore = neighbor.similarity * rating; // 相似度 × 邻居评分 candidateScores.merge(spotId, weightedScore, Double::sum); // 累加加权分 } } // Step 4: 按加权分降序,取Top10,查询景点详情 return candidateScores.entrySet().stream() .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed()) .limit(10) .map(entry -> getTouristSpotById(entry.getKey())) .collect(Collectors.toList()); }逻辑说明:这里没有使用矩阵分解或深度学习,而是经典基于用户的协同过滤(User-Based CF)。关键设计点:
- 冷启动缓解:当
topNeighbors为空(即无相似用户)时,代码默认回退到getHotSpotsByCategory()——按景点分类统计user_tour_preference中count字段出现频次,返回该分类下TOP5热门景点。这是应对新用户无行为数据的务实方案;- 权重设计:
weightedScore = similarity * rating,而非简单平均,确保高相似度用户的评分影响力更大;- 去重机制:
neighborRated.removeAll(userRatedSpots)保证推荐的是用户未接触过的新景点,避免“已看过还推荐”的尴尬。
3.3 冷启动与稀疏性问题的实战应对
协同过滤在旅游场景天然面临两大挑战:新用户无行为、小众景点交互稀疏。该项目通过三层策略应对:
- 注册时强制分类选择:用户注册页面
register.jsp包含5个单选按钮(<input type="radio" name="category" value="夏季旅游">),提交时将选择存入user.category字段(需在user表新增此列)。RecommendServlet检测到新用户(user_tour_preference无记录)时,直接调用getCategoryTopSpots(user.category)返回该分类下热度最高的5个景点。 - 热度衰减因子:在
getHotSpotsByCategory()方法中,景点热度计算公式为hotScore = log10(totalClicks + 1) * (1 / (daysSinceFirstRecord + 1)),其中daysSinceFirstRecord从tourist_spot表中create_time字段(需手动添加)计算。这使青海湖(常年热门)不会永远压制新开的祁连山草原线路。 - 混合推荐兜底:当协同过滤结果不足5个时,自动拼接
getHotSpotsByCategory()结果补足。代码位于RecommendServlet.java第89行:if (recommendList.size() < 5) { recommendList.addAll(getHotSpotsByCategory(category)); }。
血泪经验:曾有学生将
user_tour_preference.count字段改为INT类型并尝试用SUM()聚合,结果发现“强烈推荐”被转为0导致推荐失效。记住:旅游推荐中的“喜好强度”是语义化的,不是数值型的——用文本字段+业务解析函数,比强求数据库类型更可靠。
4. JSP页面交互与推荐结果渲染:从Servlet到前端的完整数据链路
4.1RecommendServlet如何将算法结果传递给JSP
Servlet与JSP的数据传递不是靠全局变量,而是严格的request作用域绑定。关键代码在RecommendServlet.java的doPost()末尾:
// ... 协同过滤计算完成后 ... List<TouristSpot> recommendList = engine.generateRecommendations(userId); request.setAttribute("recommendList", recommendList); // 绑定到request request.setAttribute("userId", userId); // 同时传递用户ID,供JSP中生成“喜欢”按钮 RequestDispatcher dispatcher = request.getRequestDispatcher("/index.jsp"); dispatcher.forward(request, response); // 转发至首页,非重定向逻辑说明:
forward()是关键——它保持同一request对象,使index.jsp能通过request.getAttribute("recommendList")获取数据。若误用response.sendRedirect(),则request对象销毁,JSP中<%= request.getAttribute("recommendList") %>将返回null。这是JSP初学者最常翻车的点。
4.2index.jsp中推荐区域的JSTL渲染模板
首页推荐区使用标准JSTL标签库,结构清晰可复用:
<!-- 首页推荐区块 --> <div class="recommend-section"> <h3>为您推荐</h3> <c:choose> <c:when test="${empty recommendList}"> <p class="no-data">暂无个性化推荐,看看热门景点吧</p> </c:when> <c:otherwise> <div class="spot-grid"> <c:forEach items="${recommendList}" var="spot" varStatus="status"> <div class="spot-card"> <img src="images/${spot.id}.jpg" alt="${spot.name}" onerror="this.src='images/default.jpg'"> <h4>${spot.name}</h4> <p class="spot-meta"> <span>📍${spot.place}</span> | <span>☀️${spot.time}</span> | <span>🏷️${spot.resource}</span> </p> <p class="spot-desc">${fn:substring(spot.resouce, 0, 30)}...</p> <div class="spot-actions"> <a href="spotDetail.jsp?id=${spot.id}" class="btn btn-primary">查看详情</a> <button class="btn btn-like" onclick="likeSpot('${sessionScope.user.uid}', ${spot.id})"> ❤️ 喜欢 </button> </div> </div> <c:if test="${status.index % 3 == 2}"><div class="clearfix"></div></c:if> </c:forEach> </div> </c:otherwise> </c:choose> </div>参数说明:
<c:forEach>遍历recommendList,varStatus="status"提供循环索引,status.index % 3 == 2实现每行3列布局(CSS中.spot-grid设为display: flex; flex-wrap: wrap);onerror="this.src='images/default.jpg'"是前端容错必备,避免景点图片缺失导致页面断裂;onclick="likeSpot(...)"调用JavaScript函数,最终发送AJAX请求到/servlet/LikeServlet,该Servlet将记录插入user_tour_preference表并更新count字段为"推荐"——这是用户行为数据闭环的起点。
4.3 用户行为反馈的AJAX实现:likeSpot()函数详解
index.jsp底部引入js/like.js,其中likeSpot()函数处理“喜欢”点击:
function likeSpot(userId, spotId) { // Step 1: 检查用户是否已登录(sessionScope.user.uid存在) if (!userId || userId === 'null') { alert('请先登录再点赞'); return; } // Step 2: 发送POST请求到LikeServlet fetch('/tourism/servlet/LikeServlet', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'userId=' + encodeURIComponent(userId) + '&spotId=' + spotId }) .then(response => response.json()) .then(data => { if (data.success) { // Step 3: 更新按钮状态,防止重复点击 event.target.textContent = '✅ 已喜欢'; event.target.disabled = true; // Step 4: 触发页面局部刷新(可选) location.reload(); // 简单粗暴,或改用DOM操作更新计数 } else { alert('点赞失败:' + data.message); } }) .catch(error => console.error('Error:', error)); }逻辑说明:
LikeServlet.java接收到请求后,执行INSERT INTO user_tour_preference (userId, tourId, count) VALUES (?, ?, '推荐')。这里count字段写死为'推荐',而非数值,正是为了与CollaborativeFilteringEngine.extractRating()的解析规则匹配。前端AJAX与后端Servlet的参数名(userId,spotId)必须完全一致,否则request.getParameter()返回null——这是调试时NullPointerException的常见源头。
5. 避坑指南:5个真实踩过的坑与血泪解决方案
5.1 现象:首页轮播图不显示,控制台报404
原因:轮播图图片路径硬编码为/images/1.jpg,但实际文件存放在/WebContent/images/目录下。Tomcat部署时,/WebContent成为应用根目录,因此正确路径应为images/1.jpg(相对路径),而非/images/1.jpg(绝对路径)。
解决:打开index.jsp,将所有<img src="/images/...">改为<img src="images/...">。同时检查/WebContent/images/目录下是否存在对应编号的JPG文件(如1.jpg,2.jpg),缺失则从附录提供的素材包补全。
5.2 现象:登录成功后跳转到空白页,URL停留在/servlet/LoginServlet
原因:LoginServlet.java中response.sendRedirect("index.jsp")写成了response.sendRedirect("/index.jsp")。前者是相对路径(相对于当前应用上下文),后者是绝对路径(相对于服务器根目录),导致Tomcat找不到/index.jsp。
解决:修改LoginServlet.java第62行,删除sendRedirect参数开头的斜杠:response.sendRedirect("index.jsp");。同理检查RegisterServlet.java中的跳转逻辑。
5.3 现象:协同过滤推荐结果总是空,recommendList为null
原因:CollaborativeFilteringEngine.java中getCommonSpots()方法的SQL查询未加WHERE条件过滤user_tour_preference表的有效记录,导致扫描全表时因count字段为空字符串而中断。
解决:在getCommonSpots()的SQL中添加AND count IS NOT NULL AND count != ''条件。例如:
SELECT t1.tourId FROM user_tour_preference t1 INNER JOIN user_tour_preference t2 ON t1.tourId = t2.tourId WHERE t1.userId = ? AND t2.userId = ? AND t1.count IS NOT NULL AND t1.count != ''5.4 现象:JSP页面中文乱码,显示为??
原因:Tomcat默认使用ISO-8859-1解码URL参数,而register.jsp中用户名为中文时,request.getParameter("name")返回乱码。
解决:在LoginServlet.java和RegisterServlet.java的doPost()方法开头,添加字符编码设置:
request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");同时,在/WEB-INF/web.xml中添加<jsp-config>配置:
<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <scripting-invalid>false</scripting-invalid> </jsp-property-group> </jsp-config>5.5 现象:点击“喜欢”按钮后,数据库user_tour_preference表无新增记录
原因:LikeServlet.java中request.getParameter("userId")返回null,因为AJAX请求的body参数名与Servlet获取的参数名不一致。前端发送'userId=' + userId,但Servlet中写成了request.getParameter("uid")。
解决:统一参数名。前端AJAX中body: 'userId=' + encodeURIComponent(userId) + '&spotId=' + spotId,后端LikeServlet.java中必须用request.getParameter("userId")获取,而非"uid"。检查所有Servlet的getParameter()调用,确保与前端fetch的body键名完全一致。
6. 进阶技巧:如何将推荐模块抽离为独立Jar包,无缝接入Spring Boot项目
6.1 提炼核心算法为可复用组件
CollaborativeFilteringEngine.java是纯Java逻辑,无任何JSP/Servlet依赖,可直接迁移。但需做三处改造:
- 替换数据库访问层:原
DBUtil.java使用BasicDataSource,Spring Boot项目应改用JdbcTemplate。新建TouristSpotDao.java:
@Repository public class TouristSpotDao { @Autowired private JdbcTemplate jdbcTemplate; public List<TouristSpot> getCommonSpots(String userA, String userB) { String sql = "SELECT t1.tourId FROM user_tour_preference t1 " + "INNER JOIN user_tour_preference t2 ON t1.tourId = t2.tourId " + "WHERE t1.userId = ? AND t2.userId = ? AND t1.count IS NOT NULL"; return jdbcTemplate.query(sql, new Object[]{userA, userB}, new TouristSpotRowMapper()); } }- 注入Spring Bean:在
CollaborativeFilteringEngine构造函数中注入TouristSpotDao,替代原DBUtil静态调用。 - 移除JSP绑定:删除所有
request.setAttribute()相关代码,方法返回List<TouristSpot>即可。
6.2 构建可发布Jar包的Maven配置
在pom.xml中添加:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>com.tour.engine.CollaborativeFilteringEngine</mainClass> </manifest> </archive> </configuration> </plugin> <!-- 打包依赖 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.tour.engine.CollaborativeFilteringEngine</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>6.3 在Spring Boot Controller中调用推荐服务
@RestController @RequestMapping("/api/recommend") public class RecommendationController { @Autowired private CollaborativeFilteringEngine recommendationEngine; @GetMapping("/{userId}") public ResponseEntity<List<TouristSpot>> getRecommendations(@PathVariable String userId) { try { List<TouristSpot> recommendations = recommendationEngine.generateRecommendations(userId); return ResponseEntity.ok(recommendations); } catch (Exception e) { log.error("Recommendation failed for user: {}", userId, e); return ResponseEntity.status(500).body(Collections.emptyList()); } } }从那以后我每次接手老系统重构,都会先扒出它的核心算法类,用
mvn clean compile验证能否独立编译,再逐行替换数据库访问层——这样既保住业务逻辑,又甩掉技术债。这份旅游推荐系统里的协同过滤引擎,就是我抽离的第一个成功案例。它现在跑在我司微服务的recommendation-service里,每天为3万用户提供青海湖、茶卡盐湖的精准推荐。希望帮到你。
本文还有配套的精品资源,点击获取