这阵子好几个同学来问我,毕业设计选了“基于SpringBoot的文旅信息服务平台”这个题目,到底该怎么下手。这类题目在计算机毕业设计里算典型性很高的选题:业务域贴近真实场景,技术栈主流,做出来的东西演示效果好,论文也好展开。我前后带过不少类似项目,从最简单的景点列表CRUD,到带订单、支付、路线规划、评论推荐的完整系统都碰过,今天就以“智慧旅行综合服务系统”的角度,把整个项目的设计思路、核心实现和踩坑记录一次性捋清楚。先说结论:这是一个SpringBoot单体应用级别的项目,前端可以选Vue或Thymeleaf,后端基于SpringBoot 2.x或3.x,数据库用MySQL,缓存按需上Redis。它解决的核心问题,是把“景点、线路、酒店、攻略、订单、用户”这几条旅游业务主线串起来,让用户能查、能订、能评、能管理。适合计算机专业毕设,也适合想入门SpringBoot全栈开发的初学者拿来当练手项目。
1. 项目设计:先想清楚再写代码
1.1 为什么选SpringBoot而不是SSH或SSM
很多教材还在讲SSH(Struts2+Spring+Hibernate)或者SSM(Spring+SpringMVC+MyBatis),但说句实在话,现在出去找工作、做毕设、写小项目,SpringBoot已经是事实上的标准起点。它最核心的价值就四个字:自动装配。
SpringBoot把Spring家族里那些繁琐的XML配置全收进了起步依赖里,你引入spring-boot-starter-web,它就自动帮你配好SpringMVC和内置Tomcat;引入spring-boot-starter-data-jpa,它就自动帮你配好数据源和ORM。你不需要再写一堆bean.xml,不需要再纠结视图解析器怎么配,项目能跑起来的时间从一两天缩短到十分钟。对于毕设来说,这意味着你能把精力放在业务功能上,而不是耗在环境搭建上。
另外,SpringBoot内置Tomcat这点,对部署演示特别友好。以前SSM项目部署,要在服务器上单独装Tomcat,再把war包丢进webapps,中间出个什么版本不匹配的问题就够折腾半天。SpringBoot打出来的jar包直接java -jar就能跑,答辩的时候带台笔记本或者租个轻量服务器,一条命令搞定,不容易翻车。
1.2 模块划分:别一上来就建二十张表
文旅平台这种题目,很多同学拿到手的第一反应是:把景点、酒店、机票、火车票、美食、攻略全做进去,表一建就是二十多张。我劝你打住。毕设项目的核心是“完整闭环”,不是“功能大而全”。你做得再多,如果订单流程走不通、权限逻辑有漏洞,一样扣分;反过来,功能适量但每条链路都跑通,反而更好讲、更好答。
我建议核心模块控制在五个:用户模块、景点/线路模块、酒店模块、订单模块、评论/收藏模块。在此基础上再加一个后台管理模块,做菜单、角色、用户权限管理。这六个模块已经能完整覆盖“游客浏览-登录-下单-支付模拟-评论-后台管理”的完整业务闭环,论文的用例图、流程图、时序图也都有东西可画。
数据库设计上,我遇到过太多人把景点表和线路表混在一起,或者评论表跟订单表关联得乱七八糟。这里给一个清晰的表结构思路:
- 用户表(user):id、用户名、密码(加密存储)、手机号、头像、角色id
- 角色表(role):id、角色名、角色标识(ADMIN/USER)
- 菜单表(menu):id、菜单名、父id、路由地址、权限标识
- 角色菜单关联表(role_menu):做多对多
- 景点表(scenic):id、名称、简介、图片、所在城市、经度、纬度、门票价格、开放时间
- 线路表(route):id、标题、行程天数、出发城市、包含景点ids、价格、封面图
- 酒店表(hotel):id、名称、地址、星级、价格区间、图片、联系电话
- 订单表(orders):id、订单号、用户id、商品类型(景点/线路/酒店)、商品id、金额、状态、创建时间
- 评论表(comment):id、用户id、商品类型、商品id、评分、内容、创建时间
- 收藏表(favorite):id、用户id、商品类型、商品id、创建时间
这套表结构遵循了基本的范式设计,又保留了适当的冗余(比如订单里冗余商品类型和id),查询起来很方便,也不用为了性能做过度设计。记住,毕设阶段的表结构,清晰比花哨重要。
1.3 版本选择:SpringBoot 2.7.18是目前最稳妥的选择
热搜词里有个“springboot版本太高”被反复提起,这个问题我在实际带项目时碰到太多次,必须单独拿出来说。SpringBoot 3.x发布后,很多人新建项目直接选了最新版,结果发现一堆老教程的代码跑不起来,心态直接崩了。
核心原因在于SpringBoot 3.0有一个破坏性变更:底层Java EE规范从javax命名空间迁移到了jakarta命名空间。你之前用的JWT工具类、Druid连接池、PageHelper等很多库的旧版本,都依赖javax.servlet,在SpringBoot 3.x里直接找不到类,必须升级到兼容版本,而有些库的兼容版本本身还不太成熟。
所以,如果你是毕业设计,或者现在电脑上装的是JDK 8,我强烈建议直接用SpringBoot 2.7.18。这个版本是2.x系列的最后一个维护版本,修补了大量漏洞,同时又完全兼容JDK 8和javax命名空间,网上绝大多数教程、开源项目、答案都能直接对标。如果确实是新机器、新JDK 17以上,才建议考虑SpringBoot 3.x,并且所有依赖都要选支持jakarta的版本。
判断自己的JDK版本,终端输入java -version就能看到。如果显示“1.8.0_xxx”,就老老实实用SpringBoot 2.7.18,别折腾。
2. 核心业务模块怎么落地
2.1 登录鉴权:JWT配合拦截器,放开Swagger
登录鉴权是这类平台最核心的基础能力,也是答辩时老师最爱问“你这接口安全怎么做的”的地方。我推荐的方式是SpringBoot + JWT + 拦截器,这也是目前前后端分离项目的主流方案。
JWT(JSON Web Token)的思路,简单理解就是:用户登录成功后,后端生成一个加密的token字符串返回给前端,前端每次请求都在请求头里带上这个token,后端通过拦截器校验token是否合法、是否过期。它不像Session那样需要服务端保存状态,天然适合分布式和前后端分离。
SpringBoot里集成JWT,主要是加入jjwt依赖,然后写一个JwtUtil工具类,里面包含生成token和解析token两个核心方法。生成时把用户id和角色放进payload,过期时间设为24小时或者按需调整。拦截器方面,我推荐实现HandlerInterceptor接口,在preHandle方法里从请求头取token、校验、把用户信息放入ThreadLocal或Request作用域。
这里有个实操细节必须提醒:如果你配了Swagger,一定要把Swagger相关的路径放行,不然在线文档调试接口时没有token,会被自己的拦截器挡住,造成“文档打不开”的尴尬。放行方式很简单,在拦截器注册时添加excludePathPatterns,把/swagger-ui/, /v3/api-docs/, /doc.html这些都排除掉。
另外,拦截器和过滤器很多人分不清。过滤器是Servlet层面的,在SpringMVC入口之前执行,能拿到原始请求和响应;拦截器是SpringMVC层面的,在Handler执行前后执行,能拿到HandlerMethod,可以精确判断某个方法有没有某个注解。对于登录鉴权,拦截器足够;对于全局字符编码、CORS跨域这类,过滤器更合适。不要什么都往拦截器里塞,职责分清楚,后面排查问题会轻松很多。
2.2 后台权限:一套轻量RBAC就够用
很多文旅平台会给不同的后台角色分配不同的菜单权限,比如普通管理员只能管理景点和评论,超级管理员才能管理用户和角色。这就是热搜词里提到的“springboot菜单角色管理”。
实现方案建议用经典的RBAC模型,就是前面表结构里的五张表:用户表、角色表、菜单表、角色菜单关联表,再加上用户和角色的一对一或一对多关系。逻辑简单说就是:用户属于某个角色,角色拥有多个菜单权限,用户登录后查出自己角色对应的菜单列表,前端根据菜单列表动态渲染侧边栏,后端在接口上用小注解(比如自定义@RequirePermission)配合拦截器做二次校验。
注意一个常见误区:前端隐藏了菜单不代表接口安全。懂行的人直接调用接口地址照样能访问,所以权限控制的重心必须放在后端接口上,前端的菜单显示只是“体验优化”。我见过不少同学只在Vue路由里做了判断,后端所有管理接口都不设防,答辩时被老师一追问就露馅。
2.3 搜索推荐:HanLP分词提升搜索体验
文旅平台最容易被老师夸“有亮点”的功能,就是搜索和推荐。热搜词里出现了“hanlp分词在springboot”,说明不少人都动过这个心思。这里聊聊怎么落地。
如果你的搜索只是SQL里的LIKE %关键词%,那确实没什么技术含量。想做得像样一点,可以引入HanLP这个小而强的中文分词库。HanLP是一个开源的Java中文自然语言处理库,支持分词、词性标注、命名实体识别等。在SpringBoot项目里引入hanlp依赖,然后调用Segment的seg方法,就能把用户输入“北京故宫门票多少钱”切分成“北京/故宫/门票/多少/钱”这样的词序列。
拿切分后的词去匹配景点名称、标签、简介,比简单字符串匹配精准得多。比如用户搜“故宫开放时间”,单纯LIKE '故宫开放时间'其实匹配不到“故宫”这个景点名,但分词之后得到“故宫”和“开放时间”,就能命中。再配合一个简单的热度权重排序,搜索体验立刻上一个档次。
不过要提醒,HanLP默认用的是内置词典和模型,效果满足毕设没问题,但别指望它达到搜索引擎级的效果。优化方向可以是把用户搜索历史存进表里,统计高频词作为热搜推荐;或者根据搜索词和景点城市进行匹配,用户搜“杭州”,就把杭州的景点和酒店都推给他。这些都是加分项。
2.4 订单与事务:别让数据出现“对不上账”
订单模块是文旅平台的业务核心,也是最容易出错的地方。比如用户下单时,订单表插入了一条记录,但库存减扣失败,或者支付状态更新失败,就会导致“用户付了钱但票没买到”或者“票买到了但钱没扣”的严重问题。
这里就必须用到SpringBoot的事务机制。在Service层方法上加上@Transactional注解,Spring会在这个方法执行期间开启一个数据库事务,方法内所有数据库操作要么全部成功提交,要么全部失败回滚。这是最基础但也是最有效的保障。
我用一个具体场景说明:用户预订一张景点门票,代码逻辑是“创建订单记录 -> 扣减景点门票余票 -> 更新用户积分”。这三个操作必须放在同一个事务方法里。顺序上建议先创建订单,再扣库存,最后更新积分;每步都要检查影响行数,如果某个步骤返回0行(比如余票已经是0),直接抛出运行时异常,触发回滚。
这里重点提醒一个事务失效的经典场景:同类内部调用。在同一个类里,方法A调用方法B,B上面标了@Transactional,但A没有标,那么B的事务其实不会生效。原因是Spring的事务是通过AOP代理实现的,内部直接调用(this.method())绕过了代理,注解就失效了。解决办法是不管是A还是B,都从外部Service入口进入,或者自己注入自己的代理(比较绕,不推荐)。这个知识不仅面试考,实际操作中也是高频问题,后面专门有一节详细讲事务失效。
3. 关键技术细节与实战坑
3.1 配置文件:自己动手写规范
文旅平台涉及数据库、Redis、上传路径、支付模拟开关等一堆配置,全堆在一个application.yml里,到后期改起来很痛苦。我的经验是至少拆成三个文件:application.yml、application-dev.yml、application-prod.yml。
application.yml放公共配置和激活项,比如spring.profiles.active=dev;dev文件放本地数据库连接、日志级别DEBUG;prod文件放服务器数据库地址、日志级别INFO。如果想直接在配置文件中定义Map类型配置,YAML写法很简单:
gateway: routes: scenic: "景点服务" hotel: "酒店服务" route: "线路服务"然后在代码里用@ConfigurationProperties(prefix = "gateway")绑定,注意要有对应的getter/setter。这个方式适合做配置量不大的业务映射,但别把太复杂的业务规则塞进配置文件,维护成本会很高。
这里还要说一个困扰很多新手的问题:“idea中springboot项目的application.yml不提示怎么办”。在IDEA里,application.yml没有自动提示,通常是因为IDEA没有把它识别为Spring配置文件。解决办法:右键点击application.yml,选择“Add as Spring Boot Configuration File”,IDEA识别之后,里面的spring.datasource.url、server.port这些配置就能自动补全和跳转。出现这种问题的原因,一般是手动创建的yml文件还没被Spring插件识别,不是项目坏了。
3.2 自动装配:搞懂它,面试和答辩都加分
SpringBoot最核心的机制就是自动装配。热搜词里的“springboot自动装配原理”常年占据面试题榜单,答辩时老师也基本会问一句“SpringBoot为什么能自动配置”。
原理用大白话讲就是:SpringBoot项目启动时,会扫描META-INF/spring.factories或者org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里列出的所有自动配置类。每个自动配置类上都有@ConditionalOnClass、@ConditionalOnProperty等条件注解,意思是“只有在类路径下存在某个类时,或者配置了某个属性时,这个自动配置才生效”。比如你引入了spring-boot-starter-data-redis,RedisAutoConfiguration检测到类路径下有RedisTemplate相关的类,就自动创建Redis连接工厂和RedisTemplate Bean。
如果你想让自己的代码也具备“自动装配”能力,可以自定义一个starter:新建一个模块,写一个自动配置类,用@AutoConfiguration注解标注,然后在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里注册。之后其他SpringBoot项目引入这个模块,配置类就会自动生效。虽然毕设阶段不太会用到这个,但能说出这一套流程,答辩观感会完全不同。
3.3 文件上传下载与资源映射
旅游平台里有很多图片上传的场景:用户传头像、管理员传景点封面图、用户传游记图片。很多同学把图片直接存进数据库的BLOB字段里,这种方案我强烈不建议,数据库会变得庞大臃肿,查询性能下降明显,备份也慢。正确做法是把文件存到服务器的磁盘目录上,数据库只保存文件的访问路径。
SpringBoot处理文件上传,主要就是MultipartFile接口。一个完整的单文件上传接口,核心步骤是:校验文件非空、校验大小和类型、生成唯一文件名(比如用UUID加时间戳)、指定存储目录、调用transferTo方法落盘、把访问路径存入数据库。这里注意一个坑:如果直接用原始文件名保存,两个人传同名图片会发生覆盖;如果文件名包含特殊字符,还有可能引发路径穿越问题。所以务必用UUID生成文件名。
文件上传之后还有一个大坑,就是“前端访问不到图片”。你明明把图片存在了服务器的/data/upload目录,但浏览器访问http://ip:8080/upload/xxx.jpg返回404。原因是SpringBoot默认只处理classpath:/static/下的静态资源,外部磁盘目录并不在映射范围。解决办法有两个:一是配置资源映射,写一个WebMvcConfigurer,重写addResourceHandlers方法,把/upload/**映射到file:D:/data/upload/;二是在application.yml里配置spring.web.resources.static-locations,加上外部路径。我推荐第一种方式,更直观、更可控。
再说“springboot如何上传下载大文件”,这个热搜被搜得多,是因为默认情况下SpringMVC对上传文件大小有限制,默认单文件1MB,请求总大小10MB。超过限制会报MaxUploadSizeExceededException。调整方式是在配置文件里加:
spring: servlet: multipart: max-file-size: 100MB max-request-size: 500MB但记住一个现实问题:大文件上传如果只用简单POST传输,网络稍有波动就要重传。真要做好,得用分片上传(前端把文件切成若干切片,后端接收后合并)或者流式上传。毕设阶段建议在小文件上传上做到规范即可,如果时间充足,可以选一个切片上传作为亮点写进论文,不需要自己造轮子,直接在配置里调大限制做演示也够用。
3.4 WebSocket:给平台加一个实时通知
文旅平台可以加一个很实用的功能:用户下单成功后,系统实时推送一条“订单支付成功”的站内消息。这个场景用WebSocket做很合适。WebSocket是HTML5提供的全双工通信协议,一次握手建立连接后,服务端可以主动向客户端推送数据,不需要像HTTP那样反复轮询。
SpringBoot集成WebSocket,主流做法是用Spring的WebSocket模块,核心分三步:写一个Handler类继承TextWebSocketHandler或实现WebSocketHandler接口,管理连接会话;写一个WebSocketConfigurer配置类,注册Handler并指定访问路径,比如/ws/notification;前端用JavaScript的WebSocket对象连接。这里插一句,网上很多教程还停留在“用注解@ServerEndpoint”的方式,那是标准Java WebSocket的用法,不是Spring框架的WebSocket。两种都能用,但Spring推荐的是通过WebSocketHandler的方式,能够和Spring的依赖注入体系更好地整合。
实操中一个容易忽视的细节:WebSocket的握手请求默认不走SpringMVC的拦截器,所以JWT鉴权需要另做处理。常用方案是在建立连接时的URL上携带token(比如ws://localhost:8080/ws/notification?token=xxx),在WebSocket握手拦截器(HandshakeInterceptor)中校验token,校验不通过就拒绝握手。这样可以复用登录态,实现“只有登录用户才能接收消息”。
3.5 定时任务:用Quartz做订单超时处理
平台里另一个典型的业务需求是订单超时自动取消。用户下单后如果10分钟未支付,系统自动把订单状态改为“已取消”,并释放库存。这个用定时任务实现。
SpringBoot自带的@Scheduled注解简单好用,但最大的问题是调度逻辑写死在代码里,重启之后无法补跑错过的任务,做复杂的调度策略也很别扭。如果想让这个模块成为论文亮点,建议集成Quartz。Quartz是一个功能强大的开源任务调度框架,可以精确到秒、支持持久化、支持集群。
SpringBoot集成Quartz的标准做法是:写一个Job类实现Quartz的Job接口,在execute方法里写业务逻辑;再写一个配置类,创建JobDetail和Trigger Bean。为了演示“可配置化”,可以把Job的执行时间从配置文件读取,比如每10分钟执行一次。注意Job类里如果需要注入Service,一定要用Spring的依赖注入,而不能在execute内部直接用new的方式去获取,否则拿不到Spring管理的Bean。这是个经典坑,很多人写Quartz任务时碰到空指针,就在这里。
3.6 接口文档:Swagger让前后端不再扯皮
前后端分离的项目,接口文档是刚需。你不把接口路径、参数、返回结构写清楚,前端同学天天来问你“这个接口返回什么字段”,效率极低。SpringBoot集成Swagger(现在官方叫springdoc-openapi)就是为了解决这个问题。
SpringBoot 2.x时代,用的库是springfox或springdoc-openapi-ui。这里踩坑提醒:springfox的版本兼容性一直比较拉胯,新项目建议直接用springdoc-openapi,它对SpringBoot 2.x的支持很成熟。集成之后,启动项目,浏览器访问/swagger-ui.html就能看到在线接口文档,每个接口的入参、出参清清楚楚,甚至可以直接在线调用。
Swagger的注解(@Api、@ApiOperation、@ApiModelProperty)一定要写上去,不要嫌麻烦。答辩的时候,打开Swagger页面,挨个接口演示,评委的体验直接拉满。平时自己调试接口,也省去了手工拼参数的痛苦。
4. 前后端联调与部署全记录
4.1 CORS跨域:前后端分离第一个坎
选了Vue做前端,启动在8080端口,SpringBoot后端跑在8080端口,前端页面直接请求后端接口,浏览器会拦截,提示跨域错误。这是前后端分离后几乎必然遇到的第一道坎。
跨域的本质是浏览器的同源策略,协议、域名、端口任一不同,浏览器就会认为请求不安全。解决办法有几种:后端加CORS过滤器、前端配置Vue代理、反向代理(Nginx)。对于毕设,我推荐后端统一处理,写一个CorsFilter或者实现WebMvcConfigurer的addCorsMappings方法。
配置的时候,allowedOriginPatterns要用“”,别用“allowedOrigins”的“”,因为后者在新版本里不支持与allowCredentials(true)同时使用,会被报错。这是个地道的版本坑,你看网上很多旧代码能跑,照抄之后报错,就是版本差异导致的。
4.2 JDK 1.8项目打包进Docker Desktop
部署环节的高频热搜是“springboot jdk1.8打包到docker desktop”。这个需求确实普遍,因为现在很多人电脑上装的是Docker Desktop,想用容器化的方式运行自己的SpringBoot项目。
先说结论:JDK 8项目完全可以用Docker部署,关键是选对基础镜像。不要直接拉最新的openjdk镜像,很多已经不再提供JDK 8版本,你会拉不到正确的镜像。建议使用eclipse-temurin:8-jdk或者docker.io/library/openjdk:8-jdk-alpine。这两个都是轻量级且兼容性较好的选择。
Dockerfile可以这样写:
# 构建阶段 FROM maven:3.8.4-openjdk-8 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 运行阶段 FROM openjdk:8-jdk-alpine WORKDIR /app COPY --from=build /app/target/travel-platform.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]这里用了多阶段构建,第一阶段负责编译打包,第二阶段只保留运行环境和jar包,最终镜像会小很多。提醒一个小坑:Maven依赖下载阶段,如果网络不好或者私服没配置,很可能会卡住。建议在pom.xml里配置阿里云镜像仓库,构建速度会有质的提升。构建命令是docker build -t travel-platform:v1 .,运行是docker run -d -p 8080:8080 travel-platform:v1。
4.3 服务器部署:从jar包到自动重启
如果你有一个Linux服务器(比如阿里云或腾讯云的轻量应用服务器),部署过程可以极简:mvn clean package打包得到travel-platform.jar,上传到服务器,然后执行:
nohup java -jar travel-platform.jar --spring.profiles.active=prod > logs/app.log 2>&1 &nohup和&配合,让进程在退出SSH会话后继续运行。但直接这样用有一个问题:项目一崩溃或者服务器重启,进程并不会自动拉起。如果想让部署更成熟一点,可以写一个systemd服务。在/etc/systemd/system/travel.service里写:
[Unit] Description=Travel Platform After=network.target [Service] ExecStart=/usr/bin/java -jar /opt/travel/travel-platform.jar --spring.profiles.active=prod Restart=always RestartSec=10 [Install] WantedBy=multi-user.target然后执行systemctl enable travel启用开机自启。这样即使进程崩溃,systemd会在10秒内自动拉起,服务器重启也会自动启动项目。这个细节写到论文部署章节,是非常漂亮的加分项。
4.4 数据库连接:MySQL 8与驱动版本之间的事
旅游平台肯定用MySQL,但很多同学第一次用MySQL 8.x时,会碰到“连接成功但中文乱码”或者“报Public Key Retrieval is not allowed”的错。前者是字符集没配好,后者是MySQL 8的认证插件机制导致的连接问题。
连接MySQL 8,JDBC驱动必须用8.x版本,8.0.33目前比较稳定。连接URL里建议显式指定useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true,避免很多隐性问题。另外,如果发现项目启动后,数据库里的中文显示正常,但控制台日志乱码,多半是连接字符串里少了characterEncoding=utf8,加上一般就能解决。
还有热搜里的“springboot连接oracle数据库”,这是另一类需求。如果学校要求或项目确实需要,需要引入ojdbc8依赖(注意Oracle JDBC驱动不放在中央仓库,需要手动install到本地Maven仓库),同时注意Oracle的驱动类名是oracle.jdbc.OracleDriver,URL格式是jdbc:oracle:thin:@host:1521:orcl。这个坑主要在两个地方:一是驱动类名写错,二是依赖下载不下来。建议优先用本地的ojdbc8.jar执行mvn install:install-file命令导入。
5. 常见问题与排查技巧实录
5.1 事务失效场景速查表
项目报“数据不一致”问题,十有八九是事务没生效。我在前面已经提过同类内部调用的问题,这里把高频场景整理成一张表,方便排查:
| 失效场景 | 原因 | 解决办法 |
|---|---|---|
| 同类内部方法调用 | this.method()绕过了AOP代理 | 拆到不同Service,或从外部入口调用 |
| 方法不是public | Spring默认只对public方法做AOP增强 | 把方法改成public |
| 异常被catch吞掉 | 事务感知不到异常,自然不会回滚 | 不要吞异常,或手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() |
| 抛出的是checked异常 | Spring默认只对RuntimeException回滚 | @Transactional(rollbackFor = Exception.class) |
| 数据库表不支持事务 | MyISAM引擎不支持事务 | 改成InnoDB引擎 |
| SpringBoot没开启事务管理 | 单数据源下一般自动开启,但误配置可能关闭 | 检查@EnableTransactionManagement是否存在 |
这张表我建议打印出来贴在电脑旁边。你写订单模块、支付模拟模块的时候,对着这张表检查一遍,能把绝大多数事务相关的Bug提前消灭。
5.2 循环依赖:A依赖B,B又依赖A
SpringBoot项目在启动时报错“The dependencies of some of the beans in the application context form a cycle”,就说明出现了循环依赖。简单说,就是Bean A需要注入Bean B,而Bean B的创建又需要Bean A,两边死锁。
热搜词里的“springboot循环依赖”常年不落榜,说明遇到的人太多了。SpringBoot 2.6版本开始,默认禁止循环依赖,项目启动时直接报错,而2.6之前只是警告但能跑。很多人从旧项目升级或从网上下老代码时,就会碰到这个问题。
解决办法有几个层次:最推荐的做法是重新设计依赖关系,比如把A和B共用的逻辑抽到第三个Service里,打破循环;其次是使用@Lazy注解延迟注入,让一方延迟初始化;再次是改用Setter注入或字段注入,在某些场景下可以规避。但根本上讲,循环依赖是代码结构不清晰的信号,重构比绕过更有价值。
我见过一个具体的案例:某个同学把景点服务和订单服务互相调用,因为下单时要取景点信息,管理景点时要看订单数量,最后写成了循环依赖。解决方式很简单:在设置景点列表的时候,不直接调用订单Service,而是通过一个事件监听或者单独写一个统计查询方法,解耦之后代码清爽,事务边界也更清晰。
5.3 SpringBoot版本太高带来的连锁反应
热搜词第一条就是“springboot版本太高”,这个感受我特别能理解。很多人新建项目时选了3.x,然后照着2.x的教程配置,一路报错:javax.servlet不存在、springfox无法启动、joda-time找不到类、druid连接池初始化失败……
我在前面已经建议过用2.7.18,这里再说清楚一个判断逻辑。如果项目本身没有特别需求,比如非要原生支持虚拟线程、非要用Spring Boot 3才有的新特性,就坚决用2.7.x。如果你的指导老师指定了版本,那就按指定的来,但所有依赖都要去Maven仓库确认是否有配套版本。
如果已经用了SpringBoot 3.x,遇到javax相关的报错,先检查所有依赖里是否引用了旧API。JWT库建议用io.jsonwebtoken 0.12.x以上并改用jakarta命名空间;Swagger用springdoc 2.x;数据库驱动用对应新版本。一句话总结:版本升级不是换数字那么简单,是生态整体迁移,没足够时间别折腾。
5.4 其余几个高频小问题速览
写SpringBoot项目,我还想快速列几个你很可能碰到的问法:
第一个,application.yml不提示。除了前面说的“Add as Spring Boot Configuration File”,还要确认IDEA是否安装了Spring Boot插件,以及是否用了正确的项目结构。如果是Maven项目,右键pom.xml点击“Add as Maven Project”之后提示才会正常。
第二个,接口返回JSON却出现奇怪的字段或者循环引用。如果用了JPA,双向关联实体序列化时可能出现无限递归。解决办法是在关联字段上标注@JsonIgnoreProperties或者@JsonIgnore,或者改用DTO去接收接口返回数据。用MyBatis时相对少遇到,但如果有嵌套对象,建议也做好VO/DTO的设计,别直接把Entity返回给前端。
第三个,Redis连接不上。配置了Redis,但本地没启动Redis服务,或者Spring Data Redis版本与Redis服务器版本不兼容。排查顺序:先确认redis-server是否在运行,再确认host和port,再排查密码。本地演示可以把密码留空,但服务器部署必须设置强密码。
第四个,端口被占用。在IDEA里启动SpringBoot时提示“Port 8080 was already in use”。解决办法是换端口,或者找到占用进程并kill掉。Windows上可以用netstat -ano | findstr 8080查看端口占用进程PID,再taskkill /PID 1234 /F强杀。Linux上则是lsof -i:8080和kill -9。这个小问题看起来简单,但不提前知道,折腾半天也排查不出来。
项目做完之后的几点建议
每年带毕设,我都会跟学生强调几件做完项目之后要做的事。第一个是把项目目录结构梳理干净,删除无用代码和注释掉的垃圾代码,统一包名规范。很多同学的代码能跑,但controller里塞了业务逻辑、Service层写了几百行、命名随意,答辩时被老师随意点开一个类,印象分直接打折。
第二个是学会写README。在项目根目录写一个标准README,包含项目简介、技术栈、运行步骤、默认账号密码、核心模块说明。这不仅是给人看的,更是给自己看的。放几天再回来看,能靠README快速回忆整个项目上下文,对准备论文和答辩都有帮助。
第三个是准备一张系统架构图。不要求画得多专业,但要把用户、前端、Controller、Service、DAO、数据库之间的关系画清楚。老师问你“系统整体架构是怎样的”,你有了这张图,回答就会清晰、有体系。
第四个是考虑扩展方向。文旅平台这个题目做完,后续可以扩展的点其实很多:接入真实地图API实现景点定位和路线导航,引入推荐算法做个性化景点推荐,加一个简单的反爬虫和接口限流,或者把单机部署升级成Nginx负载均衡集群。如果觉得目前的系统不够复杂,把其中一个扩展做出来,项目的档次能提升不少。
最后一个提醒,以上所有方案和代码,都只是帮助你完成项目的参考。毕设的意义不只是交一个东西,而是在这个过程中,你亲手设计表结构、调试接口、解决异常、部署上线,这些能力才是真正能带到工作里的东西。哪怕项目做得不算豪华,只要每行代码都是自己写出来的、每个bug都是自己排查出来的,答辩时就有底气。