资讯动态

SpringBoot+协同过滤:甘肃旅游景点推荐平台毕业设计实战解析

发布时间:2026/9/18 5:57:57 来源:尧图企业网站定制
毕业设计选“甘肃旅游景点推荐平台”这个题目说实话在Java后端方向的毕设里属于非常典型的“进可攻退可守”的选择。它不像商城、博客那样烂大街又不像AI算法类题目那样容易把自己逼到死角。更重要的是springboot 景点推荐这个组合既能展示你的后端功底又能让“推荐”这个亮点在答辩时成为一个很好的加分故事。这篇就围绕这个项目从选题逻辑、系统设计、核心算法落地、到“论文和代码不一致”这种经典翻车现场一次性讲透。1. 项目定位与整体设计思路1.1 为什么“旅游景点推荐”是毕设性价比之王先聊点实在的。毕业设计最怕什么不是不会做是做完之后不知道在讲什么。很多同学选“学生管理系统”“图书管理系统”功能做完了答辩时只能说“实现了增删改查”这种话一出口基本就暴露了项目的含金量。而“甘肃旅游景点推荐平台”天然带两个关键词一个是“旅游”有业务场景有用户需求有数据可以分析另一个是“推荐”这是算法和智能化的切口可以在论文里写出完整的“数据建模—算法选型—效果评估”链路。选题还有一个隐含的优势地域限定得非常具体。甘肃的旅游资源非常丰富但又不像北京、上海那样被做了成千上万遍。这意味着你在数据采集、需求分析部分可以写出真正属于自己的内容而不是满篇抄同行的表述。答辩老师看腻了千篇一律的“某大学图书馆管理系统”看到一个地域特色鲜明的题目印象分天然会高一些。1.2 核心需求到底拆出了哪些功能点按标准的软件工程流程拆下来前台用户端和后台管理端的分工大概是这样的。前台面向普通游客核心诉求是“来甘肃玩怎么快速决定去哪儿”。所以必须有的功能是景点列表展示、景点详情图片、简介、开放时间、门票价格、所在城市、按城市或景点类型筛选、搜索以及用户登录注册、收藏景点、发表评论、评分。这些功能做出来前台的骨架就完整了。后台面向管理员核心诉求是“把内容管起来”。景点信息管理新增、编辑、上下架、用户管理、评论管理、收藏数据的查看。如果做得好一点还可以加一个数据统计面板比如每个城市的景点数量、评论热门词等不用特别复杂够展示就行。推荐功能是这个平台的灵魂。游客登录后首页不再是简简单单按默认排序展示景点而是基于用户的历史行为收藏、评分、浏览记录生成一个“猜你喜欢”的个性化列表。这个功能听起来高级但在实现层面用基于物品的协同过滤或者基于标签的余弦相似度推荐完全够用不需要堆深度学习模型那是自己给自己找麻烦。1.3 技术栈选型为什么是“SpringBoot Vue”这个万能组合后端选SpringBoot不用多说这是当前Java后端的绝对主流。企业里在用教学大纲里在教招聘要求里在写选了它至少不会错。SpringBoot最大的优势不是它有多强而是它帮你把几乎所有的配置都“约定”好了你只需要关注业务代码这种体验对学生来说非常友好。前端选Vue是因为它和SpringBoot的组合在整个开发社区里已经形成了非常成熟的前后端分离方案。Vue的生态完善Element UI能把后台管理页面搭得非常快而且网上有大量的现成模板可以借鉴。对于一个以“完成毕设”为首要目标的同学来说效率就是生命。1.4 数据库设计从ER图到三张核心表数据库设计是论文里占篇幅最多的部分之一也是答辩老师一眼就能看出你水平的地方。建议直接按第三范式来设计不要过度冗余表之间别搞一堆复杂的关联。围绕这个平台最核心的表大概有这几张用户表user、景点表scenic_spot、评论表comment、收藏表favorite、评分表rating如果做了游记功能还要加一张游记表travel_note。每张表的字段要符合“够用但不冗余”的原则。比如景点表包含景点名称、所在城市、景点简介、详细描述、图片URL、门票价格、开放时间、建议游玩时长、热度值其中热度值可以用初期的手动值加上后面用户行为统计值来动态更新这种细节写到文档里会很加分。在写ER图之前先想一想表之间的关系一个用户可以收藏多个景点一个景点可以被多个用户收藏这是典型的多对多关系所以要有收藏表来承接一个景点有多条评论一条评论属于一个用户这是两个一对多关系所以评论表里要同时存用户ID和景点ID。2. 核心功能模块与推荐算法落地2.1 用户端前台功能每个按钮背后的接口逻辑前台模块做得好不好直接决定了演示效果。别小看一个景点列表里面有很多可以深挖的细节。景点列表页通常要做分页加条件筛选。SpringBoot后端用MyBatis Plus的PageSchenicSpot对象接收分页参数配合LambdaQueryWrapper写条件查询几行代码就能搞定。需要支持按城市、按景点类型、按价格区间这几个维度筛选每个筛选条件在Wrapper里加一个eq或者between就够了。景点详情页要展示的基本信息在景点表里都有。注意一个细节详情页的图片不要直接存一个URL最好存一个JSON数组格式的字符串后端返回给前端后再由前端解析成轮播图列表。这种处理方式比一张图展示更有“商业产品”的感觉。详情页还需要展示评论列表和景点评分评论列表按时间倒序分页查询评分用AVG()聚合一下就行。收藏功能是用户行为数据的重要来源。用户点收藏、取消收藏前端调两个接口后端对应做insert和delete操作。收藏表在insert之前必须先做一次查询判断这个用户对这个景点是否已经收藏避免重复数据破坏唯一性约束。登录注册模块建议直接用JWT方案不要用Session。SpringBoot集成JWT也不复杂引入jjwt依赖登录成功后签发一个token返回给前端前端每次请求都带上后端写一个拦截器校验。这个方案在前后端分离项目里是标准做法也方便你在论文里写出一小节“基于JWT的用户认证机制”作为技术亮点。2.2 后台管理端功能不要只做CRUD凑页面后台管理端是很多学生最容易忽略的部分觉得能加数据能删数据就算完事。其实后台恰恰是展示工程素养的地方。景点管理模块除了基本的增删改查一定要加上图片上传功能。图片上传的实现在SpringBoot里很成熟——接收MultipartFile把文件写到服务器的某个目录下然后把访问路径保存到数据库。注意一个细节文件保存的文件夹要按日期分目录比如/upload/2025/05/15/xxx.jpg这样做一方面避免单目录文件过多另一方面也符合大多数商用系统的习惯。数据分析模块哪怕做得再简单也建议加一个。最基础的是在后台首页展示几张统计图表景点数量按城市分布柱状图、评论数量走势折线图、景点热度排行TOP10表格。后端写几个带GROUP BY的统计SQL前端用ECharts渲染。这一块能显著提升项目的“完成度观感”因为它在视觉上告诉答辩老师——这不只是一个CRUD系统而是一个有数据意识的应用。2.3 推荐算法怎么落地协同过滤的工程化实现推荐模块是这个项目的核心亮点也是论文中最值得浓墨重彩的部分。先说结论基于物品的协同过滤Item-Based Collaborative Filtering是最适合这个场景的算法。原因很简单——用户数量可能不多但景点数量是相对稳定的物品间的相似度矩阵计算量可控而且实现起来比基于用户的协同过滤更容易讲清楚。算法的核心逻辑分几步。第一步构建“用户—物品”评分矩阵。评分数据的来源有三个显式评分用户打的星、隐式反馈收藏记1分、浏览记0.5分、评论记1分。第二步计算景点之间的相似度最常用的是余弦相似度。比如景点A和景点B的评分向量分别是[5, 4, 0, 3]和[4, 5, 2, 0]余弦相似度的公式是计算两个向量夹角的余弦值值越大说明越相似。第三步就是根据用户的历史行为找到他互动过的景点列表然后从相似景点中挑出没有被用户看过的、相似度最高的N个景点推荐给他。在工程实现上不需要每次都实时计算相似度矩阵。更合理的做法是写一个定时任务SpringBoot的Scheduled注解就能做到每天凌晨跑一次把相似度矩阵的结果存到一张推荐结果表里。用户登录时后端直接从推荐结果表里查询而不是临时算。这个细节非常关键既能秀出你对性能的思考又能在论文里写出一个完整的“离线计算 在线推荐”架构。2.4 冷启动问题如何处理任何推荐系统都绕不开冷启动问题。新用户没有任何行为数据系统怎么给他推荐新景点刚上线没有任何人评分它怎么被用户发现处理思路分两类。对新用户采用基于规则和热度的推荐——默认返回数据库中热度值最高的景点列表再加一个“热门城市”维度的推荐兜底。对新景点可以适当分配一些曝光量在推荐结果里穿插一到两个新景点保证新内容有机会被用户看到。这部分的策略不需要多复杂但要能说清楚因为“如何处理冷启动问题”几乎是答辩老师必问的问题。3. 关键技术点与SpringBoot实战细节3.1 SpringBoot自动配置原理在项目中的体现很多同学能把SpringBoot项目跑起来但被问到底层原理就老实了。在论文的技术选型部分建议花一到两页讲清楚SpringBoot的自动配置机制。再绕不开的就是SpringBoot的自动配置机制。简单理解就是SpringBoot通过spring.factories或AutoConfiguration.imports文件里配置的自动配置类在项目启动时自动加载你引入的依赖对应的配置。比如你引入了spring-boot-starter-webSpringBoot会自动帮你配置Tomcat、DispatcherServlet、消息转换器等组件引入了mybatis-plus-boot-starter会自动帮你配置SqlSessionFactory和Mapper扫描。写这段技术文档的窍门是不要只写概念要结合项目讲。你可以写“本项目通过在pom.xml中引入相关starter依赖并通过SpringBootApplication注解启动自动配置实现了仅需少量配置即可完成项目搭建的目标”然后再详细展开自动配置背后的条件注解ConditionalOnClass、ConditionalOnMissingBean机制。3.2 配置文件里的参数设计与脱敏技巧application.yml的整体结构要规范。开发环境的数据源连接、Redis连接、文件上传路径等基础配置放在application-dev.yml生产环境的同类配置单独放一份application-prod.yml主配置文件里用spring.profiles.active来切换。这种分环境设计的习惯在企业里是基本要求写到论文里就是加分项。敏感信息处理这块有讲究。数据库密码、JWT密钥等敏感配置不要硬编码在配置文件里。一种可行的做法是使用ConfigurationProperties注解配合自定义配置类再结合Jasypt这个工具对密钥进行加密。Jasypt集成到SpringBoot项目里非常方便引入依赖后在配置里加上加密后的密文项目启动时会自动解密。把这个写进论文的“系统安全设计”章节内容的档次立刻就不一样了。3.3 统一返回结果与全局异常处理这是一个绝大多数毕设项目都没做到位、却极其重要的细节。很多同学的接口返回格式不统一有的直接返回实体类有的返回Map有的成功返回true、失败返回null。这种杂乱无章的返回格式会让开发联调阶段非常痛苦。正确做法是定义一个统一的返回类ResultT包含code、message、data三个字段所有Controller接口的返回值都用这个类包装。前端通过判断code是否为200来决定下一步操作。全局异常处理用RestControllerAdvice注解配合ExceptionHandler来捕获不同类型的异常。业务异常返回自定义错误码系统异常返回通用错误码并打印完整日志方便排查。这样Controller层就变得非常干净每一种异常都有统一的出口前端也能根据错误码弹出对应提示。3.4 MyBatis Plus还是JPA写给选择困难症MyBatis Plus是国内企业项目的事实标准如果你的毕业设计不需要特别复杂的SQL选它效率最高。它提供了内置的BaseMapper接口单表CRUD完全不需要手写SQL直接用selectById、selectList这些方法就行。条件查询用LambdaQueryWrapper拼接比写XML文件里的动态SQL可读性好得多。JPA在英文技术资料和教学场景里更常见如果你打算以后申请国外院校或者在简历里突出国际化能力选JPA也不是不行。但国内答辩场合推荐MyBatis Plus。它还有一个特别实用的能力——代码生成器。如果你做完了数据库设计可以用MyBatis Plus Generator一键生成实体类、Mapper接口、Service接口及实现类、Controller层代码骨架可以把大量机械性编码时间压缩到十分钟以内。如果你的项目中有一部分复杂查询需要手写SQL也完全可以混用。MyBatis Plus支持在Mapper接口里写Select注解也支持在XML文件里灵活编写。比如推荐模块中的相似度结果查询、统计报表中的聚合查询这类SQL手写反而更清晰。4. 实操过程与核心环节实现4.1 开发环境准备与项目初始化整个项目的开发环境我用的是Java 8 Maven 3.6 MySQL 5.7。Java 8虽然老但胜在稳定绝大多数基于SpringBoot 2.x的项目都跑在Java 8上。如果你的机器装的是更高版本的JDK记得在pom.xml里把java.version设为1.8否则编译会报错。创建项目的方式有两种。一种是访问Spring Initializr网站选择Spring Boot 2.7.x版本勾选Web、MySQL Driver、MyBatis Plus这里注意Initializr网站本身没有MyBatis Plus选项需要后续在pom.xml里手动加依赖等依赖后生成项目压缩包。另一种方式是使用IDEA内置的Spring Initializr过程和网页版没有区别图形化界面勾选即可。项目生成后pom.xml里要加几个核心依赖spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、jjwtJWT、hutool-all工具类库。Lombok非常值得一用通过Data注解自动生成getter/setter实体类可以少写几十行冗余代码。Hutool工具包能提供很多现成的工具方法省去自己手写的时间。4.2 数据库建表脚本的核心SQL参考直接用一段建表SQL说明核心结构基本覆盖了系统的核心实体。推荐表的设计参考了推荐结果存储定时任务会每天更新这张表。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码加密存储, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4; CREATE TABLE scenic_spot ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 景点名称, city varchar(20) DEFAULT NULL COMMENT 所在城市, type varchar(20) DEFAULT NULL COMMENT 景点类型如自然风光/人文古迹, intro text COMMENT 景点简介, detail text COMMENT 详细描述, cover_image varchar(255) DEFAULT NULL COMMENT 封面图URL, images varchar(1000) DEFAULT NULL COMMENT 轮播图URLJSON数组, price decimal(10,2) DEFAULT 0.00 COMMENT 门票价格, open_time varchar(50) DEFAULT NULL COMMENT 开放时间, heat int(11) DEFAULT 0 COMMENT 热度值, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4; CREATE TABLE user_favorite ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, scenic_id bigint(20) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_scenic (user_id,scenic_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;需要留意的是如果直接在聚合函数或分组查询中用了MySQL 5.7的默认配置遇到sql_modeonly_full_group_by的问题时需要调整sql_mode配置或者查询里严格遵循分组字段的规范写法。4.3 实现一个完整的景点列表查询接口以“根据城市筛选分页查询景点列表”为例看看一个标准的Controller-Service-Mapper三层接口是怎么写的。Controller层负责接收请求参数和返回统一格式结果RestController RequestMapping(/api/scenic) public class ScenicSpotController { Autowired private ScenicSpotService scenicSpotService; GetMapping(/list) public ResultPageScenicSpotVO list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String city, RequestParam(required false) String type) { PageScenicSpotVO page scenicSpotService.getScenicPage(pageNum, pageSize, city, type); return Result.success(page); } }Service层负责处理业务逻辑包括判断参数、分页查询、类型转换。由于使用了MyBatis Plus查询条件直接通过LambdaQueryWrapper即可完成不需要任何XML的代码。Service public class ScenicSpotServiceImpl extends ServiceImplScenicSpotMapper, ScenicSpot implements ScenicSpotService { Override public PageScenicSpotVO getScenicPage(Integer pageNum, Integer pageSize, String city, String type) { PageScenicSpot page new Page(pageNum, pageSize); LambdaQueryWrapperScenicSpot wrapper new LambdaQueryWrapper(); if (StrUtil.isNotBlank(city)) { wrapper.eq(ScenicSpot::getCity, city); } if (StrUtil.isNotBlank(type)) { wrapper.eq(ScenicSpot::getType, type); } wrapper.orderByDesc(ScenicSpot::getHeat); PageScenicSpot result this.page(page, wrapper); // 实体转VO隐藏不需要返回给前端的字段 PageScenicSpotVO voPage new Page(pageNum, pageSize, result.getTotal()); ListScenicSpotVO voList result.getRecords().stream().map(scenic - { ScenicSpotVO vo new ScenicSpotVO(); BeanUtils.copyProperties(scenic, vo); return vo; }).collect(Collectors.toList()); voPage.setRecords(voList); return voPage; } }注意一下代码中用了MyBatis Plus的LambdaQueryWrapper通过方法引用ScenicSpot::getCity来指定查询字段这种方式在编译期就能发现字段名拼写错误比字符串硬编码安全得多。这是MyBatis Plus最值得推荐的一个细节。4.4 推荐接口离线计算在线查询的完整示例推荐模块的核心是相似度矩阵计算。下面给出一个简化版的示例使用基于物品的协同过滤通过Scheduled定时任务每天凌晨执行一次计算并刷新推荐结果。Component public class RecommendTask { Autowired private ScenicSpotMapper scenicSpotMapper; Autowired private UserFavoriteMapper userFavoriteMapper; Autowired private RecommendResultMapper recommendResultMapper; Scheduled(cron 0 0 3 * * ?) public void calculateSimilarity() { // 1. 加载所有景点 ListScenicSpot allScenic scenicSpotMapper.selectList(null); int n allScenic.size(); // 存储景点ID与下标映射 MapLong, Integer idToIndex new HashMap(); for (int i 0; i n; i) { idToIndex.put(allScenic.get(i).getId(), i); } // 2. 加载用户-景点行为矩阵 ListUserFavorite allFavorites userFavoriteMapper.selectList(null); MapLong, SetLong userItems new HashMap(); for (UserFavorite fav : allFavorites) { userItems.computeIfAbsent(fav.getUserId(), k - new HashSet()) .add(fav.getScenicId()); } // 3. 计算景点间相似度余弦相似度简化版 double[][] similarityMatrix new double[n][n]; for (int i 0; i n; i) { for (int j i 1; j n; j) { SetLong usersI new HashSet(); SetLong usersJ new HashSet(); for (Map.EntryLong, SetLong entry : userItems.entrySet()) { if (entry.getValue().contains(allScenic.get(i).getId())) { usersI.add(entry.getKey()); } if (entry.getValue().contains(allScenic.get(j).getId())) { usersJ.add(entry.getKey()); } } // 计算共同喜欢两个景点的用户集合交集大小作为相似度度量 SetLong intersection new HashSet(usersI); intersection.retainAll(usersJ); double similarity intersection.size() / Math.sqrt(usersI.size() * usersJ.size() 1e-6); similarityMatrix[i][j] similarity; similarityMatrix[j][i] similarity; } } // 4. 对每个用户生成推荐列表 for (Map.EntryLong, SetLong entry : userItems.entrySet()) { Long userId entry.getKey(); SetLong liked entry.getValue(); MapLong, Double scoreMap new HashMap(); for (Long likedScenicId : liked) { int likedIndex idToIndex.get(likedScenicId); for (int j 0; j n; j) { Long candidateId allScenic.get(j).getId(); if (liked.contains(candidateId)) { continue; } scoreMap.merge(candidateId, similarityMatrix[likedIndex][j], Double::sum); } } // 取前10名写入推荐结果表 ListLong recommendList scoreMap.entrySet().stream() .sorted(Map.Entry.Long, DoublecomparingByValue().reversed()) .limit(10) .map(Map.Entry::getKey) .collect(Collectors.toList()); for (int i 0; i recommendList.size(); i) { RecommendResult record new RecommendResult(); record.setUserId(userId); record.setScenicId(recommendList.get(i)); record.setScore(scoreMap.get(recommendList.get(i))); record.setRank(i 1); recommendResultMapper.insert(record); } } } }这段逻辑虽然简化了但完整表达了基于物品的协同过滤的核心思想构建物品间的相似度关系再根据用户历史行为找出最相似的未浏览物品。在线接口就简单多了用户进入首页时从推荐结果表中查出该用户前10条记录根据scenic_id关联查询景点详情返回前端。4.5 前端如何与后端高效联调前后端联调是毕设开发中耗时最多的环节。效率最高的方式是用Vue CLI或Vite创建一个项目安装Element UI和Axiosaxios实例统一配置baseURL和请求拦截器。在请求拦截器里从localStorage中取出JWT token添加到请求头的Authorization字段中这样就实现了所有请求自动携带凭证。在响应拦截器里统一处理返回结果如果code是200就正常解包如果不是200就弹出错误提示。开发环境还有一个非常重要但不被注意的配置跨域。前后端分离项目前端跑在8080端口、后端跑在8081端口两者之间必然存在跨域问题。后端的处理方式是在SpringBoot里写一个配置类实现WebMvcConfigurer接口重写addCorsMappings方法允许指定的前端地址跨域访问。前端的Vite配置里也可以设置proxy代理把/api开头的请求转发到后端。两种方式选一种即可我更推荐用后端解决因为这样前端构建部署之后不会有额外问题。4.6 远程调试被别人远程操作到底做了什么很多同学买了源码之后最担心的就是“远程调试”这一个环节。其实远程调试在毕设项目里绝大多数时候做的是这么几件事帮你把Java环境、Maven环境、MySQL装好导入项目并修改配置文件里的数据库密码为本机的初始化数据库启动后端验证接口启动前端验证页面。整个过程本质上就是“帮你把项目在你自己的电脑上跑起来”。有远程调试需求的话自己电脑上最好先准备好IDEA、Navicat或者DataGrip。调试过程中我会建议你把每一步操作都截图保存尤其是环境变量的配置和数据库导入这些步骤。因为负责远程调试的人不可能天天都在你身边项目一旦中途跑出问题你手里有截图记录自己排查起来会快很多。5. 常见问题与排查技巧实录5.1 启动报错端口被占用SpringBoot项目默认端口是8080如果被其他程序占用启动日志里会直接看到Port 8080 was already in use这样的报错。处理方式有几种第一在application.yml里修改server.port为其他端口比如8081第二直接把占用端口的进程找出来杀掉。在Windows上用netstat -ano | findstr 8080查到占用进程的PID再到任务管理器里结束那个进程。在Mac或Linux上用lsof -i:8080查PID然后kill -9 PID。建议你优先选择第一种方式因为换一个端口对项目本身没有任何影响而杀进程有可能会误杀别的服务。5.2 数据库连接失败连接信息核对与驱动版本排除这类问题最多原因也最集中。第一application.yml里数据库的名称打错了第二用户名和密码不对第三数据库服务本身没启动第四MySQL版本和驱动版本不兼容。前三个都好排查第四个是最隐蔽的。举个例子如果你的MySQL是8.0以上版本而pom里引入的MySQL驱动是5.1.47这个老版本启动时就会报Public Key Retrieval is not allowed这个错误。解决办法是换用8.0的驱动并且在连接参数里加上allowPublicKeyRetrievaltrueuseSSLfalse。顺便提一句MySQL 8.0驱动类的全限定名也改了从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver配置时不要写错。5.3 Maven依赖下载慢或报红国内访问Maven中央仓库速度很慢依赖下载失败是家常便饭。解决办法是配置阿里云镜像。在Maven的settings.xml文件里找到mirrors节点加入阿里云的mirror配置。配置之后重新导入项目IDEA会自动开始重新下载依赖基本都能顺利解决。如果某个依赖始终下载不下来还有一种情况是版本号有问题。SpringBoot 2.7.x的版本对应MyBatis Plus 3.5.x是兼容的但如果你用的是SpringBoot 3.x要对应用MyBatis Plus 3.5.5以上版本同时JDK版本要求17以上这个时候会连带着改不少东西建议还是老老实实用SpringBoot 2.7.x JDK 8这个组合踩坑最少。5.4 前端调接口报后端动不动返回404前端的接口路径和后端Controller的请求映射不一致是联调阶段最常见的低级错误。后端的映射是通过RequestMapping和GetMapping里写的路径拼接起来的前端axios请求的URL必须完整一致。建议在开发过程中约定一个习惯后端每写完一个接口就在浏览器地址栏直接访问一次该接口如果能正常返回JSON数据再通知前端开始联调。这样能避免“前后端同时盲猜路径”的低效状态。后端自测接口还有一个利器——Swagger或SpringDoc集成到项目里只需要加一个依赖和几行配置就能自动生成接口文档和测试页面前端看着文档请求接口路径问题基本就杜绝了。5.5 时间字段差8小时数据库里存的时间比实际时间早了8个小时或者后端返回给前端的时间格式不对。根源通常出在时区配置上。后端的解决方案需要在JDBC连接参数中加serverTimezoneAsia/Shanghai在Jackson配置里指定日期格式和时区。如果你使用LocalDateTime类型注意在实体类的对应字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解。这个细节很多人到了答辩前夕才会发现那时候再改全项目的时间格式会比较手忙脚乱提前处理掉最省心。5.6 表格整理典型报错速查清单现象最常见原因排查方向项目启动直接失败端口被占用或依赖缺失查启动日志最底部Caused by段接口返回500数据库连接配置问题或SQL异常看IDEA控制台完整异常栈不要只看第一行前端页面数据出不来跨域未配置或路径不一致打开浏览器开发者工具看Network面板数据库查询中文乱码数据库连接未指定UTF-8连接URL加characterEncodingutf8登录后刷新页面就失效JWT生成或校验逻辑有Bug考虑持久化token到localStorage并检查过期时间图片上传后访问404静态资源映射未配置添加WebMvcConfigurer映射/upload/**到本地目录5.7 答辩前最容易忽视的三个细节首先数据库里的测试数据不能太敷衍。不要只用“景点1”“景点2”这种毫无意义的数据至少要准备20条以上真实感强的甘肃景点数据比如敦煌莫高窟、张掖七彩丹霞、天水麦积山石窟、嘉峪关关城每一项都配上真实的简介和开放时间。数据越真实演示观感越好答辩老师查询数据时也不会一眼识破是测试数据。其次要准备好演示脚本。很多同学答辩时手忙脚乱是因为没有一个清晰的演示路径。建议按这个顺序走用户注册登录展示JWT鉴权→搜索甘肃景点展示分页和筛选→点击详情展示图片和评论→收藏和评分产生行为数据→进入推荐页展示个性化推荐结果→切换到管理员账号展示后台管理→演示数据统计。这个顺序本身就是一个连贯的故事从开始到结束都有逻辑衔接。最后不要忽视“杀后台”演示。打开系统的进程列表把后端的Java进程直接杀死给答辩老师看前端页面的报错提示。这个瞬间是非常大的加分点——它证明了你的报错处理机制是真实存在并且起作用的同时也证明了这个项目不是用静态页面糊弄出来的。6. 源码获取与二次开发的建议先说明一下我对网上那些“全套源码 文档 远程调试”商品的态度。毕设阶段买一套完整源码来学习本身没什么问题关键在于你怎么用。如果你买来之后只是交差了事那最后答辩照样会露馅如果你当成一个高质量的项目范例逐行读懂每一处逻辑然后把别人的代码消化吸收变成自己的在答辩时能够清晰地讲出系统架构、核心算法、数据库设计的来龙去脉那这套源码就是有价值的参考工具。拿到源码后第一步是完整读一遍文档和数据库脚本把系统的数据流理清楚用户从页面发起请求经过Controller层调到Service层再到Mapper层访问数据库返回结果再逐层封装给前端。这个过程顺下来你对整个项目的理解就会达到一个新的高度。第二步是尝试做二次开发。我的建议是加一个“游记分享”功能用户可以发布图文形式的旅游心得。为什么要推荐加这个因为大部分景点推荐平台的源码模板里是没有这个功能的你加上了项目就有别人没有的差异化亮点。而且这个功能的实现也不复杂本质就是一张游记表加上发布、列表展示、详情展示三个接口工作量不大但答辩时的“创新点”就出来了。第三步是提前准备好“如果让我重做一次”这个问题的答案。答辩老师很喜欢问这道题。不要简单地回答“我觉得哪里做得不好”要结合技术选型来谈。比如“如果重新设计我会在推荐模块引入Redis缓存相似度矩阵进一步提高响应速度”“我会把前后端改造为Docker容器化部署”等等。这就能展示出你确实思考过项目的优化方向也能展现出你对技术有一定的视野。我在实际做这类毕设项目的过程中最大的一个体会是毕业设计拼的从来不是某个技术的深度而是整个系统的完整度和工程化程度。一个功能齐全、文档完善、演示流畅的旅游景点推荐平台哪怕算法只是个简单的协同过滤也远比一个看上去用了牛逼框架却跑不通的半成品项目要有价值得多。把每一步走扎实该实现的都实现到位把每一个你写进文档的细节都弄明白答辩的时候你就已经赢了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价