拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于SpringBoot的机票预订系统:Java毕设选题到答辩全流程拆解

基于SpringBoot的机票预订系统一个值得入手的Java毕设选题从选题到答辩全流程拆解每年毕业季总有人问我Java的毕设到底做什么题目才不烂大街、又能把学的东西串起来说实话类似“图书管理”“学生选课”“宿舍报修”这类题目不是不行而是业务太浅撑不起一篇像样的论文答辩时也容易被老师问穿。我带过不少学弟学妹做毕设如果要我推荐一个“性价比”最高的选题基于SpringBoot的机票预订系统绝对排前三。它听起来不复杂但里面该有的东西全都有——用户体系、后台管理、机票查询、下单购票、余票扣减、订单管理甚至还能延伸到并发控制、状态机、接口安全这些深度话题。这篇就把整个项目从选题到答辩的关键环节给你梳理一遍附上我在实操中踩过的坑和排查经验直接可以当参考手册用。这个系统适合谁如果你是Java基础还行、对SpringBoot有初步了解但没做过完整项目的应届生或者你是想快速补一个能写在简历上的项目经验这个选题都合适。它不需要分布式、不需要高并发中间件一套SpringBoot MySQL 前端页面就能完整跑通但业务逻辑又足够让你讲出“门道”。1. 选题拆解一个看似普通的毕设能练到什么真东西1.1 为什么是机票预订而不是图书管理或学生系统先说实话“机票预订系统”这个名字在毕设题目库里面不算新鲜每年都有人做。但正因为做的人多可参考的开源项目、文档、源码才足够多你上手的时候不会两眼一抹黑。更重要的是这个业务本身有“天然复杂度”——它不是一个单表的增删改查而是一个涉及多表联动、状态变化、数据一致性的综合场景。拿图书管理对比一下图书管理核心就是书、借阅记录、读者三个表借书还书只要判断一下库存和借阅状态逻辑上没有任何“并发竞争”的概念。而机票预订不一样一趟航班只有那么多座位两个人同时买最后一张票系统必须保证只卖出去一张。这就引出了“并发扣减余票”的问题而这个话题在面试和答辩里都非常能打。再说真实感。机票预订系统的表结构有用户、航班、订单、乘机人订单关联用户和航班一个订单可能对应多个乘机人你给家人一起买票的场景这种“一对多”“多对一”的关联关系比图书管理的“一对一借阅”复杂得多也更贴近真实电商系统的模型。做一遍这个项目你对“关系型数据库为什么这样设计”的理解会深一个层次。1.2 这个项目覆盖的完整学习地图我帮大家把这个项目能练到的知识点列了一遍你可以对照检查自己缺什么SpringBoot核心自动配置原理、starter机制、配置文件多环境切换application-dev.yml / application-prod.yml持久层框架MyBatis / MyBatis-Plus的使用Mapper接口编写XML映射文件条件构造器数据库设计ER模型设计、三范式权衡、字段类型选择、索引设计航班查询条件字段比如起降城市、日期业务逻辑层事务管理、业务校验、状态机流转Web层RESTful API设计、参数校验JSR 303、统一返回结果封装、统一异常处理前端整合Vue / Layui / Thymeleaf任选一种理解前后端交互方式安全与权限登录验证、管理员与普通用户角色区分可以用JWT或Session项目工程化Maven依赖管理、项目分层结构、代码规范这些技能点随便挑两个出来写进论文的“关键技术”章节再配合实际代码截图答辩的时候老师问什么你都有东西可讲。2. 功能设计与技术栈先画好图纸再动工2.1 角色权限与功能清单做项目之前先别急着写代码。我见过太多人上来就建表结果表建到一半发现功能对不上又回头改表结构一个周末就这么耗没了。正确做法是先把功能清单和角色想清楚。这个系统我建议分两个角色普通用户前台注册、登录航班查询按出发城市、到达城市、出发日期筛选查看航班详情余票、价格、起降时间在线订票选择航班、填写乘机人信息、生成订单我的订单查看订单列表、订单详情、取消订单个人信息管理管理员后台登录和管理员账号单独认证航班管理新增航班、修改航班信息、停用航班订单管理查看所有订单、退款处理可选用户管理查看用户列表、禁用/启用账号数据统计可选加分项每日订单量、热门航线排名可以用简单的SQL统计 ECharts展示功能不用贪多能把上面这些做到“完整闭环”已经超过六成毕设的水平了。所谓闭环就是用户从注册登录开始到查询航班、下单、查看订单、取消订单整个流程都能走通且每一步之间数据正确联动。2.2 SpringBoot MySQL MyBatis-Plus 选型背后的理由技术栈推荐一套最稳妥的组合也是这个项目最常见的配置JDK 1.8兼容性最好Spring Boot 2.x 全家桶都支持工具链最成熟。不推荐一上来就用 JDK 17 Spring Boot 3.x不是不行而是你搜到的大多数中文资料、视频教程都是基于Spring Boot 2.x的遇到问题能搜到答案比“用新版”重要得多。Spring Boot 2.7.x2.X的最后一个稳定大版本既保留了传统习惯又修复了很多已知问题。网上有一类热词叫“springboot版本太高”说的就是有人直接上3.x结果MyBatis-Plus老版本不兼容、javax改成jakarta、还需要额外处理一堆东西纯粹给自己找麻烦。MySQL 5.7 或 8.0建议直接用8.0但要注意驱动和连接串配置后面第五章我会细说。生产环境基本都8.0了趁毕设提前用上没坏处。MyBatis-Plus 3.5.x这个必须推荐。它把单表CRUD的Mapper方法全封装好了BaseMapper自带selectById、selectList、insert这些你只需要写业务相关的复杂SQL。毕设的核心理应放在业务逻辑上而不是浪费时间手写一整套增删改查。再说了现在企业里用MyBatis-Plus的比例也不低不算“花架子”。前端如果没啥前端基础直接用Layui Thymeleaf服务端渲染学习成本最低。如果还想顺手补点前端技能用Vue 2 Element UI做前后端分离配Swagger接口文档简历上更好看。其他Lombok省掉getter/setter、Druid连接池自带监控页面、knife4j接口文档增强版、Hutool工具类库生成订单号特别好用。2.3 项目目录结构与分层规范分层这个东西看起来是“规范问题”其实直接决定你后面好不好调试、答辩好不好讲。我推荐的标准结构是com.example.flight ├── controller // 控制层接收请求、参数校验、返回结果 ├── service // 业务层核心逻辑 │ └── impl // 业务实现 ├── mapper // 持久层Mapper接口 ├── entity // 数据库实体映射 ├── dto // 数据传输对象接收前端参数 ├── vo // 视图对象返回给前端的数据 ├── config // 配置类CORS、拦截器、全局异常 ├── common // 通用类Result封装、枚举、常量 └── utils // 工具类JWT、日期处理等很多初学者喜欢把业务逻辑直接写在Controller里图省事。我强烈不建议这么做。Controller就做三件事收参数、调Service、返回结果。业务判断比如“余票不足不能下单”必须放在Service层。这样做的理由很实际第一一个接口可能要复用在多处比如前台查询和后台查询共用同一个Service方法第二事务注解Transactional必须加在Service方法上才生效这个我后面会展开讲第三答辩时老师问“你的分层是怎么设计的”你至少有一套能说清楚的结构。3. 数据库与核心业务机票预订最关键的几个坎3.1 表结构设计从用户到订单的完整链路这个项目的表我建议至少设计四张核心表外加一张登录时需要的角色字段或者独立角色表。个人推荐把角色直接做成字段毕设规模不需要单独一张表别过度设计。用户表userid主键自增username唯一索引passwordMD5或BCrypt加密存储real_name、phone、email联系信息role区分普通用户/管理员可以用0和1status是否禁用create_time航班表flightid、flight_no航班号比如CA1835airline航空公司departure_city、arrival_city起降城市注意存城市名就行不需要关联城市表departure_time、arrival_time起降时间用datetime类型price经济舱价格seat_count总座位数remain_seats余票数status航班状态正常/停用这里有个常见错误有人会把price分成多个舱位价格头等舱、经济舱然后字段越加越多。毕设阶段我建议就一张舱位表或者直接在航班表里放一个price字段真要扩展的时候再说。功能完整性和复杂度之间要有一个平衡点别把自己绕进去。订单表orderid、order_no订单号唯一建议用时间戳随机数生成user_id外键关联用户flight_id外键关联航班passenger_count乘机人数total_amount订单总金额status订单状态用int类型0待支付、1已支付、2已出票、3已取消、4已退款create_time、pay_time、cancel_time乘机人表passengerid、order_id订单号、name、id_card身份证号、phone如果需要复用常用乘机人可以再加user_id做关联但毕设阶段订单内保存乘机人信息就够了别贪多。为什么要订单和乘机人分开两张表因为你一个订单可以买多张票如果把乘机人直接塞在订单表一个字段里那后续你要查“某个身份证买了哪些票”就会非常难受。分表设计更符合实际业务答辩时也能解释清楚设计思路。3.2 航班查询与余票扣减并发下的关键实现机票预订系统最核心的一个业务逻辑就是“订票时扣减余票”。这里有一个教科书写烂了但在项目里必须亲手处理的问题并发超卖。什么叫超卖假设航班还剩最后1张票两个用户同时提交订单。如果代码的逻辑是// 错误的写法示范 Flight flight flightMapper.selectById(flightId); if (flight.getRemainSeats() 0) { // 生成订单 flight.setRemainSeats(flight.getRemainSeats() - 1); flightMapper.updateById(flight); }这两个请求都读到了remainSeats为1都判断大于0都去扣减结果就变成2个订单各买了一张“不存在”的票。这就是经典的并发问题。解决办法有几种我直接给你一个最实用的方案——乐观锁思想 条件更新SQLUPDATE flight SET remain_seats remain_seats - 1 WHERE id #{flightId} AND remain_seats 0配合MyBatis-Plus在Service里这样写Transactional public void createOrder(CreateOrderDTO dto) { // 1. 做订单相关的插入操作 // 2. 扣减余票用条件更新保证只扣成功一次 int rows flightMapper.updateRemainSeats(dto.getFlightId()); if (rows 0) { throw new BusinessException(余票不足下单失败); } // 3. 后续操作 }这里的关键是update语句的“remain_seats 0”这个条件由数据库层面保证原子性MySQL在执行单条UPDATE时是加锁的两个并发事务不可能同时把余票从1扣到0。受影响行数rows为0就意味着当前票已经被别人抢走了。这种方法不需要分布式锁、不需要悲观锁SELECT FOR UPDATE代码简单面试还能讲出“乐观锁”的口诀很划算。3.3 订单状态流转与支付模拟订单状态这个点是答辩高频区一定要提前想清楚。我建议订单状态设计成4到5个状态值含义触发动作0待支付用户提交订单1已支付用户模拟支付2已出票支付后自动出票简化处理3已取消用户取消或超时未支付4已退款管理员操作退款毕设里支付功能怎么处理我的建议是模拟支付不要真接支付宝/微信。就是订单生成后页面上有个“去支付”按钮点击后调一个本地支付接口把订单从“待支付”变成“已支付”同时把状态改成“已出票”。这个方案有几个好处不涉及第三方平台的申请流程也不需要在论文里花大量篇幅讲支付回调这些和毕设主线无关的东西。但要注意一点用户取消订单时一定要把票“还回去”也就是remain_seats要加回来。很多人的项目做到这步容易漏导致出现“取消订单后余票不减反增”的假象。另外订票和扣余票必须放到同一个事务里一旦某一步失败整个操作要回滚不能让订单生成了但余票没扣或者余票扣了但订单没生成。关于事务这里要特别强调一个新手最容易踩的坑。Spring的事务是基于AOP代理实现的Transactional它的生效条件是方法必须通过Spring容器代理调用且不能被同类中的其他方法绕过。什么意思我见过有人这样写Service public class OrderServiceImpl { Transactional public void createOrder(...) { // 业务代码 } public void submitOrder(...) { // 前面一些逻辑 this.createOrder(...); // 同类内部调用事务失效 } }同类内部调用this.createOrder()时Transactional不生效因为this调用没有经过Spring的代理对象。解决办法很简单把createOrder放到另一个Service里注入调用或者干脆在submitOrder这个入口方法上直接加事务注解。这个问题我在实际带项目的时候几乎每个学员都遇到过提前告诉你能省一整天的调试时间。4. 实操过程从环境搭建到联调跑通4.1 环境准备与项目初始化环境这部分我直接给一份“不会出错”的清单照着做就行JDK 1.8安装后记得配置JAVA_HOME环境变量和PATH。命令行里输入java -version能看到版本号才算装好。Maven 3.6配置本地仓库路径和阿里云镜像不然拉依赖能拉到你怀疑人生。IDEA装Lombok插件新版IDEA自带确认Enable annotation processing勾上。MySQL 8.0安装时注意选utf8mb4字符集密码记好后面连接串要用。这一步很多人卡在“安装MySQL”和“配置MySQL”上核心问题不是安装本身而是忘记设置字符集或者root密码策略太严格导致后续连不上建议安装时直接用简单密码反正本地开发环境。Spring Boot项目初始化最简单的方式是去Spring Initializrstart.spring.io生成一个基础工程Spring Boot 2.7.x版本依赖先选Spring Web、MyBatis框架、MySQL Driver、Lombok。注意在Initializr里直接搜MyBatis是搜不到的因为MyBatis不是Spring官方starter你需要到pom.xml里手动加mybatis-plus-boot-starter依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency然后配置application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/flight?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword type: com.alibaba.druid.pool.DruidDataSource mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.flight.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里有两个配置细节必须盯住都是实打实加过班的教训driver-class-name要写成com.mysql.cj.jdbc.Driver带cj。老资料里写的com.mysql.jdbc.Driver是MySQL 5时代的东西8.0驱动早就改了。有人启动报错“Loading class com.mysql.jdbc.Driver. This is deprecated”就是这个原因。serverTimezoneAsia/Shanghai一定要加。MySQL 8.0默认时区配置可能导致你的日期时间字段比实际差8个小时排查起来极其痛苦直接在连接串里写死最省心。4.2 登录鉴权与航班管理的实现要点登录这块毕设级别我推荐用JWT它虽然比Session多几行代码但好处是前后端分离也能用而且答辩时“无状态认证”这个概念比“Session存一下”高级得多。实现步骤不复杂写一个JwtUtil工具类提供生成token和解析token的方法登录成功时将用户ID和角色放进token返回给前端写一个拦截器或过滤器拦截需要登录的接口从请求头中取出token解析并校验管理员接口额外校验角色这里容易被忽略的一点是密码不能明文存数据库。最少用MD5加盐推荐用BCryptSpring Security里自带或者用Hutool的BCrypt工具类。毕设虽然不会真的被攻击但论文里写“密码加密存储”是加分项老师也很爱问“你密码是怎么处理的”这个问题。航班管理是后台的核心功能。管理员添加航班时前端页面提交航班号、起降城市、时间、价格、座位数后端Service要做参数校验起飞时间不能晚于到达时间、价格必须大于0、座位数必须是正整数然后再insert。查询航班列表建议加分页MyBatis-Plus自带分页插件配置一个MybatisPlusInterceptor就搞定Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }4.3 联调、调试与数据准备系统写完第一遍接下来就是联调阶段。这里我建议按这个顺序自测别东一榔头西一棒子先测基础链路用户注册 → 登录 → 拿到token。把这一步跑通说明数据库连接没问题前后端交互没问题。再测核心主流程管理员登录 → 添加航班 → 前台用户登录 → 搜索航班 → 下单 → 支付 → 查看订单。每一步都确认数据变了没有尤其看订单表和航班表的余票字段。最后测异常场景余票为0时下单、重复点击支付按钮、取消已支付订单、非法参数请求负数价格、超长字符串等。调试工具我强烈推荐IDEA自带的Debug模式在Service层和Mapper层打断点看变量值。很多“订单状态不对”的问题断点一看就明白是哪个环节的值传错了别靠System.out.println一行行打太原始了效率太低。数据准备也是个大头。你总不能一条航班数据都没有就演示吧。推荐写一个data.sql初始化脚本或者干脆在项目启动后手动往库里插准备至少10条真实感强的航班数据比如从北京到上海、广州、成都、杭州这些热门航线时间分布在当天不同时段。这样演示搜索“北京到上海”的时候能出来多个航班效果完全不同。5. 常见问题与排错实录5.1 MySQL连接不上的几个典型原因“数据库连接失败”绝对是我见过最多的启动报错。整理一下最常见的三种情况你可以直接对着排查情况一驱动版本冲突。项目里同时存在MySQL 5的驱动和8的驱动或者driver-class-name写错。解决办法确认pom中只有一个mysql-connector-java依赖8.x版本把过时的驱动依赖删除并把连接属性改成带cj的驱动。情况二连接串里的时区或SSL问题。MySQL 8的默认SSL和时区策略跟旧版不一致报错信息里如果出现“Communications link failure”或者“Public Key Retrieval is not allowed”直接检查连接串有没有useSSLfalse和allowPublicKeyRetrievaltrue。加完之后大部分问题都能解决。情况三防火墙或端口没起。netstat -ano看一下3306端口有没有监听用Navicat测试一下能不能连通。确认MySQL服务有没有启动——Windows服务里MySQL的启动类型如果设成了手动重启电脑之后它就静悄悄停了你启动项目当然连不上。5.2 日期时间格式的“隐形坑”这个坑非常隐蔽而且出现频率极高。现象是前端传的日期字符串和后端接收的Date类型格式不一致或者页面显示的时间比数据库里存的时间少了8小时。典型的错误表现是你用了一个String类型接收datetime字段存进数据库的格式和MySQL要求的yyyy-MM-dd HH:mm:ss对不上直接报Data truncation错误。解决的问题有两条路。第一接收前端参数时用DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss)注解指定格式第二后端返回JSON给前端时用JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解指定输出格式timezone必须写不写的话Jackson默认按UTC序列化就会出现“时间少了8小时”。这个两个注解配合同一张表里的时间字段一起使用稳定性最高。5.3 事务与并发订单重复、超卖怎么排查这是整个项目里“含金量”最高的问题排查环节也是答辩的时候老师最喜欢深挖的地方。分享一个我实际排查过的场景有学弟做出来下单功能测试时快速连点两次“提交订单”结果同一个航班同一个用户生成了两个订单余票扣了两次。这个问题的排查思路是这样的先检查下单接口有没有做“重复提交校验”最简单的方式是前端提交后按钮置灰后端层面可以用订单号幂等也就是前端生成一个流水号后端判断这个流水号是否已经处理过。如果没做幂等那就要看事务和并发控制是否正确。再回到超卖问题。我在第三章给了条件更新的方案这里补充一个排查技巧如果你的代码已经用了remain_seats 0条件更新但测试还是出现了超卖请重点检查两件事——第一这个UPDATE有没有真的在你Service的事务方法里执行还是你另外单独写了个普通方法第二你是不是用select先查了余票再在Java代码里判断大于0才执行update如果是那依然存在并发窗口。正解就是直接执行那条带条件的UPDATE用受影响行数判断成功与否。这个排查要点记下来等答辩时被问到“怎么防止多卖一张票”一讲一个准。5.4 问题速查表现象主要原因快速处理启动报ClassNotFoundException: Driver依赖缺失或驱动坐标错误检查pom中mysql依赖页面中文乱码数据库表字符集不是utf8mb4ALTER TABLE CONVERT TO CHARACTER SET utf8mb4端口被占用启动失败8080被其他进程占用换端口或找到占用进程杀掉前端请求跨域报错未配置CORS添加WebMvcConfigurer设置CorsFilter查询条件模糊匹配无效原生态SQL没用like要么写XML用concat拼接支付后订单还是待支付前端没调用支付接口检查接口路径和状态更新的service逻辑管理员登录永远失败密码加密方式不一致确认存库用的是同一加密算法MyBatis-Plus更新不生效实体对象没有主键值给updateById传带id的实体6. 答辩准备与后续扩展思路6.1 答辩时的高频问题答辩不是看你代码多炫而是看你有没有真正理解自己做的东西。我总结了老师对这个项目最爱问的几个问题提前想好答案现场就不会慌“你的订单号和数据库主键id有什么区别”这个很好回答id是数据库内部自增主键用来做表关联订单号是业务上给用户看的唯一编号比如包含日期和随机数需要对外展示还要保证唯一性。生成订单号用Hutool的IdUtil或者System.currentTimeMillis()加随机数都可以。“多个用户同时买最后一张票怎么处理”这个问题直接把你第三章讲的“条件更新UPDATE 受影响行数判断”说清楚。如果还能补一句“如果要扩展成高并发考虑Redis预扣库存或分布式锁”老师已经觉得你超出预期了。“你的事务加在哪一层为什么”回答加在Service层的业务方法上因为一个业务操作往往对应多个数据库操作事务要包住整个业务操作。这个问题的隐藏考点是“事务失效的场景”你可以主动说一下同类内部调用导致的事务失效以及正确写法加分效果很明显。“为什么选MyBatis-Plus而不直接写JDBC”这个问题你要答出“开发效率”和“SQL控制”的平衡。MyBatis-Plus帮你解决单表CRUD复杂查询自己写SQL既高效又能精确控制SQL执行。6.2 让项目加分的扩展方向如果你的时间比预期充裕或者想把这个项目做成简历上的正式项目我推荐三个性价比高的扩展方向第一个是引入Redis缓存热门航线查询结果。航班查询是典型的读多写少场景把高频查询条件比如“北京到上海当天的航班”缓存到Redis设置5分钟过期可以明显提升查询响应速度。这个扩展能讲出“缓存穿透、缓存雪崩、缓存一致性”这些面试高频词非常划算。第二个是增加简单数据统计。管理员后台做一个仪表盘统计每日订单数量、销售金额Top航线、用户增长趋势用SQL的GROUP BY加日期函数就能实现前端用ECharts画几张图表。这个方向尤其受导师欢迎因为它让项目看起来有“数据分析”的味道论文里还好截图。第三个是部署到云服务器。买个最低配的云服务器学生机很便宜把SpringBoot打jar包部署上去MySQL也迁过去再配一个域名或IP地址直接访问。简历上写“项目已上线可在线访问”比任何项目描述都有说服力。不过这块工作量主要在运维配置上如果时间紧先用本机演示也完全够。关于部署这里多提醒一句打jar包之前记得把application.yml里的数据库密码、端口、文件路径等配置改成“通过环境变量注入”的模式用${DB_PASSWORD:默认值}这种写法。这样以后不管部署到哪台机器改环境变量就行不用改代码重新打包。这是企业开发里很基本的习惯但很多毕设项目没做毕业设计评分的时候评审老师一问“你配置文件里的密码怎么是明文写死的”一下就露怯了。最后再分享一个小技巧整个项目做完以后我建议你花一个下午把“测试数据”整个走一遍录屏从注册登录、添加航班、下单支付、取消订单到后台查看录成一段3分钟左右的操作演示视频。这个视频一是答辩时可以直接放给老师看比现场手忙脚乱操作演示稳得多二是发在简历上或者GitHub项目说明里别人点进去看到实物运行效果好感度完全不一样。我当时的经验是把README写成“项目简介 技术栈 功能清单 界面截图 启动步骤”导师看了都多给了两分印象分。机票预订这个选题胜在业务真实、技术覆盖面广、扩展性强认认真真做一个你对SpringBoot的理解绝对会上一个台阶。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门