1. 项目背景与核心需求
校车车票预订系统是高校后勤管理数字化转型的重要组成部分。在传统模式下,学生乘坐校车往往需要现场排队购票,高峰期经常出现拥挤、票务信息不透明、座位分配混乱等问题。我们团队开发的这套基于SSM框架的系统,正是为了解决这些痛点而生。
这个系统最核心的价值在于实现了校车运营的线上化管理闭环。从学生端的车次查询、在线选座、电子票务,到管理员端的车辆调度、票务统计、财务对账,所有环节都实现了数字化流转。特别值得一提的是,我们针对高校场景做了大量定制化设计:
- 支持按学期预购月票(针对固定路线通勤的教职工)
- 高峰期动态加开车次算法
- 与校园一卡通系统的无缝对接
- 移动端适配的极简购票流程
2. 技术架构解析
2.1 SSM框架选型考量
选择Spring+SpringMVC+MyBatis这套经典组合主要基于以下考虑:
- Spring IOC容器:完美管理车次调度、票务库存等核心业务组件的生命周期
- SpringMVC的RESTful支持:为前后端分离架构提供优雅的API接口
- MyBatis的灵活SQL:应对复杂的票务统计报表需求
实际开发中,我们特别优化了MyBatis的二级缓存配置,将高频访问的车次查询结果缓存到Redis,QPS从最初的120提升到了2100+。以下是核心配置片段:
xml复制<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
2.2 数据库设计要点
票务系统的数据库设计有几个关键挑战:
- 座位库存的并发控制:采用乐观锁机制,在ticket表添加version字段
- 历史数据归档:按月分表的sharding策略
- 支付事务一致性:本地消息表+定时任务补偿
核心表结构设计:
sql复制CREATE TABLE `schedule` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`bus_no` varchar(20) NOT NULL COMMENT '车牌号',
`route_id` int(11) NOT NULL COMMENT '路线ID',
`departure_time` datetime NOT NULL,
`total_seats` int(11) DEFAULT '40',
`available_seats` int(11) DEFAULT '40',
`driver_id` int(11) DEFAULT NULL,
`status` tinyint(4) DEFAULT '1' COMMENT '1-待发车 2-已发车 3-已取消',
PRIMARY KEY (`id`),
KEY `idx_route_time` (`route_id`,`departure_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现细节
3.1 购票业务流程
完整的购票流程涉及多个服务的协同:
- 库存预占:采用Redis分布式锁保证原子性
- 支付处理:对接校园支付网关的SDK
- 电子票生成:PDF417二维码编码规范
- 消息通知:站内信+邮件+短信三通道
关键代码片段(Spring事务管理):
java复制@Transactional(rollbackFor = Exception.class)
public Ticket purchase(Long userId, Long scheduleId, Integer seatNo) {
// 1. 校验库存
Schedule schedule = scheduleMapper.selectForUpdate(scheduleId);
if (schedule.getAvailableSeats() <= 0) {
throw new BusinessException("座位已售罄");
}
// 2. 扣减库存
scheduleMapper.reduceInventory(scheduleId);
// 3. 创建订单
Order order = createOrder(userId, scheduleId, seatNo);
// 4. 生成电子票
String ticketCode = generateTicket(order);
// 5. 发送通知
notifyService.sendPurchaseSuccess(userId, ticketCode);
return buildTicket(order);
}
3.2 动态调度算法
针对周五放学等高峰时段,系统实现了智能加车算法:
- 基于历史数据的LSTM需求预测
- 实时监控购票队列长度
- 司机资源池的智能匹配
算法核心参数:
properties复制# 需求预测参数
predict.window=4h
predict.threshold=0.8
# 资源调度参数
dispatch.max_extra=3
dispatch.min_interval=30m
4. 部署与运维实践
4.1 生产环境部署
我们采用Docker Compose进行服务编排,主要包含以下容器:
- web:Tomcat 9 + 应用war包
- db:MySQL 8.0(配置了主从同步)
- cache:Redis 6集群
- monitor:Prometheus + Grafana监控套件
典型的docker-compose.yml配置:
yaml复制version: '3'
services:
mysql-master:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
redis:
image: redis:6-alpine
command: redis-server --appendonly yes
volumes:
- ./redis/data:/data
ports:
- "6379:6379"
4.2 性能调优经验
在压力测试中我们遇到了几个典型问题:
-
购票接口超时:发现是MySQL连接池不够,调整后:
properties复制spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.connection-timeout=30000 -
二维码生成瓶颈:引入Google的ZXing库本地缓存
java复制private static final Map<String, BitMatrix> CACHE = new LRUCache<>(1000); -
支付回调堆积:改用RabbitMQ实现削峰填谷
java复制@RabbitListener(queues = "payment.callback") public void handleCallback(PaymentMessage message) { // 处理逻辑 }
5. 典型问题排查实录
5.1 座位重复售卖问题
现象:在高并发场景下,偶尔会出现同一个座位被卖给多个用户。
排查过程:
- 检查数据库隔离级别(REPEATABLE_READ)
- 验证MyBatis缓存配置(关闭了localCacheScope)
- 最终发现是前端轮询接口导致的状态不同步
解决方案:
java复制// 添加分布式锁
public boolean lockSeat(Long scheduleId, Integer seatNo) {
String key = "lock:seat:" + scheduleId + ":" + seatNo;
return redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
}
5.2 支付状态同步延迟
现象:用户支付成功后,票务状态更新有5-10秒延迟。
根因分析:
- 校园支付网关的回调机制有1-3秒延迟
- 本地事务日志表索引缺失
- 补偿任务执行间隔设置过长
优化措施:
sql复制-- 添加复合索引
ALTER TABLE payment_log ADD INDEX idx_order_status (order_id, status);
6. 项目演进方向
当前系统已经稳定运行在某985高校三个学期,后续计划:
- 智能排班系统:结合教师课表数据自动推荐最佳乘车时段
- 人脸核验上车:基于OpenCV实现无感检票
- 新能源车调度:对接电动车充电桩API优化路线规划
一个有趣的统计数据:系统上线后,校车准点率从68%提升到了92%,学生投诉量下降了76%。这让我们深刻体会到:好的技术方案必须扎根于真实的业务场景,用数据驱动持续优化。
