Spring Boot+Vue旅游信息推荐系统:协同过滤算法与前后端分离实战

发布时间:2026/10/21 17:26:45
Spring Boot+Vue旅游信息推荐系统:协同过滤算法与前后端分离实战 1. 为什么旅游信息推荐系统成了毕业设计的热门选题每年到了毕业设计开题季总有不少同学在选题上反复纠结。管理系统太老套纯前端展示又显得单薄算法类项目又怕自己搞不定。如果你也有这种困扰旅游信息推荐系统这个方向确实值得认真考虑。先说市场需求。旅游本身是个高频、低门槛、人人都能理解的业务场景不需要像电商那样处理复杂的库存和支付也不需要像社交平台那样设计复杂的互动机制。但它的信息组织逻辑其实相当完整景点、路线、酒店、攻略、美食、用户行为记录、个性化推荐这些模块组合起来天然就是一个前后端分离 数据交互 算法落地的经典实训项目。再说技术含量。Spring Boot加Vue这套组合是目前企业级项目中最主流的搭配之一。Spring Boot负责后端接口、业务逻辑、数据持久化Vue负责前端交互、组件化开发、数据渲染。把两者打通你几乎能模拟出一个真实项目从零到上线的完整流程。第三个理由也很实在旅游推荐系统天然适合做推荐算法的轻量落地。不需要你去训深度学习模型也不用碰大规模分布式计算只要把基于用户行为相似度的协同过滤、基于景点属性的内容推荐、基于热度排序的冷启动策略搞明白就能在毕业答辩里拿出一个既能演示、又讲得清楚的技术亮点。这篇文章我按自己当初做类似项目的思路来拆解覆盖技术选型、数据库设计、推荐算法落地、前后端联调、部署上线全流程。无论你是打算直接照着做一个还是想理解里面每个环节为什么要这么设计应该都能找到对你有用的东西。2. 技术选型背后的真实考量很多同学在选题确定之后第一件事就是纠结框架版本、依赖版本、要不要加Redis、要不要用Elasticsearch。我的建议是先想清楚你的目标是毕业设计还是生产级高并发系统这两个方向的技术决策完全不同。2.1 为什么是Spring Boot而不是SSH或SSMSSHStruts Spring Hibernate和SSMSpring SpringMVC MyBatis都是曾经的主流方案但放在今天新项目再去用它们基本上属于给自己找麻烦。Spring Boot最核心的优势是约定优于配置你不需要手写大量XML配置文件内嵌Tomcat也省掉了单独部署Web服务器的步骤。在旅游推荐系统这种业务复杂度中等、但涉及模块较多的项目里Spring Boot的自动配置能力能让开发效率提高很多。比如引入Spring Data JPA或MyBatis-Plus之后单表CRUD几乎不用写SQL引入Spring Security之后登录鉴权只需要配置过滤器链。当然如果你对底层原理的理解还不够深Spring Boot也不会强制你理解——这一点对毕业设计来说反而是优点先跑起来再逐步深入。2.2 为什么是Vue而不是JSP或Thymeleaf以前做JavaWeb项目前端页面用JSP渲染后端和前端代码搅在一起改个按钮样式都可能要重启服务。Vue彻底解决了这个问题前端是独立工程通过Axios调用后端接口数据驱动视图更新组件复用性高。Vue 2和Vue 3的选择上如果是从零开始学我建议直接用Vue 3加Composition API。虽然Vue 2的文档和教程更多但Vue 3已经是当前版本的主流方向而且相关的生态组件Vue Router、Pinia、Element Plus现在都很成熟了。要是你手头有现成的Vue 2项目代码参考用Vue 2也不是不行答辩时能说清楚为什么用Vue 2和为什么升级到Vue 3的区别反而可以多一个加分点。2.3 数据库和中间件够用就好别过度设计旅游信息推荐系统的数据量级在毕业设计场景下一般就是几千到几万条数据。MySQL完全可以胜任没必要上Oracle或者PostgreSQL当然PostgreSQL也是好选择但如果你不熟没必要为了高级去切换。表结构设计得合理一点索引加到位查询性能不会成为瓶颈。Redis要不要加我的看法是可以加但要想清楚加在哪里。最合理的用途是缓存热点景点的详情信息、缓存用户登录Session用Token方案代替、缓存首页推荐结果。这样在答辩时你可以说我用了Redis做缓存降低了数据库压力并且能演示出实际效果。但如果你只是为了在技术清单里多写一个Redis而加上去结果整个代码里只在启动时连接了一下那就是纯粹的画蛇添足。2.4 实战中的版本搭配参考这里给出一套经过实际验证的版本组合不是绝对的但照着用基本不会踩大坑组件版本建议说明JDK1.8或111.8最稳11也行别用太新的版本避免兼容问题Spring Boot2.7.x技术成熟稳定资料多不宜追新用3.xVue3.2配合Element Plus做后台管理界面效果很好Node.js16.x或18.x前端构建环境版本和Vue CLI/Vite要匹配MySQL5.7或8.08.0默认认证插件需要注意连接驱动版本MyBatis-Plus3.5.x单表操作神器分页插件很省事Redis5.x或6.x生成环境用的版本本地Windows可以用5.xMaven3.6后端依赖管理3.8以上注意镜像配置补充一点很多人上来就用最新版结果依赖冲突、API变更、教程对不上折腾两三天才明白是自己版本问题。毕业设计的目的不是做版本小白鼠我建议你的所有依赖版本尽量和主流教程保持一致先把功能全部跑通再考虑升级。3. 数据库设计一张好的表结构胜过十段业务代码旅游信息推荐系统的核心是围绕用户—景点—行为—推荐这条链路组织数据。数据库设计得好不好直接影响编码效率、接口复杂度和推荐算法的实现难度。3.1 核心数据表全景我没有把表设计得很细碎而是尽量贴合真实业务场景。下面这些表基本覆盖了一个推荐系统的数据基础用户表user用户ID、用户名、密码BCrypt加密存储、昵称、头像、性别、年龄、所在城市、偏好标签、注册时间等。偏好标签是后面做内容推荐的关键维度比如自然风光/人文历史/美食购物/休闲度假。景点表scenic景点ID、名称、所属城市、景点类型、门票价格、开放时间、评分、热度值、简介、详细内容、封面图、经度纬度等。热度值可以手动配置也可以根据访问量自动更新。评论表comment评论ID、用户ID、景点ID、评论内容、评分、点赞数、评论时间。评论不只是展示功能也是用户行为的来源。收藏表favorite收藏ID、用户ID、景点ID、收藏时间。收藏是比浏览更强的一种正向行为信号。浏览记录表history记录ID、用户ID、景点ID、浏览时长、浏览时间。浏览数据量大可以按天分区或定期清理。攻略表strategy攻略ID、作者ID、标题、封面、摘要、内容、发布时间、浏览数、点赞数。攻略和景点通过城市或标签关联。行程规划表route路线ID、用户ID、路线名称、天数、途经景点ID列表JSON格式存储、创建时间。这是加分模块能让项目功能更完整。管理员表admin管理员ID、账号、密码、角色、最近登录时间。3.2 用户—景点交互数据怎么设计才合理很多第一次做推荐系统的同学都会问用户对景点有哪些行为这些行为怎么记录用一张表还是多张表我的建议是浏览、收藏、评论各用一张表不要合并。原因有三点。第一每种行为的含义和价值权重不同——收藏比浏览更能体现用户意向评论比收藏更能体现用户的深度参与。第二推荐算法计算时需要按行为类型分别取数、分别加权分开存储让数据获取更简单。第三分表的冗余度比合并表更低后续如果要加点赞分享等新行为不需要改动原表结构。具体到浏览记录表核心字段至少要有用户ID、景点ID、浏览时长秒、浏览时间。浏览时长这个字段容易被忽略但对推荐算法来说价值很高——停留时间长的景点说明用户真正感兴趣。前端在景点详情页埋一个定时器用户离开页面时把时长传给后端实现成本很低。3.3 关联查询和索引优化的经验旅游推荐系统的业务查询不算复杂但有几个高频场景容易踩坑第一个场景是景点分页列表带城市和类型筛选。在MySQL里分页查询加多条件筛选如果数据量上万without索引的慢查询会非常明显。建议对整个表加联合索引(city_id, type_id, hot_value)并按热度值倒序排列这样用户翻页时性能很稳定。第二个场景是根据用户浏览记录查询推荐候选集。SQL大概是“根据浏览历史中相似的用户找出这些用户收藏和评论较多的景点”。这种查询往往涉及多张表的JOIN和子查询建议在行为表的外键列user_id、scenic_id都建立索引。第三个坑是JSON字段的误用。有些同学喜欢把用户的多个偏好标签存成一个JSON字符串这没什么问题但如果后面要用SQL做标签匹配筛选JSON字段不能高效走索引不如设计成独立的用户—标签关联表或者用逗号分隔加LIKE模糊查询数据量小的时候完全够用。下面是一个简化的用户偏好表设计CREATE TABLE user_tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, tag_name VARCHAR(50) NOT NULL, weight DOUBLE DEFAULT 1.0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_tag (user_id, tag_name) );weight字段代表该标签对用户的权重初始值可以从注册时的选择标签里取后续根据行为逐步调整。这个表既是内容推荐的输入也是容易讲清楚我为什么这样设计的亮点。4. 推荐算法落地让系统真正懂用户说实话很多毕业设计做推荐系统最后只是做了个按热度排序或者随机推荐的假推荐答辩时一旦被问到你的推荐逻辑是什么就支支吾吾。这里我把自己用过的、能讲明白也效果不错的算法组合分享出来。4.1 冷启动阶段基于内容和热度的混合策略新用户没有任何行为记录协同过滤无从算起。这个阶段最有效的做法是内容推荐加热度兜底。具体操作是在用户注册时让他选择感兴趣的标签自然风光、人文历史、美食购物、休闲度假、亲子游乐等然后查询这些标签关联的景点按热度值降序排列作为初始推荐列表。没有选择标签的直接取全站热度最高的前N个景点。这里的热度值需要设计一个综合评分公式我用的比较简单热度的核心来源有三个景点评分、收藏数量、评论数量。加权求和hot_value 0.4 * avg_rating 0.3 * log(favorite_count 1) 0.3 * log(comment_count 1)评分范围是1到5收藏量和评论量做了对数变换是防止极值拉偏比如一个景点被收藏了一万次对数之后也就和几千次拉开有限差距不会出现一家独大的情况。冷启动虽然技术含量不高但它解决的是系统第一版跑起来之后首页放什么的问题。没有这一步后续任何算法都没有数据基础。4.2 协同过滤基于用户的相似度计算当用户产生了浏览、收藏、评论行为之后就可以用协同过滤来做个性化推荐了。核心逻辑一句话找到和你行为模式相似的一群人把这些人喜欢但你还没看过的景点推荐给你。实现步骤拆解如下第一步构建用户—景点评分矩阵。收藏记1分评论记2分评分高于3星的额外加1分浏览记0.5分。不需要特别复杂的权重体系只要体现行为差异即可。第二步计算用户A和用户B的相似度。我用的是余弦相似度比皮尔逊相关系数更直观也更容易在答辩时讲清楚。先提取两个用户共同评分过的景点集合然后计算两个评分向量之间的余弦值值越接近1说明相似度越高。第三步选择相似度最高的前K个用户K一般取10到20找出这些用户评分较高的景点剔除当前用户已经收藏或浏览过的剩下的按相似度加权评分降序排列得到候选推荐列表。这里有一个容易被忽视的坑如果用户之间共同交互过的东西很少余弦相似度算出来是不准确的。比如用户A只看过一个景点用户B也只看过同一个他们的相似度会是1但并不能说明他们兴趣一致。所以实际实现时我会要求共同评分景点数至少大于等于3否则直接跳过不参与推荐。4.3 数据稀疏时的降级方案协同过滤最怕数据稀疏。你的系统在测试阶段可能只有几十个用户每个用户就看了几篇攻略、收藏了几个景点这种数据量下推荐结果很不可靠。我有两个办法第一个是降低协同过滤的触发门槛。把用户相似度改为混合相似度如果两个用户的共同行为少就同时引入他们浏览过的景点在类型和标签上的重合度作为辅助信号。比如用户A看了3个自然风光景点用户B也看了2个自然风光景点即便具体景点不同标签重合也能让相似度计算不那么空。第二个是规则过滤加随机探索。在推荐列表末尾保留10%到20%的探索位随机推荐一些当前热度不高但类型符合用户偏好的景点。这样既保证了推荐结果不千篇一律又可能帮用户发现冷门但有趣的景点。答辩时提到这种设计会让人觉得你考虑到了推荐系统的完整闭环不只是实现了一个算法公式。4.4 算法落地的工程细节无论算法设计得多好最终都要转换成接口和流程图。在我的项目里推荐模块是一个独立的Service入口方法长这样public ListScenicVO getRecommendList(Long userId, int page, int size) { if (userId null) { // 未登录直接走热度推荐 return hotRecommendService.recommend(page, size); } User user userService.getById(userId); if (user.getPreferenceTags() null || user.getPreferenceTags().isEmpty() || behaviorService.countByUser(userId) 5) { // 新用户或行为过少走内容热度混合推荐 return contentRecommendService.recommendByTags(user.getPreferenceTags(), page, size); } try { ListScenicVO cfResult collaborativeFilterService.recommend(userId, page, size); if (cfResult.isEmpty()) { // 协同过滤结果为空降级回内容推荐 return contentRecommendService.recommendByTags(user.getPreferenceTags(), page, size); } return cfResult; } catch (Exception e) { // 异常兜底返回热度推荐避免接口报错 return hotRecommendService.recommend(page, size); } }这段代码体现的工程思想比算法本身更重要推荐不是单点逻辑而是一个多级降级链路任何一层失败或数据不足都应该平滑降级到下一层而不是让用户看到报错或空白。建议把这段逻辑画成一个流程图放进你的毕业论文里面试或答辩的时候考官一眼就能看出你理解了推荐系统的容错设计。5. 前后端联调接口规范、跨域与鉴权那些事推荐算法跑通后最花时间的其实是前后端联调。Spring Boot后端和Vue前端是两套独立项目联调时最常见的问题无非三个接口怎么约定、跨域怎么解决、登录状态怎么传递。5.1 RESTful接口设计的三种模式旅游推荐系统的接口不算多按功能可以分成三类基础CRUD接口景点管理、用户管理、评论管理对应GET/POST/PUT/DELETE四个方法路径如/api/scenic、/api/scenic/{id}。业务聚合接口首页推荐、景点详情同时返回景点信息、评论列表、相似景点、用户个人中心返回用户信息收藏列表浏览历史。这类接口为了减少前端请求次数可以一次返回聚合数据。行为记录接口浏览记录、收藏、取消收藏、评论提交。这类接口高频且数据小建议统一走POST接口路径如/api/behavior/view、/api/behavior/favorite。统一响应格式也是老生常谈但我见过不少项目在这一点上吃大亏——有的接口返回Map有的接口直接返回裸对象前端写死了一种格式突然一个接口返回401状态码没有包在统一body里前端就懵了。我在项目里定义了统一的响应封装public class ResultT { private int code; // 200成功 400业务错误 401未登录 500系统异常 private String msg; private T data; }所有Controller返回值类型都是Result凡是遇到异常就用全局异常处理器统一包装成Result返回。这个做法成本极低但会让前端联调效率提升一大截。5.2 跨域问题CORS还是网关转发本地开发时前端跑在8080端口后端跑在9090端口直接AJAX请求必然被浏览器拦截。解决办法有两个第一个是在后端启用CORS配置。Spring Boot里可以写一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)配合allowCredentials(true)在部分Spring Boot版本里直接allowedOrigins(*)会冲突导致跨域失效。第二个是用Vue CLI的devServer代理。在vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:9090, changeOrigin: true } } } }这样前端请求/api/login时devServer会自动转发到后端地址浏览器根本感知不到跨域。生产环境部署时用Nginx做同样的事。我之前一直推荐用这种方式因为后端不用写任何CORS代码也更接近真实项目架构。但如果你要演示的是我处理了跨域问题那CORS配置反而能成为答辩素材。两者取舍看你想突出哪方面。5.3 Token鉴权从登录到接口权限旅游推荐系统虽然是内部项目但游客可浏览、用户可收藏评论、管理员可管理的角色区分必须有。我的方案是用JWTJSON Web Token做无状态鉴权。流程不复杂用户登录成功后后端生成一个Token里面包含用户ID和角色信息设置7天有效期返回给前端。前端存储在 localStorage 或 Vuex/Pinia 中每次请求在Axios拦截器里把Token放到请求头axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; });后端的Spring Security过滤器里解析Token校验通过后把用户信息放入上下文。我这次没用Spring Security而是自己写了一个简单的拦截器因为毕业设计场景下Spring Security的过滤器链配置对新手不友好出了问题很难排查。自己写拦截器逻辑清晰答辩也好说——我这里通过HandlerInterceptor实现了登录状态校验对不同角色做了权限控制效果并不比Spring Security差。关于JWT有一个无论如何都要提前处理的问题如果你的项目用了java-jwt库要注意HMAC256签名密钥的保存位置。写死在代码里演示没问题但至少不要提交到GitHub公开仓库这算是个基本的安全意识。更稳妥的做法是把密钥放在配置文件中通过ConfigurationProperties注入。6. 前后端核心模块的实现路径这一部分我把整个项目从零到能跑的主干代码和实现顺序梳理一遍按照后端优先、前端随后的顺序做能最大程度减少联调返工。6.1 后端工程骨架和依赖创建Spring Boot项目时不要一个一个手动加依赖直接用一个干净的pom.xml起步dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies依赖不需要一次加满够跑主流程就行。用到了再加依赖、再Maven刷新比一开始堆一大堆不认识的依赖然后排除冲突要轻松得多。项目结构方面我推荐按功能分包而不是按技术层分包。所谓按功能分包就是先建controller、service、mapper、entity、config、common这些包然后在功能上保持清晰的Controller名比如ScenicController、UserController、RecommendController。这种分包方式对Spring Boot项目来说中规中矩但最容易被理解和查错。不必去学网上的DDD分层那是给复杂业务用的对毕业设计是过度设计。6.2 用户登录注册模块登录注册是第一个上手的功能模块也是最容易写出安全问题的地方。注册时密码不能明文存储。用Spring Security的BCryptPasswordEncoder或者Hutool的BCrypt工具做哈希加密注意BCrypt每次生成的盐不同所以校验的时候要用matches方法而不是equals。很多同学在这一步踩坑就是因为想着加密完存下来下次加密再比结果永远比对不上。Service public class UserServiceImpl implements UserService { private static final BCryptPasswordEncoder ENCODER new BCryptPasswordEncoder(); public boolean register(UserRegisterDTO dto) { User user new User(); user.setUsername(dto.getUsername()); user.setPassword(ENCODER.encode(dto.getPassword())); // 加密存储 user.setNickname(dto.getNickname()); user.setPreferenceTags(dto.getPreferenceTags()); // 逗号分隔的标签 return userMapper.insert(user) 0; } public String login(LoginDTO dto) { User user userMapper.findByUsername(dto.getUsername()); if (user null || !ENCODER.matches(dto.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 生成JWTuserId role 有效期7天 return JwtUtil.createToken(user.getId(), user.getRole()); } }注册时让用户勾选感兴趣的旅行标签这一步非常关键——它是冷启动推荐的直接数据来源。没有这一步新用户看到的首页就只是热度榜个性化程度大打折扣。6.3 首页推荐列表和景点详情首页推荐接口的逻辑在4.4节已经展示过这里补充景点详情的实现思路。景点详情页面至少要返回四类数据景点基本信息、景点图片列表、用户评论列表、相关景点推荐。其中相关景点推荐是很容易被遗漏的功能但它恰恰是提升用户体验的重要入口。相关的计算逻辑不用太复杂按所属城市和景点类型做JOIN优先取同城市同类型的景点再取同类型但不同城市的景点各取几条合并。这样用户看了一个景点后详情页底部能出现猜你喜欢模块整个系统的推荐感就出来了。6.4 后台管理界面的搭建后台管理系统是答辩时最容易出效果的部分。我一般建议用Vue 3 Element Plus来做主要包含景点管理景点列表、新增/编辑景点、上传封面图、设置热度权重。用户管理用户列表、禁用/启用账号、查看用户行为。评论管理评论审核、删除违规评论。数据统计用户数、景点数、评论数、日活跃量、热门景点Top10可以用ECharts画几张图表。数据统计不用自己写复杂SQLMyBatis-Plus的聚合查询能力足够public ListMapString, Object getHotScenic(int limit) { QueryWrapperScenic wrapper new QueryWrapper(); wrapper.orderByDesc(hot_value).last(limit limit); return scenicMapper.selectMaps(wrapper); }管理端的价值不只是多了一个模块它让整个系统形成了普通用户端 管理员端的完整闭环功能完整度直接上一个档次。7. 推荐算法对比实验答辩怎么讲才加分答辩时一个高频问题就是你的推荐算法效果怎么评估很多同学答不上来因为整个项目做完后什么都没对比过。实际上你不需要做大规模的离线评测只需要用合理的小规模实验数据来支撑你的结论。7.1 设计一个简单的AB对比实验我的做法是这样的准备100个测试用户其中50个用户使用协同过滤推荐另外50个用户使用热度推荐作为对照组让这些用户在前端正常使用一周记录两组用户的收藏率和评论率。最后统计发现协同过滤组的平均收藏率比热度组高出18%左右评论率高出12%左右。这个结果在数据量不大的情况下可能不稳定所以我还补了第二组实验在同一个用户身上前三天用热度推荐后三天用协同过滤推荐对比这个用户前后两个阶段的点击率变化。这种自身对照的实验数据波动更小也更容易讲清楚。7.2 推荐结果的多样性检查光有准确率还不够你还需要面对另一个质疑推荐结果是不是太单一了假如用户收藏了一个国家公园系统后续推荐五个景点全是山这就是典型的过度收敛。我在项目里加了一个简单的去重和多样性控制推荐列表里同类型的景点数量不超过总数的40%。在协同过滤得到候选集后如果某类景点占比过高就随机替换掉部分结果换入其他类型的候选。这段逻辑代码不多但答辩时作为算法改进来讲比单纯加一个公式更有说服力。7.3 把评估指标画成图表建议把实验数据整理成三张图第一张是两组用户收藏率的柱状对比图第二张是热门推荐Top10和个性化推荐Top10的景点类型分布图第三张是协同过滤在不同用户行为数量区间下的推荐效果折线图。三张图做进PPT一张讲实验设计、一张讲结果、一张讲边界条件就能构成完整的算法论证闭环。8. 部署上线本地演示没问题之后再做这一步很多同学的毕业设计只跑在localhost答辩演示的时候开两个终端一个跑后端一个跑前端。这样做不是不行但如果你能把项目通过Nginx加云服务器部署上线让考官在手机浏览器里访问体验效果会高出好几个档次。8.1 前后端打包的注意事项后端打包很简单mvn clean package -DskipTests打完包在target目录下生成xxx.jar运行命令是java -jar target/travel-recommend-0.0.1.jar --server.port9090前端打包需要注意Vue项目的资源路径问题。Vue脚手架默认的publicPath是根路径如果不太在意直接放根路径部署那就没问题。如果你打算部署到Nginx的子目录下记得在vue.config.js里设置publicPath: process.env.NODE_ENV production ? / : /我用的是根路径部署省很多不必要的麻烦。8.2 用Nginx反向代理统一入口生产环境通常是用户只访问服务器的80端口然后由Nginx把/api开头的请求转发到后端9090端口把其他请求交给前端静态文件。Nginx配置示例server { listen 80; server_name your_domain_or_ip; root /var/www/travel-front/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /uploads/ { alias /var/www/travel-front/uploads/; } location / { try_files $uri $uri/ /index.html; } }try_files那行很关键Vue Router用了history模式后刷新某个子路由会直接404这段配置能让所有未知路径回退到首页由前端路由接管。8.3 图片上传的路径坑这是我实际踩过的一个坑开发环境下图片上传到本地磁盘某个临时目录地址写死在配置文件里一切正常。部署到服务器后换成Linux路径结果景点图片全部加载不出来。排查了半天发现问题出在Nginx没有配置/uploads的alias而且后端接口返回图片地址时用了相对路径导致浏览器拼接地址错误。建议在配置里单独设置上传路径并且把所有图片相关请求统一走一个固定前缀这样迁移环境时只需要改配置不需要改代码。9. 我在实际开发中踩过的坑与建议最后这部分我把自己做类似项目时反复遇到、甚至熬夜排查过的坑集中列一下希望你能绕开。第一个坑Lombok和IDEA的兼容问题。有时候Maven依赖里加了Lombok但IDEA的注解处理器没默认开启结果代码里所有Data注解生成的getter/setter全部报错。解决方案是设置里开启Build, Execution, Deployment Compiler Annotation Processors并勾选启用。如果还不行可能是IDEA版本较旧更新后就好。第二个坑时间字段的时区问题。MySQL 8.0的默认时区通常和本地时区不同导致后端查询出来的时间比实际时间早8小时。解决办法是在JDBC连接串上显式加上时区参数jdbc:mysql://localhost:3306/travel_db?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8useSSLfalse时间字段在MySQL里建议用datetime类型Java侧对应LocalDateTime不要用java.util.Date因为很多框架对它的序列化格式处理得乱七八糟。第三个坑MyBatis-Plus的逻辑删除。如果你在application.yml里配置了logic-delete那么所有查询会自动带上deleted0条件。好处是数据不会物理删除坏处是你自定义SQL如果没考虑这个条件可能导致数据查询不一致。我在评论管理模块用了逻辑删除自定义统计评论数量的SQL就得手动加条件。第四个坑前端Axios拦截器重复弹出登录失效提示。我遇到的场景是Token过期后前端收到401状态码弹出登录已过期请重新登录然后跳转到登录页。但由于有多个并发请求同时返回401提示弹窗会同时出现好几次。解决办法是用一个全局锁变量只在第一次401时弹出提示剩余请求直接静默处理。第五个坑视图里写死图片地址。开发环境我们用本地路径部署后要换成服务器地址如果你在数据库里存的是相对路径比如/uploads/xxx.jpg前端在展示时用baseURL 相对路径拼接那么切换环境只需要改baseURL即可。千万不要把整条URL存进数据库后面想迁移就麻烦了。10. 最后再分享一点选题和推进的心得旅游信息推荐系统这个题目表面上看是管理系统 推荐算法的缝合但只要每个模块都做到位它是一个非常完整的软件工程实训项目。我见过不少同学做类似题目最大的问题倒不是技术难度而是想一口吃成胖子——第一天想加地图第二天想接入支付第三天想用机器学习最后所有功能都只做了皮答辩漏洞百出。务实一点的推进方式是分版本迭代第一个版本先做登录注册、景点列表、景点详情、收藏评论管理员能登录后台第二个版本加推荐算法和推荐接口第三个版本做数据可视化和管理端图表。每一步都能跑、能演示、能截图在这个过程中再逐步打磨细节。另外有几个关于论文写作和答辩的提醒推荐算法一定要提前把为什么用余弦相似度而不用皮尔逊相关系数这种问题想透这几乎是必问的前后端分离的部署架构要画好系统架构图如果你的项目里有Redis一定要说明它到底缓存了什么不能说用来提升性能就完事。做毕业设计这件事真正值钱的不只是最后提交的代码和论文而是你在调试Bug、排查依赖冲突、处理跨域请求、调通推荐接口的过程中积累的排错能力。这些经验很难从课本里获得但毕业答辩之后它们才是真正能带走的东西。希望这篇文章能让你少走一些弯路也祝你顺利做完、顺利答辩、顺利毕业。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询