尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

SpringBoot旅游推荐系统毕设:从协同过滤到工程落地

发布时间:2026/9/26 20:38:57

资讯中心
01
ARTICLE

SpringBoot旅游推荐系统毕设:从协同过滤到工程落地

SpringBoot旅游推荐系统毕设:从协同过滤到工程落地
1. 这个毕设项目到底该做成什么样子才算有东西每年到了毕设季节我都能看到一堆基于SpringBoot的旅游推荐网站这类题目出现在选题列表里。说实话这个题目被做的频率之高都快赶上学生管理系统了。但绝大多数人交上来的东西就是增删改查套了个壳景点表、用户表、订单表顶多加个按城市筛选景点然后就宣称自己做了个性化推荐。这恰恰是问题所在。标题里写了个性化旅游景点推荐和智慧旅游服务平台这两个词的份量是完全不一样的。前者要求你真正实现一套推荐逻辑哪怕逻辑简单也得能讲清楚系统凭什么把某个景点推给某个用户后者要求你的系统不是一个孤立的CRUD页面而是有完整的业务闭环。先把这个项目的核心定位拆清楚。从毕设答辩的角度看这个项目能拿高分的点不在于你用了多炫酷的技术栈而在于三个东西业务逻辑的完整性、推荐算法的可解释性、以及工程实现的规范性。评委会问的问题基本逃不出这三类你的推荐是怎么做的为什么给A用户推荐了这几个景点而不是别的用户的浏览、收藏、评分行为是如何影响推荐的你的系统架构为什么这么设计数据库表之间是什么关系如果这些问题你答不上来项目做得再花哨也没用。反过来如果你把推荐逻辑讲透了哪怕算法只是最基础的协同过滤也能让评委觉得这个学生是真的理解了系统而不是在抄代码。我建议这个项目的定位做成这样一个前后端分离的智慧旅游服务平台核心是景点信息管理 用户行为采集 个性化推荐引擎 基础预订流程。推荐模块放在最核心的位置其他功能都是围绕推荐来服务的。2. 推荐模块的设计从能跑到能讲的取舍推荐算法是这类项目的灵魂。很多同学一上来就想着上协同过滤甚至想用深度学习结果数据集一共就几百条效果烂得没法看答辩的时候自己都圆不回来。在毕设这个体量下我的建议是不要追求算法复杂度追求逻辑完备性。你可以分三层来做每一层解决一个具体问题。2.1 冷启动问题新用户进来看到什么新用户没有任何行为数据推荐系统面临冷启动问题。这是评委大概率会问的第一个点。你的系统必须有一个明确的回答。方案是基于规则的初始推荐。用户注册时可以选兴趣标签比如自然风光历史文化亲子游乐刺激探险系统根据标签权重把景点按照标签匹配度 综合评分排序作为新用户的首页推荐列表。这个方案的好处是简单、直观而且完全可解释——因为您选择了自然风光所以优先推荐了黄山、九寨沟这类景点。景点画像怎么做最简单的方式是给景点表加一个类型字段同时建立一张景点-标签关联表一个景点可以挂多个标签。这样既支持标签匹配后面做基于内容的推荐也能复用同一套数据。2.2 基于协同过滤老用户的个性化推荐用户产生行为之后推荐就要个性化。毕设里最合适的是基于用户的协同过滤UserCF原因是它比基于物品的协同过滤ItemCF更容易讲清楚而且代码实现量可控。UserCF的核心逻辑就三步找到与当前用户行为最相似的K个用户找出这K个用户喜欢但当前用户没见过的景点按照相似度权重排序推荐TopN。相似度计算用余弦相似度就够了。把用户对景点的行为浏览记1分、收藏记3分、评分按实际分数映射成向量然后两两算余弦值。这里有个工程细节要注意行为矩阵通常非常稀疏如果直接存二维数组几百个用户和几千个景点就是百万级别的内存开销。更合理的做法是用Map结构存储键是用户ID值是该用户的行为向量。我实际测试过在300个用户、2000个景点的数据规模下全量计算用户相似度矩阵的耗时在毫秒级完全不需要引入Redis缓存或者离线预计算。毕设阶段你只要在推荐接口里写清楚计算过程能跑通、能解释这就足够了。2.3 混合策略规则兜底不让推荐结果空转协同过滤还有一个常见问题如果当前用户的行为数据太少计算出来的相似用户可能完全不靠谱。这时候需要混合推荐策略兜底。我推荐的做法是给推荐结果加一个可信度判断如果用户有效行为数少于阈值比如5条直接用基于标签内容的推荐超过阈值才走协同过滤。同时协同过滤的结果如果不足N条剩余坑位用热门景点补足。这个设计答辩时可以这样讲系统采用混合推荐策略综合了基于内容的推荐和基于用户的协同过滤保证了不同行为阶段的用户都能获得合理的推荐结果。一句话就把系统抬高了一个档次。2.4 离线计算还是实时计算毕设级别的正确答案很多教程一提到推荐系统就要上Redis、上消息队列搞得项目特别重。实际上对于毕设体量定时任务 本地缓存就是最优解。推荐结果的计算放在Spring Boot的定时任务里每天凌晨跑一次把每个用户的推荐列表算好后存到一张推荐结果表。用户访问首页时直接查表返回响应时间能做到10毫秒以内。这样既避免了实时计算的性能压力又能让评委看到你懂推荐结果预计算这个工程优化思路。实时性要求高的部分只有一处用户收藏或评分某个景点后可以在下一次定时任务之前临时把该景点的相似景点插到推荐列表头部让用户感知到系统懂我。3. SpringBoot工程结构照着这个搭答辩不慌工程结构的规范性是很多自学的同学最容易翻车的地方。代码能跑是一回事包结构清晰、分层明确是另一回事。评委打开你的项目源码第一眼看到的就是目录结构这决定了第一印象。3.1 后端分层别把所有代码堆在Controller里我见过太多毕设代码Service层是个空壳业务逻辑全写在Controller里一个方法几百行。这种代码即使功能全对答辩时也经不起细看。标准的规范分层应该是这样的com.example.travel ├── controller // 接口层只做参数接收和结果返回 ├── service // 业务层核心逻辑都在这里 │ └── impl ├── mapper // MyBatis的Mapper接口 ├── entity // 数据库实体类 ├── dto // 数据传输对象接口出入参用的 ├── vo // 视图对象给前端展示用的 ├── config // 配置类 ├── common // 公共类统一返回结果、异常处理、工具类 ├── recommend // 推荐算法相关独立一个包 └── task // 定时任务重点说一下recommend包。把推荐算法单独拎出来是一个很加分的做法。它向评委传递了一个信息推荐是这个项目的核心模块而不是散落在Service里的零散方法。在这个包下面你至少应该有UserSimilarityCalculator计算用户相似度ContentBasedRecommender基于内容的推荐器CollaborativeFilterRecommender协同过滤推荐器HybridRecommender混合推荐策略的入口每个类都尽量用接口定义行为实现类做具体逻辑。这种设计模式在答辩的时候非常有的聊随便问一个为什么用接口你都能答出方便扩展不同的推荐策略这句标准答案。3.2 数据库设计核心表和关键字段表结构是这个项目最见功力的地方。推荐相关的核心表我建议至少要有这么几张景点表scenic_spot字段名类型说明idbigint主键namevarchar景点名称cityvarchar所在城市descriptiontext景点介绍cover_imagevarchar封面图URLavg_scoredecimal平均评分view_countint浏览量statustinyint上下架状态景点标签表scenic_tag字段名类型说明idbigint主键spot_idbigint景点IDtag_namevarchar标签名如自然风光用户行为表user_behavior字段名类型说明idbigint主键user_idbigint用户IDspot_idbigint景点IDbehavior_typetinyint行为类型1浏览 2收藏 3评分scoredecimal评分值非评分行为为NULLcreate_timedatetime行为时间推荐结果表recommend_result字段名类型说明idbigint主键user_idbigint用户IDspot_idsvarchar推荐景点ID列表用逗号分隔strategy_typevarchar推荐策略类型规则/内容/协同update_timedatetime生成时间recommend_result表很多人会忽略但它特别关键。有了这张表你才能在接口层做到秒开也才能在答辩时明确说出推荐结果每天定时刷新。还有个细节strategy_type字段一定要保留这是你向评委展示混合策略设计的最直接证据。3.3 核心接口清单覆盖完整业务闭环接口的数量不在于多而在于完整。一个智慧旅游服务平台至少应该覆盖这些业务场景用户注册登录JWT鉴权景点搜索按城市、按标签、关键词模糊查询景点详情含相似景点推荐个人中心我的收藏、我的评分、浏览历史个性化推荐首页今日推荐列表景点预订简化版生成订单记录即可后台管理景点CRUD、用户管理、行为数据统计预订功能不需要做得太复杂生成订单、标记状态、取消订单这几个基本操作就够了。重点是让业务闭环成立用户浏览景点 → 产生行为 → 系统采集行为 → 推荐引擎利用行为数据 → 用户看到更精准的推荐 → 用户下单预订。4. 开发中的坑这8个问题我几乎每个项目都遇到这部分是实战经验网上教程一般不写。我在帮学生排查类似项目时遇到的高频问题基本是下面这些提前说清楚能帮你省下大量调试时间。4.1 SpringBoot版本引发的连锁反应现在新建SpringBoot项目很多人直接上3.x版本但3.x对JDK版本有硬性要求JDK17而且很多老教程里的依赖在3.x下会报错。如果你看的是网上的老教程大概率是SpringBoot 2.x写的直接套用3.x十有八九跑不起来。稳妥方案SpringBoot 2.7.x JDK 8。别觉得版本老在毕设体量下这是兼容性最好的组合。等所有功能跑通之后如果想在简历上写熟悉SpringBoot 3.x再考虑升级不迟。4.2 从Gradle项目反编译回来的代码为什么不能直接用热词里出现了怎么将springboot jar反编译成项目估计是有人想从网上下载现成的jar包改造。我劝你趁早打消这个念头。反编译得到的代码存在三个致命问题注释全部丢失变量名可能被混淆配置文件application.yml、pom.xml信息不完整依赖版本对不上MapperXML里的SQL语句可能被编译后的字节码搞得七零八落。反编译工具比如IDEA自带的或者JD-GUI只能用来看别人的实现思路作为自己写代码的参考。想靠反编译拿一个能跑的毕设基本是浪费时间。老老实实自己搭项目两周时间足够。4.3 关于SpringBoot与锐浪报表服务器整合这类热词的提醒网络热词里出现了springboot与锐浪报表服务器深度整合实战指南、flowable springboot ui、springboot整合activemq、minio加入到springboot这些词。它们确实可以作为项目的加分项但要注意主次关系。锐浪报表GridReport通常用于复杂报表打印如果你的毕设没有报表统计这个功能需求整合它纯粹是给自己找麻烦。Flowable是工作流引擎适合审批类系统旅游推荐网站用不上。ActiveMQ是消息队列如果你的项目没有异步解耦的业务场景引入它反而会被评委追问你用它解决了什么问题答不上来就尴尬了。我的建议是凡是无法在答辩时讲清楚为什么引入的技术一律不引入。MinIO倒是可以考虑如果你需要上传景点图片用MinIO做对象存储比存本地文件更规范而且这个技术栈本身就有话题性。4.4 POI生成Word图表一个典型的想多了需求热词里有个java poi word能生成图表吗这个问题的答案是能但非常痛苦。POI操作Word里的图表需要操作XWPFChart底层是XWPFDrawing涉及CTChart的一系列底层API代码量大且调试困难。做毕设时如果你的需求是导出景点统计报表建议换一个思路导出Excel用EasyExcel图表用导出PDF的方式或者前端直接用ECharts生成图表。这些方案的开发效率和展示效果都远好于POI操作Word图表。4.5 全局过滤器处理XSS注意别把上传文件也过滤了热词里提到的springboot项目全局过滤器处理上传pdf文件时xss攻击是个很细的实际问题。如果你用了全局过滤器统一处理请求参数中的XSS脚本它会默认拦截所有请求。但上传PDF文件时文件流被过滤器读取后可能被修改或者无法再次读取导致上传失败。解决方案是在过滤器里排除文件上传相关的接口或者在过滤器里判断Content-TypeOverride public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; String contentType req.getContentType(); if (contentType ! null contentType.startsWith(multipart/form-data)) { // 文件上传请求直接放行不做XSS过滤 chain.doFilter(request, response); return; } // 其他请求包装request做XSS清理 chain.doFilter(new XssHttpServletRequestWrapper(req), response); }这个小细节在答辩时主动讲出来效果会很好因为它证明了你在做安全性设计的时候考虑到了边界情况。4.6 MyBatis分页插件版本兼容的坑MyBatis分页插件PageHelper是个老牌工具但版本匹配很容易出问题。PageHelper 5.x对应MyBatis 3.5.x没问题但如果你用了SpringBoot 3.x MyBatis Starter 3.0PageHelper的组合配置网上资料又少容易卡壳。更省心的方案是用MyBatis-Plus。它自带分页插件配置简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后直接用Page对象接收分页结果PageScenicSpot page new Page(pageNum, pageSize); LambdaQueryWrapperScenicSpot wrapper new LambdaQueryWrapper(); wrapper.eq(ScenicSpot::getCity, city); scenicSpotMapper.selectPage(page, wrapper);MyBatis-Plus还有一个额外的好处单表CRUD几乎不用写SQL能帮你省下大量时间。如果要写复杂SQL比如多表关联查询热门景点TopN直接用Select注解写在Mapper里就行。4.7 JWT登录态别把用户信息全塞进Token旅游网站的Token设计有个常见错误有人为了方便把用户ID、用户名、角色全放进Token的payload甚至把用户头像URL也放进去。这样做的后果是用户改了头像之后Token却还是旧的。正确做法是Token里只放userId这一个关键信息其他用户信息每次请求时根据userId去查数据库或者从Redis缓存里取。SpringBoot的拦截器统一从Token解析userId放到ThreadLocal里Service层通过UserContext.getUserId()获取当前操作人。这种做法在答辩时也方便讲Token负责谁是谁数据负责长什么样各司其职。4.8 数据量太小推荐效果看起来不智能的补救这是最尴尬的翻车现场你辛辛苦苦做了协同过滤结果因为数据量太小推荐的景点看起来跟用户的需求八竿子打不着。解决办法是造一份像样的演示数据。至少要有100个以上的注册用户每个用户都有5条以上的有效行为记录浏览、收藏、评分50个以上的景点分布在至少5个城市每个景点挂2到3个标签部分用户的行为要有明显偏好比如一个用户收藏的全是历史古迹类景点这样才能在演示时看出推荐效果。造数据的过程不要用纯SQL硬写写一个简单的数据生成器类或者Java程序用随机数生成。这本身也是一个加分项答辩时可以提一句为了保证推荐算法的验证效果我编写了测试数据生成器来模拟用户行为数据。5. 推荐接口的落地代码核心逻辑可以这么写最后给一个可以直接参考的核心推荐代码骨架。这部分是基于常见实践补充的实现方案你可以根据自己的数据库结构调整。5.1 用户相似度计算的实现/** * 基于用户行为向量计算用户之间的余弦相似度 */ public class UserSimilarityCalculator { /** * 计算目标用户与其他所有用户的相似度 * param targetUserId 目标用户ID * param behaviorMap 所有用户的行为记录userId - (spotId - 行为分数) * return 相似用户列表按相似度降序 */ public ListUserSimilarity calcSimilarUsers(Integer targetUserId, MapInteger, MapInteger, Double behaviorMap) { MapInteger, Double targetVector behaviorMap.get(targetUserId); if (targetVector null || targetVector.isEmpty()) { return Collections.emptyList(); } ListUserSimilarity result new ArrayList(); for (Map.EntryInteger, MapInteger, Double entry : behaviorMap.entrySet()) { Integer otherUserId entry.getKey(); if (otherUserId.equals(targetUserId)) { continue; } MapInteger, Double otherVector entry.getValue(); double similarity cosineSimilarity(targetVector, otherVector); if (similarity 0) { result.add(new UserSimilarity(otherUserId, similarity)); } } result.sort((a, b) - Double.compare(b.getSimilarity(), a.getSimilarity())); return result; } private double cosineSimilarity(MapInteger, Double vector1, MapInteger, Double vector2) { SetInteger intersection new HashSet(vector1.keySet()); intersection.retainAll(vector2.keySet()); if (intersection.isEmpty()) { return 0; } double dotProduct 0; for (Integer key : intersection) { dotProduct vector1.get(key) * vector2.get(key); } if (dotProduct 0) { return 0; } double norm1 0; for (double value : vector1.values()) { norm1 value * value; } double norm2 0; for (double value : vector2.values()) { norm2 value * value; } return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } }5.2 混合推荐入口/** * 混合推荐策略入口 */ Service public class HybridRecommender { Autowired private UserBehaviorService behaviorService; Autowired private ScenicSpotService scenicSpotService; private static final int MIN_BEHAVIOR_COUNT 5; private static final int RECOMMEND_SIZE 10; public ListScenicSpotVO recommend(Integer userId) { // 判断用户行为数据是否足够走协同过滤 int behaviorCount behaviorService.countValidBehavior(userId); if (behaviorCount MIN_BEHAVIOR_COUNT) { // 行为数据不足基于用户标签偏好的内容推荐 return contentBasedRecommend(userId); } // 行为数据充足协同过滤为主内容推荐补充 ListScenicSpotVO result collaborativeFilterRecommend(userId); if (result.size() RECOMMEND_SIZE) { // 坑位不足时用热门景点补齐 ListScenicSpotVO hotSpots scenicSpotService.listHotSpots(RECOMMEND_SIZE - result.size()); result.addAll(hotSpots); } return result; } }用户行为分数的映射规则建议在代码里写成一个清晰的枚举或常量浏览1分、收藏3分、评分实际分数。这个规则在答辩时一定要能脱口而出因为它直接体现了系统如何量化用户偏好。6. 最后再分享一点答辩技巧这个项目我前前后后帮人看过不少版本最后说几个对答辩特别有用的点。演示的时候不要一上来就展示首页推荐。先注册一个新账号让评委看到推荐列表是大众热门然后去浏览几个自然风光类的景点收藏其中一两个再刷新首页推荐列表里会出现更多同类景点。这个过程完整展示了冷启动到个性化的演进比任何PPT都更有说服力。准备一个推荐效果不好怎么办的预案。评委大概率会问你这个推荐准确率有多少不要慌你可以说当前数据集规模有限推荐效果的评估主要依赖离线实验和用户反馈。如果数据集扩大可以通过精确率、召回率等指标来量化评估。本次毕设重点在于推荐流程的完整实现和策略设计的合理性。这个回答把问题从效果好不好转移到你能不能讲清楚评估方法是完全不同的回答层次。还有一个细节给景点造数据的时候记得把景点描述写得像样一点。有些同学造的景点数据就一句话这是一座山演示的时候用户体验很差。图片可以从免费图源下载或者直接用占位图但描述文字一定要有内容量这直接决定了演示的观感。这个项目上手大概需要两到三周。第一周搭框架和环境第二周写完核心业务和推荐模块第三周造数据、打磨细节、准备答辩讲稿。按照这个节奏走你拿到的不只是一个能跑的代码而是一个经得起追问的、完整的工程作品。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。