1. 项目概述:基于SpringBoot+Vue的现代化列车售票系统
这个售票系统采用前后端分离架构,前端使用Vue.js构建响应式用户界面,后端基于SpringBoot提供RESTful API服务。系统实现了从车次查询、余票显示到在线支付的全流程电子化售票功能,相比传统售票窗口效率提升300%以上。我在实际开发中发现,这种架构特别适合需要快速响应和高并发处理的票务场景。
2. 技术架构解析
2.1 后端技术栈设计
SpringBoot 2.7.x作为后端核心框架,配置了以下关键组件:
- Spring Security + JWT实现认证授权
- MyBatis-Plus 3.5.x操作MySQL数据库
- Redis 6.x缓存热点数据(如余票信息)
- RabbitMQ处理异步消息(如订单超时)
数据库表设计要点:
sql复制CREATE TABLE `train_schedule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`train_number` varchar(20) NOT NULL COMMENT '车次',
`departure_station` varchar(50) NOT NULL,
`arrival_station` varchar(50) NOT NULL,
`departure_time` datetime NOT NULL,
`duration` int NOT NULL COMMENT '分钟',
`business_seats` int DEFAULT 0,
`first_seats` int DEFAULT 0,
`second_seats` int DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_station_time` (`departure_station`,`arrival_station`,`departure_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 前端技术方案
Vue 3.x + Element Plus构建的管理端和用户端:
- 动态路由权限控制
- Axios封装请求拦截
- ECharts实现售票数据可视化
- WebSocket实时推送余票变更
重要提示:余票查询接口必须做防刷处理,建议采用Redis令牌桶算法限流
3. 核心业务实现
3.1 余票计算算法
采用席位分段锁定策略,核心逻辑:
java复制public synchronized TicketResult lockSeats(TicketRequest request) {
// 1. 检查余票缓存
String cacheKey = "train:"+request.getTrainId()+":date:"+request.getDate();
Integer remain = redisTemplate.opsForValue().get(cacheKey);
// 2. 数据库行级锁查询
TrainSchedule schedule = trainMapper.selectForUpdate(request.getTrainId());
// 3. 生成临时订单记录
Order order = new Order();
order.setStatus(OrderStatus.LOCKED);
orderMapper.insert(order);
// 4. 设置15分钟支付倒计时
redisTemplate.opsForValue().set("order:"+order.getId(), "locked", 15, TimeUnit.MINUTES);
}
3.2 高并发解决方案
实测在4核8G服务器配置下:
- 使用Redisson分布式锁处理超卖问题
- 余票信息每5秒更新到Redis
- Nginx配置负载均衡+静态资源缓存
压力测试结果:
| 并发用户数 | 平均响应时间 | 错误率 |
|---|---|---|
| 500 | 238ms | 0.12% |
| 1000 | 417ms | 0.35% |
| 2000 | 1.2s | 1.8% |
4. 典型问题排查
4.1 座位重复销售问题
现象:同一座位在支付期间被重复锁定
解决方案:
- 添加数据库唯一索引:
sql复制ALTER TABLE ticket ADD UNIQUE INDEX `idx_seat` (`train_id`,`carriage`,`seat`,`date`);
- 实现二次校验机制:
java复制public boolean confirmSeat(Long orderId) {
// 检查订单状态是否已变更
Order order = orderMapper.selectById(orderId);
if(order.getStatus() != OrderStatus.LOCKED) {
throw new BusinessException("订单状态异常");
}
// 实际扣减库存
return trainMapper.updateSeatCount(order.getTrainId(),
order.getSeatType(), -1) > 0;
}
4.2 支付超时处理
采用状态机模式管理订单生命周期:
code复制[新订单] --锁定--> [待支付] --支付成功--> [已完成]
|--超时--> [已取消]
|--手动取消--> [已取消]
定时任务配置:
java复制@Scheduled(cron = "0 */1 * * * ?")
public void checkTimeoutOrders() {
List<Order> orders = orderMapper.selectTimeoutOrders();
orders.forEach(order -> {
order.setStatus(OrderStatus.CANCELLED);
orderMapper.updateById(order);
// 释放座位库存
trainMapper.returnSeat(order.getTrainId(),
order.getSeatType(), 1);
});
}
5. 部署优化实践
5.1 容器化部署方案
Docker Compose编排文件示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
5.2 性能调优参数
关键JVM参数:
code复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-Xms2g -Xmx2g
Nginx优化配置:
code复制worker_processes auto;
events {
worker_connections 10240;
multi_accept on;
}
http {
gzip on;
gzip_min_length 1k;
keepalive_timeout 65;
}
实际开发中我发现,采用Read/Write Through模式缓存余票信息能显著降低数据库压力。当用户查询余票时,系统首先检查Redis缓存,如果不存在则从数据库加载并更新缓存。对于购票操作,采用Cache Aside策略,先更新数据库再删除缓存,确保数据一致性。
