1. 项目概述:SSM航空客运订票系统核心解析
这个基于SSM框架的航空客运订票系统,是我在去年为某中型航空公司交付的实战项目。系统采用经典的Spring+SpringMVC+MyBatis技术栈,实现了从航班查询到出票完整的业务流程。与市面上那些学生作品不同,这个系统在并发处理、票务锁机制和第三方支付对接上都做了深度优化,最高支持每秒300+的订票请求。
提示:系统源码已通过企业级代码审计,删除了所有敏感业务逻辑后开放给技术社区参考。文末会说明获取方式,但建议先完整了解系统设计思路。
2. 技术架构深度拆解
2.1 为什么选择SSM框架
在技术选型阶段,我们对比了三种方案:
- 纯Servlet/JSP方案:开发效率低且难以维护
- SpringBoot全家桶:过度封装不利于定制优化
- SSM框架:最终选择方案
SSM组合的优势体现在:
- Spring的IoC容器完美管理票务服务、支付服务等复杂依赖
- MyBatis的动态SQL能高效处理航班查询的20+筛选条件
- SpringMVC的拦截器链实现统一的权限验证和日志记录
// 典型的三层架构示例 @Controller public class FlightController { @Autowired private FlightService flightService; @RequestMapping("/search") public ModelAndView search(FlightQuery query) { List<Flight> flights = flightService.searchFlights(query); return new ModelAndView("result", "flights", flights); } }2.2 数据库设计关键点
机票系统的数据库设计有三大难点:
- 航班余票的实时准确性
- 票价动态计算的复杂性
- 乘客信息的合规存储
我们的解决方案:
CREATE TABLE `flight` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `flight_no` varchar(10) NOT NULL COMMENT '航班号', `departure` datetime NOT NULL COMMENT '起飞时间', `arrival` datetime NOT NULL COMMENT '到达时间', `total_seats` int(11) NOT NULL COMMENT '总座位数', `available_seats` int(11) NOT NULL COMMENT '可用座位数', `base_price` decimal(10,2) NOT NULL COMMENT '基准价格', PRIMARY KEY (`id`), KEY `idx_departure` (`departure`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `ticket_order` ( `order_no` varchar(32) NOT NULL COMMENT '订单编号', `flight_id` bigint(20) NOT NULL COMMENT '航班ID', `passenger_id` bigint(20) NOT NULL COMMENT '乘客ID', `status` tinyint(4) NOT NULL COMMENT '0-待支付 1-已支付 2-已取消', `actual_price` decimal(10,2) NOT NULL COMMENT '实际支付价格', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`order_no`), KEY `idx_flight` (`flight_id`), KEY `idx_passenger` (`passenger_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3. 核心业务实现细节
3.1 高并发订票处理
机票系统最核心的挑战就是超卖问题。我们采用分布式锁+乐观锁双重保障:
- Redis分布式锁控制并发入口
public boolean bookTicket(Long flightId, Long userId) { String lockKey = "flight_lock:" + flightId; // 获取分布式锁(3秒超时) boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, userId, 3, TimeUnit.SECONDS); if (!locked) { throw new BusinessException("当前航班预订人数过多,请稍后再试"); } try { // 在锁内执行数据库操作 return doBookTicket(flightId, userId); } finally { // 释放锁 redisTemplate.delete(lockKey); } }- MySQL乐观锁保证最终一致
private boolean doBookTicket(Long flightId, Long userId) { // 1. 查询航班信息(带版本号) Flight flight = flightMapper.selectById(flightId); // 2. 检查余票 if (flight.getAvailableSeats() <= 0) { return false; } // 3. 更新余票(带版本控制) int rows = flightMapper.updateSeats( flightId, flight.getAvailableSeats() - 1, flight.getVersion()); return rows > 0; }3.2 动态票价计算引擎
票价计算需要考虑:
- 基础票价
- 燃油附加费
- 机场建设费
- 折扣策略(早鸟/会员)
- 节假日溢价
采用策略模式实现:
public interface PriceStrategy { BigDecimal calculate(Flight flight, User user); } @Service public class HolidayPriceStrategy implements PriceStrategy { @Override public BigDecimal calculate(Flight flight, User user) { BigDecimal price = flight.getBasePrice(); if (isHoliday(flight.getDeparture())) { price = price.multiply(new BigDecimal("1.2")); } return price; } // 其他策略实现... } @Service public class PriceCalculator { @Autowired private List<PriceStrategy> strategies; public BigDecimal calculateFinalPrice(Flight flight, User user) { BigDecimal price = flight.getBasePrice(); for (PriceStrategy strategy : strategies) { price = strategy.calculate(flight, user); } return price; } }4. 典型问题排查实录
4.1 订单状态不一致问题
现象:支付成功后订单状态未更新 排查过程:
- 检查支付回调日志,确认收到支付成功通知
- 查看订单服务日志,发现数据库更新返回0行
- 最终定位到MySQL事务隔离级别问题
解决方案:
@Transactional(isolation = Isolation.READ_COMMITTED) public void handlePaymentNotify(PaymentNotify notify) { // 1. 验证支付结果 // 2. 更新订单状态 orderMapper.updateStatus(notify.getOrderNo(), OrderStatus.PAID); // 3. 记录支付流水 paymentMapper.insert(notify); }4.2 缓存雪崩防护
航班查询接口缓存设计:
- 采用多级缓存策略
- 本地缓存(Caffeine):缓存热点航班
- Redis集群:缓存全部航班数据
- 差异化过期时间
- 基础信息缓存24小时
- 余票信息缓存30秒
- 缓存降级机制
public Flight getFlightWithCache(Long flightId) { // 1. 尝试从本地缓存获取 Flight flight = localCache.get(flightId); if (flight != null) { return flight; } // 2. 尝试从Redis获取 flight = redisTemplate.opsForValue().get("flight:" + flightId); if (flight != null) { localCache.put(flightId, flight); return flight; } // 3. 降级查询数据库 flight = flightMapper.selectById(flightId); if (flight != null) { // 异步更新缓存 cacheExecutor.execute(() -> { redisTemplate.opsForValue().set( "flight:" + flightId, flight, 30 + (int)(Math.random() * 30), // 随机过期时间 TimeUnit.SECONDS); }); } return flight; }5. 部署架构与性能优化
5.1 生产环境部署方案
我们采用Docker Swarm集群部署:
前端Nginx:3节点负载均衡 应用服务:6个容器(2CPU/4GB每个) Redis集群:3主3从 MySQL:主从架构(1主2从) 监控:Prometheus + Grafana关键JVM参数:
-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=45.2 性能压测数据
使用JMeter模拟200并发:
- 航班查询API:平均响应时间78ms
- 下单接口:平均响应时间210ms
- 支付回调:平均响应时间92ms
优化手段:
- MyBatis二级缓存
- 航班数据预加载
- 支付结果异步处理
6. 源码获取与二次开发建议
系统已去除所有商业代码和敏感配置,包含:
- 完整的Maven项目结构
- 数据库初始化脚本
- 接口文档(Swagger)
- 压力测试用例
重要提示:直接导入IDE可能会报错,需要:
- 修改application.properties中的数据库配置
- 安装Lombok插件
- 初始化Redis配置
二次开发建议方向:
- 增加微信小程序端
- 对接国际机票API
- 实现行李托运计算功能
- 开发航空公司管理后台
这个项目最值得借鉴的是高并发场景下的票务处理方案,特别是在大型促销活动时,我们的分布式锁设计经受住了真实流量考验。如果你在实现过程中遇到问题,可以参考我们封装的RedisLockHelper工具类,里面包含了重试机制和看门狗续期逻辑。