1. 项目概述
这个基于SpringBoot和JavaWeb的火车票订票系统,是我去年为一个地方铁路公司开发的商业项目。系统从零开始设计,完整实现了用户注册登录、车次查询、余票显示、在线选座、订单支付等核心功能模块。现在我把这个项目的设计思路和关键实现细节整理出来,希望能给正在开发类似系统的同行一些参考。
系统采用经典的B/S架构,前端使用Thymeleaf模板引擎配合Bootstrap,后端基于SpringBoot 2.7.3开发,数据库选用MySQL 8.0。特别在票务库存管理模块,我们实现了高并发的座位锁定机制,在春运高峰期实测可以稳定支撑每分钟3000+的订票请求。
2. 核心架构设计
2.1 技术栈选型
选择SpringBoot作为基础框架主要考虑三点:
- 自动配置特性大幅减少了XML配置
- 内嵌Tomcat简化了部署流程
- 丰富的Starter依赖可以快速集成各种组件
数据库方面对比了MySQL和PostgreSQL后,最终选择MySQL 8.0是因为:
- 对JSON字段的良好支持,方便存储动态座位图
- 窗口函数在统计报表中的优异表现
- 公司现有运维团队对MySQL更熟悉
2.2 系统分层架构
系统采用典型的三层架构:
code复制表示层:Thymeleaf + Bootstrap
业务层:SpringBoot + Spring Security
数据层:MyBatis + MySQL
特别在业务层,我们做了以下优化设计:
- 使用Spring Cache抽象实现多级缓存
- 采用HikariCP作为数据库连接池
- 通过Spring Retry实现支付接口的自动重试
3. 关键模块实现
3.1 余票查询模块
余票计算是系统的核心难点,我们设计了专门的缓存策略:
java复制// 余票缓存更新策略
@Scheduled(cron = "0 0/5 * * * ?")
public void updateTicketCache() {
// 从数据库读取最新余票
List<Ticket> tickets = ticketMapper.selectAvailableTickets();
// 更新Redis缓存
redisTemplate.opsForValue().set("ticket_cache", tickets);
}
3.2 座位锁定机制
为了避免超卖问题,我们实现了分布式锁:
java复制public boolean lockSeat(String trainNo, String seatNo) {
String lockKey = "lock:" + trainNo + ":" + seatNo;
// 使用Redis的SETNX实现分布式锁
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "locked", 5, TimeUnit.MINUTES);
return Boolean.TRUE.equals(success);
}
4. 数据库设计
4.1 主要表结构
sql复制CREATE TABLE `train` (
`id` bigint NOT NULL AUTO_INCREMENT,
`train_no` varchar(20) NOT NULL,
`start_station` varchar(50) NOT NULL,
`end_station` varchar(50) NOT NULL,
`departure_time` datetime NOT NULL,
`arrival_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_train_no` (`train_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 索引优化
针对高频查询场景,我们添加了以下索引:
- 车次表(train_no)
- 出发时间(departure_time)
- 出发站-到达站联合索引
5. 高并发处理
5.1 缓存策略
采用多级缓存架构:
- 本地缓存(Caffeine):存储静态数据如车站列表
- Redis集群:缓存动态数据如余票信息
- MySQL:持久化存储
5.2 限流措施
在网关层实现令牌桶限流:
java复制@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
6. 安全设计
6.1 认证授权
集成Spring Security实现RBAC:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().permitAll();
}
6.2 数据加密
敏感字段使用AES加密:
java复制public String encrypt(String data) {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
7. 部署方案
7.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: ticket-system:latest
ports:
- "8080:8080"
depends_on:
- redis
- mysql
7.2 监控方案
集成Prometheus + Grafana:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true
8. 开发中的经验教训
-
余票缓存更新策略最初设置的1分钟间隔,在高峰期导致缓存穿透,后来调整为动态间隔(闲时5分钟,忙时30秒)
-
第一次上线时没有做接口限流,导致促销活动时系统崩溃,后来增加了Redis+Lua的分布式限流
-
支付接口超时设置过长(默认30秒),造成线程池耗尽,优化为:
- 首次请求5秒超时
- 重试3次,每次间隔2秒
这个项目让我深刻体会到,票务系统最难的不是功能实现,而是如何在高并发场景下保证数据一致性和系统稳定性。特别是在节假日等购票高峰期,任何一个小问题都可能被放大成系统级故障。
