1. 项目概述
这个基于SpringBoot和SSM框架的影院票务管理系统,是我去年为一个本地连锁影院开发的线上购票平台。系统上线后帮助影院实现了日均3000+订单的线上转化,将窗口排队时间缩短了65%。对于中小型影院而言,这种轻量级解决方案比商业软件节省了80%的运维成本。
核心功能覆盖了从排片管理、在线选座到支付核销的全流程。特别在高峰时段,系统需要处理每分钟200+的并发请求,这对SSM框架的会话管理和SpringBoot的自动配置提出了挑战。接下来我会重点解析几个关键技术实现方案。
2. 技术架构设计
2.1 框架选型对比
当初在技术选型时,我们对比了三种方案:
- 纯SSM架构(Spring+SpringMVC+MyBatis)
- SpringBoot单体架构
- SpringBoot+SSM混合架构
最终选择第三种方案主要基于:
- 影院业务存在大量传统JDBC操作(如座位状态实时更新)
- 需要快速实现OAuth2.0认证(SpringBoot优势)
- 历史票务数据迁移需要MyBatis的灵活SQL
具体技术栈组合如下:
// 核心POM依赖 <dependencies> <!-- SpringBoot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SSM集成 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- 影院特色组件 --> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>4.1.0</version> </dependency> </dependencies>2.2 高并发场景设计
针对售票高峰期的并发问题,我们采用三级缓冲策略:
- 前端层:使用Vue.js实现座位状态轮询(3秒间隔)
- 服务层:通过Redis缓存影院排片数据(TTL 5分钟)
- 数据层:MySQL采用行级锁+乐观锁机制
关键Redis配置示例:
# application-redis.properties spring.redis.timeout=3000 spring.redis.jedis.pool.max-active=200 spring.redis.jedis.pool.max-wait=10003. 核心业务实现
3.1 动态座位锁定机制
这是系统最复杂的业务逻辑,需要处理:
- 座位状态实时同步
- 15分钟未支付自动释放
- 不同影厅的座位图差异
我们设计的状态机模型如下:
public enum SeatStatus { AVAILABLE(0), // 可售 LOCKED(1), // 锁定中 OCCUPIED(2), // 已售出 MAINTENANCE(3); // 维修中 // 状态转换校验逻辑 public boolean canTransferTo(SeatStatus newStatus) { switch(this) { case AVAILABLE: return newStatus == LOCKED; case LOCKED: return newStatus == OCCUPIED || newStatus == AVAILABLE; default: return false; } } }3.2 支付对接方案
支持三种支付方式:
- 微信支付(占比78%)
- 支付宝(20%)
- 影院会员卡(2%)
以微信支付为例的关键时序:
- 前端生成预支付订单
- 后台调用微信统一下单API
- 异步接收支付结果通知
- 更新订单状态并释放座位
重要提示:支付回调接口必须做签名验证和幂等处理,我们曾因未做幂等导致重复核销座位。
4. 性能优化实践
4.1 MyBatis二级缓存陷阱
初期直接启用MyBatis二级缓存导致数据不一致:
<!-- 错误的缓存配置 --> <cache eviction="LRU" size="1024"/>优化方案:
- 对静态数据(如影院信息)启用缓存
- 动态数据(座位状态)采用手动缓存控制
- 添加@CacheEvict注解保证数据一致性
4.2 Tomcat参数调优
通过JMeter压测发现的瓶颈及解决方案:
| 参数 | 默认值 | 优化值 | 效果提升 |
|---|---|---|---|
| maxThreads | 200 | 500 | +45% |
| acceptCount | 100 | 300 | +32% |
| connectionTimeout | 20000 | 8000 | +18% |
配置示例:
server.tomcat.max-threads=500 server.tomcat.accept-count=3005. 部署与监控
5.1 Jenkins自动化部署
我们的CI/CD流程包含:
- 代码提交触发Git Hook
- SonarQube静态代码分析
- 多环境打包(dev/test/prod)
- Docker容器化部署
关键Jenkinsfile片段:
stage('Deploy to Test') { steps { sh 'mvn clean package -DskipTests' sshPublisher( publishers: [ sshPublisherDesc( configName: 'test-server', transfers: [ sshTransfer( sourceFiles: 'target/*.jar', removePrefix: 'target', remoteDirectory: '/opt/cinema' ) ] ) ] ) } }5.2 SpringBoot Admin监控
监控看板重点关注:
- 线程池活跃度
- JVM内存使用
- 数据库连接池状态
- 订单创建速率
配置示例:
@Configuration @EnableAdminServer public class AdminConfig { @Bean public MailNotifier mailNotifier() { return new MailNotifier(Collections.emptyList()); } }6. 踩坑实录
日期格式化问题:
- 现象:跨天排片显示异常
- 原因:未统一时区配置
- 修复:在Application中强制设置时区
@PostConstruct void started() { TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); }微信证书加载失败:
- 现象:生产环境支付异常
- 原因:证书路径使用了绝对路径
- 修复:改为classpath资源加载
InputStream certStream = getClass().getResourceAsStream("/cert/apiclient_cert.p12");MyBatis分页插件冲突:
- 现象:PageHelper失效
- 原因:同时引入了多个分页插件
- 修复:排除冲突依赖
<exclusions> <exclusion> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> </exclusion> </exclusions>
这个项目让我深刻体会到,即使是常规的票务系统,在高并发场景下也会遇到各种意外问题。建议在开发类似系统时,尽早进行全链路压测,我们就是在第三次压测时才发现了座位锁定的临界区问题。现在回看,如果当时采用分布式锁方案可能会更稳妥,但考虑到中小影院的实际服务器配置,现在的方案在成本和性能之间取得了不错的平衡。