1. 选题需求拆解:三个标题其实指向同一套系统
1.1 从标题关键词反推系统边界
先说你拿到的这类题目。仔细看"云上航空"、"云端飞航"、"航旅随心"这三个词,剥掉包装的外壳,底子完全一样:一个基于 SpringBoot 的航旅APP及配套管理后台。这类毕设标题在学校里非常常见,或者说很多老师出题就是给一个业务场景,再配上"基于SpringBoot的XX平台",剩下的系统边界要靠你自己去定义。
我辅导过不少做这类题目的同学,发现最常犯的错不是代码写不出来,而是在动手前没有把"系统到底要做什么"想清楚,结果做到一半发现功能太多,毕业设计根本收不了尾。拿到这个题,第一件事是拆出核心业务实体。航旅平台绕不开四样东西:用户、航班、舱位(库存)、订单。围绕这四个实体展开的,就是用户注册登录、航班查询、航班预订、订单支付、行程管理这几条线。APP端负责给乘客用,管理端负责维护航班和舱位数据,二者通过接口联动。
有个容易忽略的点:APP在毕设里的形态。你可以做 Android 原生(Java/Kotlin)、微信小程序,也可以做 H5 套壳,或者干脆用 Vue 写移动端网页然后通过 WebView 打包成 APP。学校验收时候只看"是不是一个能跑的移动端应用",所以完全没必要在客户端上追求复杂技术,把精力留给后端业务逻辑才是稳妥路线。
1.2 用户端与管理端的功能清单与优先级划分
把功能范围控制住,是毕设活下去的前提。我建议按下面这个表格来规划模块:
| 端 | 模块 | 具体功能 | 优先级 | 复杂度 |
|---|---|---|---|---|
| 用户端APP | 账号体系 | 注册、登录、修改密码、个人信息 | P0 | 中 |
| 用户端APP | 航班查询 | 按起降城市、日期查询航班列表,支持舱位筛选 | P0 | 中 |
| 用户端APP | 在线预订 | 选择航班舱位、填写乘机人、提交订单 | P0 | 高 |
| 用户端APP | 订单管理 | 待支付订单、查看订单详情、模拟支付、取消订单 | P0 | 高 |
| 用户端APP | 行程管理 | 按时间线展示出行计划、倒计时、历史行程 | P1 | 中 |
| 用户端APP | 消息中心 | 航班变动通知、订单状态通知 | P1 | 低 |
| 管理后台 | 航班管理 | 航班录入、班期规则、起降时间维护 | P0 | 中 |
| 管理后台 | 舱位管理 | 各舱位等级定价、余票库存调整 | P0 | 中 |
| 管理后台 | 订单管理 | 订单查询、改签/退票审核、状态干预 | P1 | 中 |
| 管理后台 | 数据统计 | 订单量、销售额简单图表 | P2 | 中 |
P0 是核心链路,缺一个系统就跑不通;P1 是亮点模块,用来丰富功能描述;P2 如果时间不够可以砍掉,或者只做最简单的统计接口,前端用柱状图糊一下。说实话,评委老师更在意核心链路能不能讲清楚,而不是你做了多少个花哨功能。
1.3 为什么 SpringBoot 是这个题目的最优解
很多同学会纠结要不要用 Spring Cloud、要不要上微服务。我的回答很直接:毕设里面用 SpringBoot 单应用就够了,微服务属于给自己挖坑。SpringBoot 的核心价值在于两点:起步依赖和自动装配。起步依赖让你不用再为 Spring 和第三方库的版本兼容头疼,引入spring-boot-starter-web就自带 Tomcat 和 Spring MVC;自动装配则通过@EnableAutoConfiguration帮你在引入依赖后自动配置好相应的 Bean,比如引入 Redis 依赖后,RedisTemplate就能直接注入使用。
这种"约定优于配置"的思路完美契合毕设场景——你的核心目标是快速实现业务功能,而不是花三周调各种 XML 配置。相比十几年前 SSH(Struts+Spring+Hibernate)时代那种面对一坨配置文件无从下手的状态,SpringBoot 几乎把搭建项目的门槛降到了零。而且答辩的时候,"我使用 SpringBoot 简化了项目初始化流程,通过自动装配减少了大量样板配置"这句话本身就是个加分点,老师爱听。
2. 技术栈选型与数据库设计:先把地基打稳
2.1 一套不容易翻车的技术组合
直接给结论,这套组合我用过很多次,稳定性很高:
| 层级 | 选型 | 版本建议 | 备注 |
|---|---|---|---|
| 后端框架 | SpringBoot | 2.7.18 | 不要用 3.x,见下方说明 |
| ORM | MyBatis-Plus | 3.5.x | 内置分页插件、条件构造器 |
| 数据库 | MySQL | 8.0 | 5.7 也行,但 8.0 更省心 |
| 缓存 | Redis | 6.x/7.x | 做航班查询缓存、Token 存储 |
| 鉴权 | JWT | jjwt 0.9.x | 无状态,适合 APP 场景 |
| 接口文档 | knife4j | 4.3.0 | 方便自测和答辩演示 |
| 前端APP | Android 原生 或 Vue3+H5 | — | 按个人熟悉程度选 |
| 管理后台 | Vue3 + Element Plus | — | 或者直接用 Thymeleaf |
这里特别想提一句 SpringBoot 版本的问题。很多同学一上手就去创建最新版项目,结果 SpringBoot 3.x 要求 JDK 17+,而部分同学的笔记本上装的是 JDK 8;还有 MyBatis-Plus、druid 这些常用库对 SpringBoot 3 的兼容也各有各的坑。热搜词里"springboot版本太高"这个搜索词,我猜就有一堆人踩了。稳一点的做法是直接用SpringBoot 2.7.18,它是 2.x 系列的终点版本,稳定、教程多、几乎不报错,配合 JDK 8 完美运行,答辩时完全够用。
2.2 四张核心表的设计思路与建表语句
数据库设计是整个项目的地基,我的建议是不要照搬网上那种几十张表的完整设计,按业务需要来。核心四张表:用户表、航班表、舱位表、订单表。
用户表不多说,就是id, username, password, phone, real_name, id_card, create_time。密码记得用 MD5 加盐或者 BCrypt 加密,别明文存储,这个细节答辩时经常被问到。
航班表是重点,它和真实的航班排期还不一样。真实系统里航班有"班期"概念(比如每天一班、每周一三五飞),但毕设可以简化成"每个日期一条航班记录",也就是flight表里存的是具体某一天某一班飞机:
CREATE TABLE flight ( id BIGINT AUTO_INCREMENT PRIMARY KEY, flight_no VARCHAR(10) NOT NULL COMMENT '航班号,如 CA1234', airline VARCHAR(20) NOT NULL COMMENT '航空公司', depart_city VARCHAR(20) NOT NULL COMMENT '出发城市', arrive_city VARCHAR(20) NOT NULL COMMENT '到达城市', depart_airport VARCHAR(50) NOT NULL COMMENT '出发机场', arrive_airport VARCHAR(50) NOT NULL COMMENT '到达机场', depart_time DATETIME NOT NULL COMMENT '起飞时间', arrive_time DATETIME NOT NULL COMMENT '到达时间', flight_date DATE NOT NULL COMMENT '飞行日期', status TINYINT DEFAULT 1 COMMENT '1正常 0取消', UNIQUE KEY uk_flight_no_date (flight_no, flight_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;唯一索引(flight_no, flight_date)保证同一天同一航班号只有一条记录,这是后面防重复预订的基础。
舱位表用来存每种舱位的价格和库存:
CREATE TABLE cabin ( id BIGINT AUTO_INCREMENT PRIMARY KEY, flight_id BIGINT NOT NULL COMMENT '关联航班', cabin_class VARCHAR(10) NOT NULL COMMENT 'Y经济舱 F头等舱 C公务舱', price DECIMAL(10,2) NOT NULL COMMENT '票价', discount_rate DECIMAL(3,2) DEFAULT 1.00 COMMENT '折扣率', stock INT NOT NULL COMMENT '余票数', total_stock INT NOT NULL COMMENT '总座位数', UNIQUE KEY uk_flight_cabin (flight_id, cabin_class) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;订单表在基本字段之外,我强烈建议加一个"快照"设计。什么叫快照?就是你下单时把航班号、起降时间、舱位等级、乘机人姓名身份证这些信息原样冗余一份存到订单里。这样之后航班改时间了、价格变动了,都不影响已经出的订单,也不影响你打印行程单。这个设计思路在答辩时说出去,老师会觉得你考虑到了真实业务场景,瞬间拉开和普通同学的距离。
2.3 接口统一规范与 Token 鉴权
接口设计直接决定前后端联调效率。我见过最痛苦的项目是每个接口返回格式都不一样,前端解析字段快疯了。所以一开始就统一返回结构:
@Data public class Result<T> { private Integer code; // 200成功,500失败,401未认证 private String msg; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMsg("success"); r.setData(data); return r; } public static <T> Result<T> fail(String msg) { Result<T> r = new Result<>(); r.setCode(500); r.setMsg(msg); return r; } }APP 端的鉴权我推荐直接上 JWT。它的逻辑并不复杂:用户登录成功后,后端用密钥生成一个带过期时间的 Token 返回,APP 把 Token 存在本地,之后每次请求在请求头里带上Authorization: Bearer <token>,后端用一个拦截器(HandlerInterceptor)校验 Token,校验通过就把用户信息放进 ThreadLocal,方便 Controller 直接取。为什么不用 Session?因为 APP 不是浏览器,Session 依赖 Cookie,跨端体验不好,而且分布式部署时 Session 还要额外做共享。JWT 天然无状态,扩展性更强,这个选型逻辑在答辩时能讲出一套完整的理由。
再说一个热搜词里出现过的点:全局过滤器处理上传 PDF 时的 XSS 攻击。这类安全问题放在毕设里其实是加分项。你可以在application.yml里配置一个过滤器,对所有请求参数做<script>、onerror等关键字符的转义,XSS 过滤器不需要很复杂,用 OncePerRequestFilter 包装一层 HttpServletRequestWrapper 重写getParameter即可。虽然航旅系统里大多数情况不会有人恶意攻击你,但答辩时能主动说出"我做了 XSS 过滤",说明你网络安全意识到位。
3. 航班查询与预订:订单业务里最考验细节的一环
3.1 多条件航班查询与 Redis 缓存加速
航班查询是用户打开 APP 后做的第一件事,也是接口设计里最需要关注的性能点。查询条件一般是:出发城市、到达城市、日期,可选舱位等级和航空公司。对应的 SQL 不难,但要注意索引:
SELECT f.id, f.flight_no, f.airline, f.depart_time, f.arrive_time, f.depart_airport, f.arrive_airport, c.cabin_class, c.price, c.discount_rate, c.stock FROM flight f LEFT JOIN cabin c ON c.flight_id = f.id WHERE f.depart_city = #{fromCity} AND f.arrive_city = #{toCity} AND f.flight_date = #{date} AND f.status = 1 AND (c.cabin_class = #{cabinClass} OR #{cabinClass} IS NULL) ORDER BY f.depart_time;注意depart_city、arrive_city、flight_date这三列是高频查询条件,要建联合索引。另外时间字段的存储,我强烈建议存DATETIME而不是字符串,这样前端拿到手直接格式化,还能避免时区换算的一堆麻烦(比如起始日期当天 0 点 5 分的航班被字符串比较搞错位)。
既然用到了 Redis,查询接口就可以加一层缓存。缓存 key 设计成flight:query:{from}:{to}:{date}:{cabinClass},value 存查询结果的 JSON,TTL 设置 5 分钟即可。为什么要 5 分钟?因为航班数据不是频繁变动的数据,5 分钟的延迟用户根本感知不到,而 Redis 缓存能扛住瞬时高并发,也让你的项目在技术方案上有"缓存"这个层次。代码逻辑就三步:先查缓存,命中直接返回;未命中查数据库再写缓存;返回结果。
有一点必须提醒:管理后台修改了航班价格或取消航班后,对应缓存要主动删除,否则用户永远查到的是旧数据。用 Spring 的CacheEvict注解或者手动redisTemplate.delete(key)都行,这个问题就是典型的缓存一致性问题,答辩常问。
3.2 预订事务与库存扣减:优雅地防止超卖
预订是整个项目里业务逻辑最重的接口,流程是这样的:用户提交订单 -> 校验航班和舱位 -> 扣减库存 -> 创建订单记录(状态:待支付)-> 返回订单号。这里有两个问题必须解决:一是扣库存和建订单要放在同一个事务里,任何一步失败都要回滚;二是并发场景下不能超卖,也就是同一个舱位剩余 3 张票,5 个人同时下单,最后只能有 3 个人成功。
用代码实现的思路有两条路:悲观锁和乐观锁。
悲观锁就是查库存的时候直接SELECT ... FOR UPDATE锁住这行,其他人只能等锁释放。实现最简单,但性能差点,适合毕设场景。推荐代码示例:
@Transactional public Order createOrder(Long userId, Long flightId, Long cabinId, List<Passenger> passengers) { // 1. 悲观锁查询舱位 Cabin cabin = cabinMapper.selectByIdForUpdate(cabinId); if (cabin.getStock() < 1) { throw new BizException("该舱位已售罄"); } // 2. 扣库存 cabin.setStock(cabin.getStock() - 1); cabinMapper.updateById(cabin); // 3. 生成订单... }selectByIdForUpdate是 MyBatis-Plus 里自定义 SQL,配合@Transactional确保整个操作原子性。
乐观锁则是在cabin表加一个version字段,更新时带上version条件:
UPDATE cabin SET stock = stock - 1, version = version + 1 WHERE id = #{cabinId} AND stock > 0 AND version = #{oldVersion}如果更新返回 0 行,说明被别人抢了,重新尝试即可。在答辩时把这两种方案都讲出来,再说一句"考虑到毕设并发量不大,我最终选了悲观锁方案,代码更直观;如果做高并发优化会采用乐观锁",既展示了知识面又体现了务实性。
还要留个心眼处理接口幂等的问题。用户手快了点击两次"提交订单",结果下了两单,这就是重复提交。最稳的办法是在订单表加一个order_no唯一索引,用UUID(去掉横线)生成订单号,插入时报主键冲突就说明重复下单;另外前端也要做按钮置灰。这两层一叠加,基本就防住了。
3.3 舱位价格计算与订单快照设计
价格这块别想复杂,真实航空的动态定价是个大数据系统,但毕设只要做好"多舱位多价格"就够了。简单策略就是:每个航班下挂 N 个舱位(经济舱/公务舱/头等舱),各自有基础价和折扣率,展示价格 = 基础价 x 折扣率。折扣率可以随时间变化,比如提前 15 天订是 0.6 折,临近起飞是 0.9 折,逻辑写死在一个PriceService里,方便答辩时讲解。
下单时把价格算好存入订单表,这里就引出快照表的价值了。订单表里冗余一份flight_snapshot字段(JSON 类型),存的是航班号、起降时间、舱位等级、单价、乘机人等信息的拼接串。用户查订单详情时直接读快照,不需要实时去 join 航班表。这个设计的好处是:订单的历史数据是"冻结"的,哪怕航班改了时间,用户看到的订单信息依然是下单那一刻的事实。用大白话讲,就像你租房时签的合同,房东之后涨价跟你没关系。
乘机人信息录入时,身份证号码要校验格式(18 位正则),手机号校验 11 位,这是成本极低但体验提升明显的小细节。建议在 APP 端做一个"常用乘机人"管理,用户把家人信息先录入,下单时勾选即可,既省事又显得功能完整。
4. 行程管理:从订单状态机到定时任务
4.1 订单状态的定义与流转规则
行程管理模块本质上是"订单状态+时间"的展示。所以第一步,把订单状态定义清楚。我用一个状态枚举来管理:
public enum OrderStatus { PENDING_PAYMENT(0, "待支付"), PAID(1, "已支付"), ISSUED(2, "已出票"), CHECKED_IN(3, "已值机"), COMPLETED(4, "已完成"), CANCELLED(5, "已取消"), REFUNDED(6, "已退票"); private final int code; private final String desc; // getter... }状态流转规则要十分明确,不能出现"从已完成突然变成已取消"这种诡异情况。合法的流转路径是:
- 待支付 ->(用户主动取消)-> 已取消
- 待支付 ->(超时未支付,定时任务取消)-> 已取消
- 待支付 ->(模拟支付成功)-> 已支付/已出票
- 已出票 ->(模拟值机)-> 已值机
- 已值机 ->(飞行日期已过)-> 已完成
- 已出票/已值机 ->(申请退票)-> 已退票
在代码层面,不要在 Service 里写一堆散落的 if else 判断状态,而是封装一个orderStateMachine或者直接在枚举里加一个canTransitTo(target)方法,统一校验。例如枚举里维护一个 Map:
private static final Map<OrderStatus, List<OrderStatus>> TRANSITIONS = new HashMap<>(); static { TRANSITIONS.put(PENDING_PAYMENT, Arrays.asList(PAID, CANCELLED)); TRANSITIONS.put(PAID, Arrays.asList(ISSUED, REFUNDED)); // ... }每次状态变更走统一入口,非法流转直接抛异常。答辩时你可以说这是借鉴了状态机设计模式,保证了订单状态的安全性,这一句话就能撑起一个加分点。
4.2 行程按时间轴展示与倒计时计算
行程管理在 APP 端呈现出来的样子,应该是"最近要飞的行程排在最前面,每个行程卡片上有倒计时"。后端接口设计上,我建议提供两个接口:
- 查询待出发行程(订单状态为已出票或已值机,且起飞时间大于当前时间)
- 查询历史行程(已完成、已取消、已退票)
待出发行程按起飞时间升序排列,返回给前端时带上departTime时间戳。前端拿到后用本地时间计算倒计时,格式可以做成"距起飞还有 2 天 5 小时 30 分"。这里有个坑:千万不要在后端算了倒计时的字符串返回给前端,因为前端页面停留期间时间一直在走,如果后端只返回静态字符串,倒计时不动,用户会觉得系统坏掉了。正确的做法是后端只返回时间戳,前端通过 JS 定时器每秒刷新一次倒计时。
行程详情的页面可以展示一个时间轴,从"预订成功"到"支付完成"到"出票成功"再到"值机成功",按时间顺序纵向排列。后端只需在订单状态变更时记录一条order_timeline表(order_id, status, operate_time, note),查询时按时间升序返回即可,实现非常轻量。有了这个表,订单详情的"操作履历"也顺便解决了,一举两得。
4.3 定时任务:清理超时订单与航班变动提醒
系统里有两类场景天然适合定时任务处理:一是用户下单后一直不支付,订单一直占着库存;二是航班状态发生变化,需要通知相关用户。
第一类场景的处理逻辑不复杂。Spring 自带的@Scheduled注解就能搞定,假设设定 15 分钟未支付自动取消,那么写一个定时任务,每 5 分钟扫描一次订单表:
UPDATE orders SET status = 5, cancel_time = NOW() WHERE status = 0 AND create_time < DATE_SUB(NOW(), INTERVAL 15 MINUTE);这个 SQL 执行完,还要把对应的舱位库存加回去(因为之前的扣减操作要还回来)。逻辑就是:查出所有被取消的超时订单,循环释放每个订单对应的舱位库存。注意这里也涉及事务,一个订单释放失败不能影响其他订单。
在阐述方案的时候,你可以补充一句"如果要做到更精准的延迟触发,应该用 RocketMQ 的延迟消息或者 Redis 过期键监听,但毕设用定时扫描已经足够",这能体现你对方案边界有认知。
第二类场景,航班取消或者延误后要给已购票用户发通知。毕设实现最简单的方案不是上消息队列,而是在消息中心表里插一条记录,同时往用户的"消息列表"里写数据。可以做成:管理后台修改航班状态时调用notifyService.flightChanged(flightId),这个 Service 查出所有关联该航班的未出行订单,给每个用户生成一条站内消息。APP 端的消息中心拉取未读数量,有红点提示即可。整体工作量很小,但功能完成度高,演示的时候效果很好。
5. 从开发到答辩:项目演示与高频追问整理
5.1 开发环境的一致性问题
做毕设期间,我见过太多"本地能跑,一换电脑就报废"的案例。环境统一是减少痛苦的唯一办法。我这里给一个完整的参考组合:JDK 8 + Maven 3.6.3(用 IDE 内置也行)+ SpringBoot 2.7.18 + MySQL 8.0 + Redis 6.x + Node 16(前端)。每个组件版本都要在项目文档里写清楚。
关于项目构建工具,很多同学纠结 Maven 还是 Gradle。我的建议是无脑选 Maven,原因有两个:一是 Maven 的依赖传递更直观,出问题看报错更容易定位;二是网上 SpringBoot 相关教程和 pom 配置几乎全是 Maven 的,整合 MyBatis-Plus 时搜答案方便。热搜词里有人搜"2020年 gradle 构建的 springboot 项目配置文件",大概率是接手了早期模板项目,然后踩了一堆 Gradle 和 SpringBoot 插件版本不兼容的坑,这类问题完全可以从选型上规避。
打包部署的问题也要提前想好。开发期你 IDE 里直接 Run 就行,但演示前的打包必须跑通。在pom.xml里确保配置了 SpringBoot 的maven-plugin,执行mvn clean package会打出一个可执行 jar。注意一个小坑:如果项目里有前端静态资源(比如管理后台的 dist),打包顺序要先用npm run build构建前端,再执行 Maven 打包把 dist 拷入src/main/resources/static,这一步顺序乱了,打包产物就会缺前端页面。
5.2 演示环境与部署建议
毕设演示最尴尬的场面是什么?u 盘里的代码在讲台上打不开,或者演示到一半 Redis 没启动直接白屏。我的建议是准备两套方案:
首选是本机演示。提前把所有服务启动好:MySQL 服务、Redis 服务、后端 jar、APP 前端(模拟器或真机)。这里有个细节,如果你用的电脑是公司或机房电脑,MySQL 和 Redis 可能没装,可以在答辩前一天把绿色版 MySQL 和 Redis 解压包准备在 u 盘里,免安装启动的命令要自己先演练两遍,比如 Redis 的redis-server.exe双击启动,MySQL 的mysqld --initialize-insecure初始化流程。
备选方案是 Docker 部署。如果你对 Docker 熟悉,可以写一个简单的 docker-compose,把 MySQL、Redis、后端三个容器编排起来。这里给一个参考 Dockerfile:
FROM openjdk:8-jre WORKDIR /app COPY target/cloud-air-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]Docker 方案的好处是换任何一台有 Docker 的电脑都能秒级复现环境,答辩时非常加分。但要注意:不要为了演示 Docker 而把简单事搞复杂,如果自己对 Docker 不熟,老老实实用本机方案即可,毕竟演示翻车比没有 Docker 更减分。
5.3 答辩现场的高频问题与回答思路
答辩环节老师大概率围绕技术选型和项目难点提问。我把出现频率最高的六个问题整理成回答思路,你可以提前准备:
问题一:SpringBoot 的自动装配原理是什么?回答思路:@SpringBootApplication是组合注解,核心是@EnableAutoConfiguration。它在启动时扫描META-INF/spring.factories(2.7 之前)或者META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7+)文件,拿到所有自动配置类的全限定名,然后通过@Conditional系注解判断当前环境是否满足条件(比如 Classpath 里有没有对应类),满足才加载配置。这就是"引入依赖即自动配置"的原理。
问题二:为什么用 JWT 而不用 Session?回答思路:APP 不是浏览器,Session 依赖 Cookie 不适用;JWT 无状态、可跨端、易于扩展;配合 Redis 做 Token 黑名单可以实现主动失效。但要主动提到"JWT 无法在服务端主动销毁,这是它的局限",显得你的思考是辩证的。
问题三:你是怎么防止库存超卖的?按照前面讲的悲观锁/乐观锁逻辑回答,然后加一句"我在订单号上加了唯一约束来保证幂等,防止重复下单"。老师基本就会点头。
问题四:Redis 缓存和数据库数据不一致怎么办?回答思路:修改数据库后主动删除/更新缓存;缓存设置 TTL 过期兜底;对一致性要求高的数据不缓存(如支付结果)。整个思路是"最终一致性"而非"强一致性"。
问题五:数据库表是怎么设计的?为什么订单表要冗余航班信息?把快照思路讲一遍,强调"保护历史订单不受后续数据变更影响"。这是最容易展现设计水平的回答。
问题六:项目里最大的难点是什么?不要回答"没有难点",也不要只回答"登录"。最好的答案是:航班预订场景下的并发库存控制和订单状态一致性。把事务边界、库存扣减、状态机流转完整讲一遍,展示你确实动手深入过。
最后再分享一个我自己反复强调的技巧:答辩演示前,把测试环境的航班库存故意改成只有 1 张,然后现场演示两个用户同时抢票,展示只有一个能成功。这个操作视觉冲击力极强,比你说一百句"我处理了并发"都管用。平时把这个场景录个视频放手机里,万一现场网络出问题,你还能兜底展示,不至于冷场。做毕设这件事,从头到尾最重要的能力不是堆新技术,而是把一条核心业务链路想明白、做扎实、讲清楚,这才是老师真正想看到的东西。