1. 项目概述
这个基于SpringBoot的电影院购票管理系统是我大三暑假开始构思的毕业设计项目,前后迭代了三个版本才最终成型。作为一个完整的商业系统模拟,它涵盖了从用户注册、影片管理到座位选择和在线支付的全流程功能。选择这个方向是因为我发现市面上很多教学项目要么过于简单(只做CRUD),要么过于复杂(直接上微服务架构),而一个中等规模的单体应用正好能全面展示SpringBoot的核心特性。
系统采用经典的三层架构:Web层用Spring MVC处理请求,Service层实现业务逻辑,DAO层通过MyBatis与MySQL交互。特别之处在于引入了Redis处理高并发的座位锁定,以及用Quartz实现定时解锁超时未支付的座位。源码已经上传到GitHub(为避免广告嫌疑这里不放链接),包含完整的单元测试和API文档。
提示:开发这类涉及金钱交易的系统时,一定要在测试环境充分模拟并发场景。我最初没做压力测试,演示时出现了座位重复售出的尴尬情况。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型解析
2.1 为什么选择SpringBoot
作为2018年就开始接触Spring生态的老用户,我坚持用SpringBoot 2.7.x(最新稳定版)而非3.0的主要考虑是:
- 兼容性:学校机房电脑还跑着JDK8,而SpringBoot 3.x需要JDK17+
- 社区支持:目前企业项目大多还在用2.x系列,遇到问题更容易找到解决方案
- 功能完备:对于毕业设计级别的项目,2.7.x已经包含所需的所有功能模块
实测在4核8G的云服务器上,这个配置能轻松支撑500+的并发购票请求。如果选择SpringMVC原生开发,同样配置下性能会下降30%左右。
2.2 数据库设计要点
核心表结构设计经历了两次重大调整:
sql复制-- 最终版影片表结构示例
CREATE TABLE `movie` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`duration` smallint unsigned NOT NULL COMMENT '分钟为单位',
`release_date` date NOT NULL,
`price` decimal(10,2) unsigned NOT NULL,
`poster_url` varchar(255) DEFAULT NULL,
`status` enum('COMING','SHOWING','OFF') NOT NULL DEFAULT 'COMING',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_status_date` (`status`,`release_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
踩过的坑:
- 最初用varchar存储时长(如"120分钟"),导致无法做范围查询
- 价格字段曾用float类型,出现精度丢失问题(如29.9变成29.899999)
- 没有给状态+上映日期建联合索引,当需要查询"正在上映的影片"时性能很差
2.3 高并发场景解决方案
座位锁定是系统最复杂的部分,最终方案是:
- 前端:用WebSocket实时推送座位状态变化
- 后端:Redis Lua脚本保证原子性操作
java复制// 简化版的座位锁定脚本
String luaScript = "if redis.call('exists', KEYS[1]) == 0 then " +
" redis.call('set', KEYS[1], ARGV[1]) " +
" redis.call('expire', KEYS[1], ARGV[2]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
- 定时任务:每5分钟清理过期的预锁定(用Redis的keyspace notification)
3. 核心功能实现细节
3.1 购票业务流程
完整的购票流程包含11个状态转换,这里列举关键节点:
- 座位查询 → 2. 座位锁定 → 3. 订单创建 → 4. 支付发起 → 5. 支付回调 → 6. 出票完成
状态机实现采用枚举+策略模式:
java复制public enum OrderStatus {
INIT {
@Override
public boolean canChangeTo(OrderStatus newStatus) {
return newStatus == LOCKED;
}
},
LOCKED {
@Override
public boolean canChangeTo(OrderStatus newStatus) {
return newStatus == PAID || newStatus == TIMEOUT;
}
},
// 其他状态...
}
3.2 支付模块集成
接入了支付宝沙箱环境,关键点在于:
- 使用RSA2签名验证回调请求的真实性
- 处理网络抖动导致的重复回调(通过订单状态+幂等处理)
- 本地事务与第三方支付的协调(用了本地消息表方案)
支付超时控制代码片段:
java复制@Transactional
public void processPaymentTimeout(Long orderId) {
Order order = orderMapper.selectByIdForUpdate(orderId);
if (order.getStatus() != OrderStatus.LOCKED) {
return;
}
order.setStatus(OrderStatus.TIMEOUT);
orderMapper.updateById(order);
// 释放座位
seatService.unlockSeats(order.getShowtimeId(), order.getSeatNumbers());
}
4. 部署与监控方案
4.1 多种部署方式对比
在阿里云学生机上测试了三种部署方案:
| 方式 | 启动时间 | 内存占用 | 适合场景 |
|---|---|---|---|
| 直接jar运行 | 4.8s | 480MB | 开发测试 |
| Docker容器 | 1.2s | 520MB | 生产环境 |
| Jenkins自动化 | 6s | 500MB | 持续集成环境 |
最终选择Docker Compose编排方案,因为:
- 一键启动所有依赖服务(MySQL+Redis)
- 方便资源限制和健康检查
- 与Jenkins流水线无缝集成
4.2 监控配置
用SpringBoot Actuator暴露的端点配合Prometheus+Grafana搭建监控看板,重点监控:
- 订单创建成功率(HTTP状态码分布)
- 座位锁定耗时(Redis操作P99延迟)
- 支付回调处理队列积压情况
application.yml关键配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
distribution:
percentiles:
http.server.requests: 0.5,0.9,0.99
5. 典型问题排查实录
5.1 座位超卖问题
现象:压力测试时出现同一座位被售出两次
排查过程:
- 检查Redis锁实现,发现没有处理客户端超时情况
- 添加心跳机制:前端每30秒续期锁
- 引入版本号校验,防止旧请求覆盖新状态
最终解决方案:
java复制public boolean tryLockSeat(String lockKey, String requestId, int expireTime) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
String value = requestId + "|" + System.currentTimeMillis();
return connection.set(
lockKey.getBytes(),
value.getBytes(),
Expiration.seconds(expireTime),
RedisStringCommands.SetOption.SET_IF_ABSENT
);
});
}
5.2 支付回调丢失
现象:支付宝显示支付成功,但系统未更新订单状态
排查工具:
- 检查Nginx访问日志 grep 'POST /api/payment/callback'
- 发现有些请求返回400错误
- 原来是验签时区配置错误导致时间戳过期
修复方案:
- 添加回调请求日志落盘
- 配置统一的服务器时区
- 实现异步补单任务
6. 项目扩展方向
虽然已经通过答辩,但系统还有改进空间:
- 引入分布式事务:目前跨服务调用(如扣库存和创建订单)还是本地事务
- 增加影院管理端:现在是通过直接操作数据库管理影片排期
- 实现推荐算法:基于用户历史购票记录推荐相关影片
一个实用的技巧:在开发支付相关功能时,一定要准备完整的测试用例矩阵,包括:
- 正常支付流程
- 重复支付处理
- 部分退款场景
- 支付超时与订单恢复
我在项目后期才补全这些用例,导致不得不重构部分核心代码。如果从一开始就采用测试驱动开发(TDD)方式,应该能节省至少30%的调试时间。
