尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

计算机毕业设计项目旅游:新手如何从零构建一个高可用的旅游推荐系统

计算机毕业设计项目旅游:新手如何从零构建一个高可用的旅游推荐系统 最近在辅导学弟学妹做毕业设计时发现很多同学对“旅游推荐系统”这类项目很感兴趣但实际动手时却常常陷入迷茫。要么是前后端代码混在一起改一处而动全身要么是数据库设计得乱七八糟加个新功能都费劲还有的同学干脆把所有逻辑都写在几个巨大的类里美其名曰“快速开发”结果后期调试苦不堪言。今天我就结合自己的一些经验聊聊如何从零开始构建一个结构清晰、具备基础推荐能力、并且方便部署演示的旅游信息系统。我们的目标是让新手也能做出一个像模像样的、可以写在简历里的项目。1. 避开新手常见坑从混乱到清晰很多同学的第一个误区是“功能堆砌”。一想到旅游系统就恨不得把地图导航、智能聊天、虚拟现实全塞进去结果每个功能都做得很浅系统却变得无比臃肿。对于毕设而言深度优于广度。抓住一两个核心点做透比如“个性化推荐”和“景点信息管理”反而更能体现技术能力。第二个常见问题是“架构模糊”。很多项目还是单体架构前端JSP、后端Servlet、数据库访问代码和业务逻辑全部耦合在一起。这种结构在开发初期似乎很快但一旦需要修改用户模块可能会意外影响到订单逻辑调试起来如同噩梦。因此采用前后端分离是迈出专业开发的第一步。前端只负责展示和交互后端通过API提供数据和服务两者通过明确的接口契约进行通信职责清晰也便于后期独立升级或替换。第三个坑是“配置硬编码”。数据库密码、第三方API密钥直接写在代码里换台机器部署就得全局搜索替换极易出错且不安全。正确的做法是使用配置文件如application.yml来管理这些易变和敏感的信息。2. 技术栈选型为什么是Spring Boot Vue面对琳琅满目的技术新手容易挑花眼。这里简单对比一下后端为什么选Spring Boot相比传统的SSH/SSM框架Spring Boot最大的优势是“开箱即用”和“约定大于配置”。它内嵌了Tomcat服务器你不需要再费力打WAR包部署它提供了丰富的Starter依赖比如spring-boot-starter-web用于Web开发mybatis-plus-boot-starter用于数据库操作引入依赖就能快速集成。相比于Python的DjangoSpring Boot在Java生态中更主流企业应用广泛学习资料和社区支持也更丰富写在简历上认可度更高。持久层为什么用MyBatis-PlusMyBatis需要手写大量SQL而MyBatis-Plus在其基础上进行了增强提供了强大的CRUD封装和条件构造器对于“景点管理”、“用户管理”这类常规的增删改查操作几乎可以不用写SQL极大提升了开发效率。它完美契合了毕设项目“快速实现业务逻辑”的需求。前端为什么选Vue 3Vue.js以其轻量、易上手、文档友好著称非常适合前端新手。Vue 3引入了Composition API逻辑组织更灵活。相比于ReactVue的学习曲线更平缓相比于纯静态页面HTML/CSS/JSVue提供了响应式数据绑定和组件化开发能力能轻松构建复杂的单页面应用SPA用户体验更好。使用Vue CLI可以快速搭建项目结构。这套组合Spring Boot MyBatis-Plus Vue 3是目前非常流行且成熟的全栈方案既能保证项目的完整度和技术深度又控制了学习成本。3. 核心模块设计与实现一个精简的旅游推荐系统可以围绕以下几个核心模块展开3.1 数据模型设计MySQL这是系统的基石。设计时要遵循基本的数据库范式避免冗余。核心表可以这样设计用户表 (user)id,username,password加密存储,avatar,create_time等。景点表 (attraction)id,name,description,city,address,image_url,tags可以用JSON存储标签如[“自然风光”, “历史古迹”]等。用户评分表 (user_rating)这是实现推荐的关键。字段包括id,user_id,attraction_id,score1-5分,create_time。这张表记录了用户对景点的偏好。注意初期可以不用外键关联通过程序逻辑保证数据一致性这样更灵活也避免了后续分库分表的麻烦这是一个重要的避坑点后面会详说。3.2 简易推荐逻辑实现基于用户的协同过滤雏形我们不去实现复杂的机器学习算法而是用一个简化的思路来模拟“协同过滤”给用户推荐和他口味相似的人喜欢的景点。思路当用户A访问系统时我们找出与他评分习惯最接近的用户B例如计算他们共同评分过的景点的分数差异差值越小越相似。然后找出用户B高分评价过、但用户A还未评价过的景点推荐给A。简化实现在实际毕设中可以进一步简化。例如直接根据景点标签进行推荐。如果用户A给带有“历史古迹”标签的景点打分都很高那么我们就将其他“历史古迹”类景点优先推荐给他。下面是一个高度简化的、基于标签匹配的推荐服务层代码示例Spring BootService public class SimpleRecommendationService { Autowired private AttractionMapper attractionMapper; Autowired private UserRatingMapper ratingMapper; /** * 为指定用户生成景点推荐列表基于标签偏好 * param userId 用户ID * return 推荐景点列表 */ public ListAttraction recommendAttractions(Long userId) { // 1. 获取该用户的所有评分记录 ListUserRating userRatings ratingMapper.selectList( new LambdaQueryWrapperUserRating().eq(UserRating::getUserId, userId) ); // 2. 提取用户偏好的标签例如评分4分的景点标签 SetString preferredTags new HashSet(); for (UserRating rating : userRatings) { if (rating.getScore() 4) { // 假设4分以上表示喜欢 Attraction attraction attractionMapper.selectById(rating.getAttractionId()); if (attraction.getTags() ! null) { // 假设tags是JSON字符串如[自然,爬山]这里需要解析 ListString tags JSON.parseArray(attraction.getTags(), String.class); preferredTags.addAll(tags); } } } // 3. 如果用户没有评分记录冷启动问题返回热门景点 if (preferredTags.isEmpty()) { return attractionMapper.selectList( new LambdaQueryWrapperAttraction() .orderByDesc(Attraction::getViewCount) // 假设有浏览数字段 .last(limit 10) ); } // 4. 根据偏好标签查询景点并排除用户已评分过的 ListLong ratedAttractionIds userRatings.stream() .map(UserRating::getAttractionId) .collect(Collectors.toList()); // 这里使用MyBatis-Plus的条件构造器进行复杂查询 LambdaQueryWrapperAttraction queryWrapper new LambdaQueryWrapper(); // 示例查询标签包含任意一个偏好标签的景点 (需要数据库支持JSON查询如MySQL的JSON_CONTAINS) // 为了简化演示假设tags字段是逗号分隔的字符串使用like查询不推荐用于生产 // 实际项目中应使用专门的标签系统或支持JSON查询的数据库 for (String tag : preferredTags) { queryWrapper.like(Attraction::getTags, tag).or(); } queryWrapper.notIn(!ratedAttractionIds.isEmpty(), Attraction::getId, ratedAttractionIds); queryWrapper.last(limit 20); return attractionMapper.selectList(queryWrapper); } }3.3 前端交互实现Vue 3 Element Plus前端主要负责展示景点列表、详情和收集用户评分。我们使用Vue 3的script setup语法和Element Plus组件库。景点卡片组件 (AttractionCard.vue)template el-card classattraction-card shadowhover template #header div classcard-header span{{ attraction.name }}/span el-tag v-fortag in attraction.tagList :keytag sizesmall typeinfo{{ tag }}/el-tag /div /template img :srcattraction.imageUrl :altattraction.name stylewidth:100%; height:180px; object-fit:cover; / p classdescription{{ attraction.description }}/p div classcard-footer span{{ attraction.city }}/span el-rate v-modeluserRating :max5 changehandleRateChange :disabled!isLoggedIn / /div /el-card /template script setup import { ref, computed } from vue; import { ElMessage } from element-plus; import { rateAttraction } from /api/attraction; // 假设封装了API请求 const props defineProps({ attraction: { type: Object, required: true } }); const emit defineEmits([rated]); // 假设从Pinia或Vuex store中获取登录状态和用户ID const isLoggedIn computed(() /* ... */ true); const currentUserId computed(() /* ... */ 1); // 用户对该景点的评分初始值可以从props.attraction.userScore获取 const userRating ref(props.attraction.userScore || 0); const handleRateChange async (score) { if (!isLoggedIn.value) { ElMessage.warning(请先登录); userRating.value 0; return; } try { await rateAttraction({ userId: currentUserId.value, attractionId: props.attraction.id, score: score }); ElMessage.success(评分成功); emit(rated, { attractionId: props.attraction.id, newScore: score }); // 通知父组件 } catch (error) { ElMessage.error(评分失败); userRating.value 0; // 失败则重置评分显示 } }; /script style scoped .attraction-card { margin-bottom: 20px; } .card-header { display: flex; justify-content: space-between; align-items: center; } .description { color: #666; font-size: 14px; margin: 10px 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .card-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } /style4. 性能与安全考量一个合格的系统不能只关注功能。SQL注入防护这是重中之重。使用MyBatis-Plus我们强烈推荐使用其提供的LambdaQueryWrapper或QueryWrapper进行条件构造它会自动处理参数预编译从根本上杜绝SQL注入。绝对不要用字符串拼接的方式组装SQL语句。JWT令牌管理用户登录后后端生成一个JSON Web TokenJWT返回给前端。前端后续请求时在HTTP Header如Authorization: Bearer token中携带此令牌。后端通过过滤器Filter或拦截器Interceptor验证令牌的合法性和有效性。这样可以实现无状态的会话管理。记得设置合理的令牌过期时间并提供刷新令牌的机制。冷启动优化对于新用户没有评分记录我们的推荐服务会返回热门景点。为了提升“冷启动”阶段的推荐质量可以在用户注册时让其选择感兴趣的标签如自然风光、美食、亲子等作为初始推荐依据。5. 生产环境部署避坑指南即使本地运行完美部署到服务器也可能问题频出。数据库外键慎用如之前提到的在业务初期或毕设项目中可以不在数据库层面建立外键约束而是在应用层通过代码逻辑保证数据完整性。这为未来可能的数据库水平拆分分库分表留有余地因为外键在分库场景下很难维护。跨域CORS配置遗漏前后端分离部署时前端域名和后端API域名往往不同浏览器会因同源策略阻止请求。必须在Spring Boot后端进行全局CORS配置。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) // 拦截所有请求 .allowedOriginPatterns(*) // 允许所有源生产环境应指定具体前端域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }Git提交敏感信息千万不要把application.yml里配置的数据库密码、Redis密码、第三方密钥等提交到Git仓库。应该使用application-dev.yml本地开发配置加入.gitignore和application-prod.yml生产配置通过服务器环境变量或配置中心管理进行隔离。前端路由History模式问题Vue Router使用history模式时如果直接访问非根路径如/attraction/1服务器会返回404。需要在Nginx等Web服务器配置中将所有前端路由重定向到index.html。location / { try_files $uri $uri/ /index.html; }API文档与测试使用Swagger或Knife4j自动生成API文档。这不仅方便前端对接也是毕设答辩时展示项目规范性的亮点。总结与思考通过以上步骤一个具备基础功能景点展示、用户评分、简易推荐和清晰架构前后端分离、模块化的旅游推荐系统就初具雏形了。这个项目麻雀虽小五脏俱全涵盖了现代Web开发的主要环节足以作为一份优秀的毕业设计。最后留一个思考题给大家也是项目一个很好的扩展方向如何在不引入复杂调度算法框架的前提下为系统增加“多城市旅游路线规划”功能你可以考虑让用户选择多个想去的城市系统根据每个城市的景点标签、用户历史评分、景点间的距离可以先用模拟数据以及用户设定的总天数通过一个贪心算法或简单的回溯算法生成一条粗略的游览路线建议。这不仅能锻炼你的算法设计能力也能让项目更具实用性和创新性。希望这篇笔记能为你打开思路祝你毕业设计顺利
返回列表