ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于SpringBoot的绍兴旅游系统毕业设计全流程解析

基于SpringBoot的绍兴旅游系统毕业设计全流程解析 每年毕业季我都能收到一堆师弟师妹的私信问得最多的就是“毕设到底该选什么题”。有人纠结前后端分离的Vue还是Thymeleaf模板有人纠结管理系统是不是太没技术含量还有人直接问“基于SpringBoot的某某旅游系统”这类题目到底值不值得做。今天我就拿手头这套《基于SpringBoot的绍兴旅游系统》当例子把选题思路、技术选型、核心模块怎么写、数据库怎么设计、哪些地方最容易翻车从头到尾给自己人捋一遍。我得先泼一盆冷水很多毕设题目看着“烂大街”比如各种“XX管理系统”、“XX商城”但如果你把“XX旅游系统”做出地域特色、做出业务闭环、做出真实可演示的亮点完全可以让答辩老师眼前一亮。毕竟毕业设计的本质是向老师证明你掌握了从前端到后端、从数据库到部署的完整工程开发能力而不是证明你用到了多冷门的技术。1. 内容整体设计与思路拆解1.1 绍兴旅游系统这个题目的真实需求在哪先别急着写代码第一步是把题目本身拆明白。旅游类系统放在SpringBoot这个框架下核心业务无非就是三块信息展示、线路与景点管理、用户互动收藏、评论、预订。绍兴作为典型的江南旅游城市有着“古城名人故里乌篷船黄酒文化”等等一系列标签整个系统的数据建模、搜索逻辑、路线推荐方式都可以也应当围绕这些标签去设计。这套系统最适合什么人群如果你是计算机专业、软件工程专业想稳扎稳打走一个“数据驱动前后端分离权限控制”的完整流程选这个题目就合适。如果你还不会MyBatis-Plus、还没搞清楚SpringBoot的自动装配是怎么回事也推荐先拿这种业务边界清晰的项目练手——它比“通用商城”更容易讲出业务故事又比“秒杀系统”这种高并发项目更容易在规定时间内落地。我见过不少学生把旅游系统做成了“景点展示墙”只有列表页加详情页没有任何业务闭环。那评委一定会问你数据库里建了那么多表逻辑上怎么关联的用户除了看页面还能干什么所以我的建议是至少要把“用户浏览景点—收藏—生成旅游路线—在线预订/留言”串成一条完整的业务链让系统有“可操作感”。1.2 方案选型的核心博弈前后端分离还是服务端模板对于SpringBoot毕设第一个大决策是“前端怎么搞”。有两个主流选择我在这里把各自利弊说明白方案技术栈优点缺点建议场景方案A前后端分离Vue3/Element-Plus SpringBoot RESTful API技术栈新、结构清晰、前端交互体验好、答辩演示效果好工作量大需要额外处理跨域、Token鉴权、前端打包部署推荐方案B服务端渲染SpringBoot Thymeleaf Bootstrap上手快、项目结构紧凑、不用处理跨域、直接跑一个应用前后端耦合较深页面交互观感“学生气”时间紧或前端基础弱如果是我带学生我会优先推方案A。原因很简单近几年面试官非常吃“前后端分离”这套而且网络上SpringBoot Vue的前后端分离脚手架已经非常成熟照着理清一遍路由、鉴权、接口请求封装本身就是很值得写进简历的经历。答辩时老师问起“你如何解决的跨域问题”“你这个token是怎么设计的”你有东西可讲这就是加分项。当然如果去掉“讲解定制”这些附加服务只求独立完成方案B确实省事得多。但千万别只图省事毕竟这是毕业设计功夫做足了对就业也有好处。1.3 基于“地域标签”构建数据模型的思路“绍兴旅游系统”和“通用旅游系统”的差别就在数据建模上。我设计时直接引入了“标签”和“线路”两张核心表景点表保存基础信息名称、简介、开放时间、门票价格、封面图、所属区县、经纬度。标签表维护地域标签“鲁迅故里”“乌篷水乡”“黄酒文化”“书法圣地”并与景点建立多对多关系。线路表把多个景点按推荐顺序拼接成路线一个游客可以在前端看到“绍兴一日游”“兰亭书法之旅”等等现成推荐也可以自己勾选景点生成定制路线。这么设计的好处一目了然无论是做景点搜索还是做首页“按标签逛绍兴”还是后台按标签推荐线路SQL都很好写业务好扩展答辩时也能讲得明明白白。如果只做几张CRUD表那真就把题目的价值做没了。2. 核心细节解析与实操要点2.1 SpringBoot项目骨架与依赖版本怎么选SpringBoot版本选择网上问得特别多也是毕设开始就卡住很多人的问题。我的建议是不必追新选一个自己熟悉的稳定版本。比如SpringBoot 2.7.x系列搭配JDK 8或JDK 11再配上MyBatis-Plus 3.5.x、MySQL 8.x这套组合的资料最丰富踩坑最少几乎任何一家查报错社区都能搜到答案。你非要去用SpringBoot 3.x JDK 17也不是不行但有些老教程里的写法已经不兼容容易在答辩前夜把自己逼疯。除了稳定还要注意“依赖一致性”。一个常见的悲剧是代码生成器用的MyBatis-Plus版本是3.5.1手写代码里却引入了另一个版本SpringBoot升级到2.7后默认的连接池从Tomcat JDBC改成了HikariCP而你还在用druid的starter结果配置项互相覆盖。排查起来非常耗时间。我建议每个依赖都用Maven的dependencyManagement或者Spring Initializr生成的原始BOM做基准别手动乱改版本号。2.2 分层架构与包结构设计的“教科书模板”毕设项目最怕的就是Controller里堆了五百行业务代码Service层形同虚设。这种代码你自己能跑但答辩老师翻源码时一定不会给高分。我常用的包结构模板是这样com.shaoxing.travel ├── controller // 接收参数、返回结果 ├── service // 业务逻辑层写接口与实现类 ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体 ├── dto // 前端传参/返回视图模型 ├── config // 配置类跨域、拦截器、WebMvc等 ├── common // 通用工具、统一返回结果、异常处理 └── utils // 工具类Controller永远只做参数校验和调用ServiceService里写事务注解和业务逻辑Mapper里用MyBatis-Plus的LambdaQueryWrapper写条件查询。实体类不要和DTO混用因为数据库字段和前端显示字段往往不对齐比如数据库存了一个status前端要显示“开放/关闭”这种转换就该交给DTO。2.3 数据库设计五张表讲清一路业务前面讲了标签思路下面给出核心表结构参考别直接照搬但照思路扩展没问题。用户表userid、username、passwordBCrypt加密、nickname、avatar、phone、role管理员/普通用户/商家、create_time。景点表scenicid、name、description、address、open_time、ticket_price、cover_image、area、latitude、longitude、status、view_count。标签表tagid、name、remark。景点标签关联表scenic_tagid、scenic_id、tag_id。为什么用关联表而不是景点表直接存字符串因为这样按标签检索数据才能走索引也方便以后做推荐。评论表commentid、user_id、scenic_id、content、score、create_time。收藏/预订表favorite/bookingid、user_id、scenic_id、type收藏或者预订、status、travel_date、create_time。这几张表建好50%的业务就立住了。关键是外键逻辑要清晰用户和评论一对多景点和评论一对多用户和景点通过收藏表多对多。别嫌基础把关联关系理清写Java代码时才顺。2.4 关键SpringBoot配置要点在application.yml里有四个配置我一直提醒学生别抄错server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shaoxing_travel?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0第一serverTimezone不配查询时间数据大概率会报错或显示UTC时间偏差8小时第二useSSLfalse可以避免本地MySQL连接时因为SSL协议不一致报错第三MyBatis-Plus的逻辑删除要提前在实体上加TableLogic注解否则delete操作是物理删除后面想恢复数据就很麻烦。拦截器方面如果做了登录鉴权建议定义HandlerInterceptor在preHandle里检查请求头里的Token然后放行登录接口和静态资源路径。很多学生这一步省了直接用SpringSecurity结果被配置类折腾得痛不欲生。毕设用自定义拦截器反而更简单、更可控。2.5 旅游推荐这个“加分模块”怎么实现如果你的论文想比同组同学多一个亮点强烈建议做一个简单的“智能推荐模块”。不用上复杂的协同过滤算法一个朴素的“基于标签的召回排序”就足够答辩了用户点击或收藏某个景点后系统拿到该景点的全部标签。在景点表中找出包含同样标签的其他景点。按“共同标签数量浏览量”排序过滤掉用户已经看过的。返回Top N给前端。实现上一条SQL带两个JOIN就能完成SELECT s.*, COUNT(st.tag_id) AS common_tags FROM scenic s JOIN scenic_tag st ON s.id st.scenic_id WHERE s.deleted 0 AND st.tag_id IN (SELECT tag_id FROM scenic_tag WHERE scenic_id #{currentScenicId}) AND s.id ! #{currentScenicId} GROUP BY s.id ORDER BY common_tags DESC, s.view_count DESC LIMIT 6写完之后在论文里配合E-R图把这段逻辑讲清楚答辩就站稳了。推荐功能不需要多准确但要让人看懂你是“花了心思做业务挖掘”的。3. 实操过程与核心环节实现3.1 从创建工程到跑通第一个接口我按“最小可用闭环”的顺序来讲先别急着写页面先用API调试工具Apifox或Postman把后端所有接口跑通再接前端。顺序反了会让自己陷入“前端调不通后端、后端不知道错在哪”的两难。第一步打开IDEA用Spring Initializr创建工程Group填com.shaoxingArtifact填travel-system依赖选Spring Web、MySQL Driver、Lombok。创建完后再手动往pom.xml加MyBatis-Plus和Hutool工具库。第二步启动一次项目确认SpringBoot能正常启动并读取数据库连接。如果报Failed to configure a DataSource基本就是依赖没引全或yml配置没被识别。第三步写一个“根据ID查景点”的测试接口RestController RequestMapping(/api/scenic) public class ScenicController { Resource private ScenicService scenicService; GetMapping(/{id}) public Result getScenicById(PathVariable Long id) { Scenic scenic scenicService.getById(id); return Result.success(scenic); } }这里我用的是MyBatis-Plus封装好的IService接口。如果你引入Service接口并继承IServiceScenicServiceImpl继承ServiceImplScenicMapper, Scenic就自动获得getById、save、page这些方法代码量能少一半。3.2 景区搜索与分页查询的实现细节搜索和分页是最常被答辩老师提问的模块也是实际开发中很容易写乱的模块。我在验证码、参数校验都放到DTO层处理避免前端乱传参导致SQL异常public class QueryScenicDTO { private String keyword; private String area; private Long tagId; private Integer pageNum 1; private Integer pageSize 10; }Service层对应的查询逻辑public PageScenic queryScenicPage(QueryScenicDTO query) { LambdaQueryWrapperScenic wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(query.getKeyword())) { wrapper.and(w - w.like(Scenic::getName, query.getKeyword()) .or().like(Scenic::getDescription, query.getKeyword())); } if (StringUtils.hasText(query.getArea())) { wrapper.eq(Scenic::getArea, query.getArea()); } if (query.getTagId() ! null) { wrapper.inSql(Scenic::getId, SELECT scenic_id FROM scenic_tag WHERE tag_id query.getTagId()); } wrapper.orderByDesc(Scenic::getViewCount); return scenicMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper); }这里有个细节当keyword和area同时存在时wrapper.and(...)会把关键词匹配括起来避免和area产生“OR优先级”错乱。如果不加and()生成的SQL可能是WHERE name LIKE ? OR description LIKE ? AND area ?这就会把不属于该地区的景点查漏。这个小坑我见过不少学生踩进去。3.3 登录鉴权与JWT的使用方式前后端分离项目里登录鉴权我推荐用JWT 自定义拦截器比Session方案更契合“无状态接口”的定位也比SpringSecurity源码更简单。流程是这样的用户用用户名密码登录Service里校验数据库里的密码BCrypt加密存储校验成功后用jjwt库生成一个三部分的Tokenheader.payload.signaturepayload里放userId和role信息设定过期时间比如2小时。前端拿到Token后存进localStorage每次请求时在Authorization头里带上。后端拦截器里先放行/api/auth/login、/api/scenic/**这些公开接口然后校验剩余请求头里的Token是否合法、是否过期。校验通过就把userId放进ThreadLocal或HttpServletRequest的属性里方便后续接口取当前用户信息。这块如果时间充足还可以额外加一个“简单多角色权限控制”管理员接口比如修改景点、删除评论要求Token里的role为admin普通用户只允许操作自己的数据。3.4 前端Vue项目的页面串联前端部分以Vue3 Vite Element-Plus为例。路由至少要有/home首页展示轮播图、热门景点、按标签分组推荐。/scenic景点列表页支持关键词搜索、区县筛选。/scenic/:id景点详情含基本信息、评论列表、收藏按钮、推荐景点。/favorites我的收藏。/login与/register登录注册。这些页面基础但不要轻视。答辩老师一定会打开浏览器看页面。页面之间要能来回跳收藏按钮要有状态变化登录了才能收藏和评论没登录则弹出提示。这些交互如果都能丝滑实现印象分会很稳。我用Axios封装了一个request.js核心是请求拦截器里统一加Token响应拦截器里统一处理状态码401Token过期跳登录和500弹出错误提示。这样每一个业务请求都干干净净request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; });3.5 可运行的演示脚本与打包部署毕设做完之后还要给老师演示。我习惯提前写一份“演示脚本”从登录页面开始正常用户操作、管理员操作、异常场景未登录访问收藏页面三步走每一步对应到哪几个后端接口张嘴就能报出来。这样答辩时不慌张。部署部分如果学校要求线上演示最省事的方案是后端打成jar包放到一台能联网的服务器上执行nohup java -jar travel-system.jar log.out 21 前端打包成静态文件后用Nginx代理并配置反代到后端端口顺便解决跨域问题。注意Nginx里有一个必经的配置location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }这里反向代理配好之后前端访问的/api路径会原样透传给后端。如果proxy_pass末尾少了或多了斜杠路径会拼错一个排查半夜的经典问题。4. 常见问题与排查技巧实录“毕设最大的难题不是功能写不出来而是出了问题不知道去哪排查。”这句话我每次带学生都要讲一遍。下面这几个问题是我在实际指导里遇到频率最高的直接做成速查表供你参考。4.1 问题速查与解决对照表现象可能原因解决办法启动报Failed to configure a DataSource数据库连接信息不对或MyBatis-Plus依赖未引入检查application.yml的url/username/password检查pom.xml是否有spring-boot-starter-jdbc或MyBatis-Plus前端请求接口报CORS跨域错误后端未开启跨域或Nginx反向代理没配好后端配置WebMvcConfigurer的addCorsMappings或使用Nginx统一转发中文乱码数据库连接URL没带characterEncodingutf8或者页面没有设置UTF-8在MySQL连接串加characterEncodingutf8前端模板统一UTF-8查询结果带出已删除数据逻辑删除字段未生效在实体加TableLogic并检查deleted字段是否存在于查询条件修改景点信息后列表不刷新后端返回数据接口缓存或前端没有重新请求检查浏览器Network确认是否请求发送检查是否调用了页面刷新逻辑打包后静态资源404前端打包路径问题Vite工程需要在vite.config.js里设置base: ./否则部署到子路径时资源路径都不对4.2 最容易让答辩“社死”的三个细节第一数据库时间字段的时区问题。如果你用了LocalDateTime且数据库连接串没加serverTimezoneAsia/Shanghai而服务器又在其他时区页面就会显示“时间差8小时”。答辩前最好测试一下“用户评论顺序是否正常”这种一眼能看出的错会让老师怀疑你代码基本能力。第二管理员账号的权限绕过。我只做了自定义拦截器但自定义拦截器是拦不到“静态资源直接访问”的如果你把管理员页面也放到了静态资源目录下别人就可以直接输入URL绕过登录查看页面。所以我建议所有需要权限的后端接口都在后端拦截器层面控制而不是靠前端路由隐藏。第三接口偶尔报500查不到日志。刚学SpringBoot的学生最喜欢不写日志直接闷头改。实际排查流程永远应该是先看控制台有没有Exception堆栈有就按报错行去对应代码没有就用Postman复现请求再一步步加断点调试。在Service层的关键方法里加上log.info(参数{}, param)会帮你省非常多时间。4.3 论文和技术讲解中的高频听众问题答辩时老师很喜欢从系统架构和业务逻辑切入提问这里列几个经常被追问的点建议提前准备好答案“为什么选择SpringBoot而不是传统的SSM”自动装配、内嵌容器、生态整合这些词要会说“MyBatis-Plus相比MyBatis有什么优势”代码生成、条件构造器、逻辑删除、分页插件“如果并发量大了这个登录方案还能用吗”JWT适合分布式扩展、Redis存token做刷新能说出来就超过90%的同组“这个推荐算法为什么不用协同过滤”基于标签的推荐适合小规模冷启动协同过滤对行为数据要求高分析清楚就有理除了技术问题还要准备两句业务上“为什么这样做”的回答比如“绍兴特色标签是怎么划分的”“区县筛选对这些标签有没有依赖”。把这些逻辑理顺答辩现场不会冷场。4.4 两个月时间线的合理规划如果你现在还没动工我给你一个可执行的时间线按每天2—3小时算第1周完成需求分析、数据库设计、项目骨架、跑通登录注册。第2周完成景点模块的列表、搜索、详情、评论与收藏接口。第3周完成后台管理模块景点管理、标签管理、用户管理。第4周完成前端所有页面的基本渲染与前后端联调。第5周优化推荐逻辑、完善权限与异常处理、写接口文档。第6周整理论文初稿针对答辩模拟问题查漏补缺。别把论文堆到最后一天写。很多过来人的教训都是代码做得还行论文却因为没截图、没数据、没时序图最后只能熬夜硬编。我会在每天开发快结束时顺手截一张页面图、保存一份接口返回数据写论文时就知道多轻松。5. 可扩展方向与个人经验心得5.1 后续还可以怎么扩展如果一个毕设想加“高级感”可以在这些方向选一个适度扩展引入Redis做验证码缓存与热点景点访问量统计。引入Elasticsearch做全文搜索替换MySQL的LIKE查询并针对绍兴地名做分词。接入地图SDK按经纬度做景区位置展示和路线规划。把推荐模块升级成基于用户行为的简单协同过滤/基于用户标签的推荐。但一定切记“扩展”的目的是把一条链路讲深而不是给自己挖坑。比如你引入了Elasticsearch就要能说清楚数据是怎么同步过去的、为什么不用MySQL解决引入了Redis就要解释清楚缓存击穿和过期策略。任何一项技术被你引进来都要变成论文里能自圆其说的一章否则多余的功能只会增加风险。5.2 一点真实体会我亲眼旁观过很多毕设从“一手好牌”打到“勉强通过”技术栈选得很潮页面炫酷但一问到底层原理、数据如何关联、线上部署流程支支吾吾答不上来。相反老老实实把SpringBoot Vue这套基础架构做扎实、把业务闭环讲清楚的同学往往拿到的成绩比预期更高。做这套绍兴旅游系统让我最有收获的地方其实不是写了多少代码而是完整走了一遍“业务分析—表结构设计—接口设计—联调部署”的流程。这种把零散知识串成体系的感觉和你在教程里“敲完就跑”完全不一样。如果你也正在经历这个阶段别焦虑一点一点把项目啃下来你得到的会比题目本身多得多。
返回列表