1. 项目背景与核心价值
航空机票预定系统作为现代旅行服务的基础设施,其技术实现涉及前后端分离架构、高并发处理、分布式事务等核心挑战。这个基于SpringBoot+Vue的全栈项目,为开发者提供了一个完整的航空票务解决方案参考模板。
我在实际开发这类系统时发现,票务业务看似简单,实则暗藏多个技术深坑。比如同一航班的库存超卖问题,在测试环境可能运行良好,但上线后遇到促销活动时就会出现灾难性后果。这个开源项目完整实现了从用户端查询到后台管理的全链路功能,特别适合需要快速构建航空票务平台的中小团队。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型解析
2.1 SpringBoot后端优势
选择SpringBoot作为后端框架主要基于三个考量:
- 自动配置机制大幅减少XML配置,比如通过
@EnableTransactionManagement一个注解即可开启事务管理 - 内嵌Tomcat容器简化部署,配合
spring-boot-maven-plugin插件可直接生成可执行JAR - 丰富的Starter依赖,例如本项目用到的:
spring-boot-starter-data-jpa用于数据库操作spring-boot-starter-security实现权限控制spring-boot-starter-mail发送订单确认邮件
关键配置示例:
java复制@SpringBootApplication
@EnableCaching
public class AirTicketApplication {
public static void main(String[] args) {
SpringApplication.run(AirTicketApplication.class, args);
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
// 航班信息缓存配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
2.2 Vue前端技术特点
前端采用Vue 2.x版本主要考虑生态成熟度,关键实现包括:
- 使用Vue Router实现SPA路由跳转
- Vuex管理全局状态(如用户登录态)
- Element UI组件库快速搭建管理后台界面
- Axios封装API请求,配合拦截器实现权限验证
机票搜索页面的核心逻辑:
javascript复制export default {
data() {
return {
flights: [],
searchParams: {
departure: '',
destination: '',
date: new Date()
}
}
},
methods: {
async searchFlights() {
try {
const { data } = await api.searchFlights(this.searchParams)
this.flights = data.map(item => ({
...item,
// 转换时间格式
departureTime: dayjs(item.departureTime).format('HH:mm'),
price: item.economyPrice // 默认显示经济舱价格
}))
} catch (error) {
this.$message.error('查询失败')
}
}
}
}
3. 核心业务模块实现
3.1 航班库存管理
采用乐观锁解决超卖问题:
java复制@Transactional
public BookingResult bookFlight(Long flightId, int seats) {
Flight flight = flightRepository.findById(flightId)
.orElseThrow(() -> new BusinessException("航班不存在"));
// 检查余票
if (flight.getAvailableSeats() < seats) {
return BookingResult.failed("余票不足");
}
// 乐观锁更新
int updated = flightRepository.reduceSeatsWithVersion(flightId, seats, flight.getVersion());
if (updated == 0) {
throw new ConcurrentBookingException("订票冲突,请重试");
}
// 生成订单...
return BookingResult.success(order);
}
配套的数据库表设计:
sql复制CREATE TABLE `t_flight` (
`id` bigint NOT NULL AUTO_INCREMENT,
`flight_number` varchar(20) NOT NULL,
`departure_city` varchar(50) NOT NULL,
`arrival_city` varchar(50) NOT NULL,
`departure_time` datetime NOT NULL,
`available_seats` int NOT NULL DEFAULT 0,
`economy_price` decimal(10,2) NOT NULL,
`business_price` decimal(10,2) NOT NULL,
`version` int NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_flight_number` (`flight_number`,`departure_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 支付与订单状态机
订单状态流转设计:
java复制public enum OrderStatus {
PENDING_PAYMENT(1, "待支付"),
PAID(2, "已支付"),
CANCELLED(3, "已取消"),
COMPLETED(4, "已完成");
// 状态转换校验逻辑
public static void validateTransition(OrderStatus from, OrderStatus to) {
if (from == PAID && to == CANCELLED) {
throw new IllegalStateException("已支付订单不能直接取消");
}
// 其他校验规则...
}
}
支付超时处理(使用Spring Scheduled):
java复制@Scheduled(cron = "0 */5 * * * ?")
public void cancelUnpaidOrders() {
List<Order> unpaidOrders = orderRepository.findByStatusAndCreateTimeBefore(
OrderStatus.PENDING_PAYMENT,
LocalDateTime.now().minusMinutes(30)
);
unpaidOrders.forEach(order -> {
order.cancel("超时未支付");
orderRepository.save(order);
// 释放库存
flightRepository.increaseSeats(order.getFlightId(), order.getSeats());
});
}
4. 系统安全与性能优化
4.1 安全防护措施
-
XSS防护:
- 前端使用vue-sanitize过滤用户输入
- 后端配置HttpSecurity:
java复制@Override protected void configure(HttpSecurity http) throws Exception { http.headers() .xssProtection() .and() .contentSecurityPolicy("script-src 'self'"); } -
CSRF防御:
- 敏感操作要求携带JWT token
- 关键表单提交使用双重验证
-
数据加密:
- 用户密码使用BCrypt加密
- 支付信息采用AES对称加密存储
4.2 缓存策略设计
采用多级缓存架构:
- 本地Caffeine缓存高频访问的航班基础信息
- Redis集群缓存热门航线查询结果
- 数据库查询添加读写分离
缓存更新策略示例:
java复制@CacheEvict(value = "flights", key = "#flightId")
public void updateFlight(Flight flight) {
flightRepository.save(flight);
// 异步更新搜索引擎数据
searchService.refreshFlightIndex(flight.getId());
}
5. 部署与监控方案
5.1 容器化部署
Docker Compose编排文件示例:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
5.2 监控指标采集
使用Spring Boot Actuator暴露指标:
properties复制# application.properties
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.tags.application=air-ticket-system
配合Grafana监控面板跟踪:
- 接口响应时间P99
- JVM内存使用率
- 数据库连接池状态
- 关键业务指标(订单创建速率、支付成功率等)
6. 典型问题排查实录
6.1 航班搜索性能劣化
现象:当航线数据超过10万条时,组合查询响应时间从200ms升至2s+
排查过程:
- 通过Arthas trace命令发现耗时主要在数据库查询
- EXPLAIN分析SQL发现未使用联合索引
- 确认是由于前端传递的排序字段动态变化导致
解决方案:
sql复制-- 添加覆盖索引
ALTER TABLE t_flight ADD INDEX idx_search (
departure_city,
arrival_city,
departure_time,
economy_price
);
6.2 支付回调丢失
现象:部分用户支付成功后订单状态未更新
根因分析:
- 支付平台重试机制不符合规范(只重试3次)
- 我方接口未做幂等处理导致重复支付被拒绝
修复方案:
java复制@PostMapping("/pay/notify")
public String handlePayNotify(@RequestBody NotifyRequest request) {
// 幂等处理
String orderNo = request.getOrderNo();
Order order = orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new BusinessException("订单不存在"));
if (order.getStatus() == OrderStatus.PAID) {
return "success"; // 已处理直接返回
}
// 验签逻辑...
order.pay(request.getTransactionId());
orderRepository.save(order);
// 触发后续业务流程
eventPublisher.publishEvent(new OrderPaidEvent(order));
return "success";
}
在开发这类系统时,我特别建议做好以下三点:1)所有核心业务操作必须有完整的日志溯源;2)关键业务流程要实现可视化监控;3)数据库变更必须通过Flyway等工具进行版本化管理。这些措施在后期排查线上问题时能节省大量时间。
