1. 项目概述
电影院售票系统是典型的B/S架构商业应用,采用SSM(Spring+SpringMVC+MyBatis)框架组合开发。这个毕设项目完整实现了从影片管理、场次排期到在线选座、支付结算的全流程业务闭环。我在实际开发中发现,这类系统虽然业务逻辑相对明确,但在高并发座位锁定、分布式事务处理等场景存在典型的技术挑战。
对于计算机专业毕业生而言,这个项目能全面锻炼三层架构设计能力。前端采用主流的Bootstrap+jQuery组合,后端基于Maven构建,数据库选用MySQL 5.7版本。系统包含6个核心模块:用户管理、影片管理、放映厅管理、排片管理、订单管理以及统计报表模块。
提示:建议使用JDK8+Tomcat9+MySQL5.7环境组合,这是经过实测最稳定的版本搭配。高版本JDK可能遇到Lombok插件兼容性问题。
2. 技术架构解析
2.1 SSM框架选型考量
Spring框架的IoC容器管理所有Bean的生命周期,通过声明式事务管理(@Transactional)确保售票过程的数据一致性。SpringMVC采用RESTful风格设计接口,MyBatis的动态SQL特性灵活处理多条件查询。相比SSH框架,SSM组合更轻量且更适合Web应用开发。
关键配置示例:
<!-- MyBatis分页插件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <value> helperDialect=mysql reasonable=true </value> </property> </bean> </array> </property> </bean>2.2 数据库设计要点
影院系统的ER图核心包含以下表:
- 影片表(movie):包含上映状态、时长、类型等字段
- 放映厅表(hall):记录座位行列数和厅类型
- 场次表(schedule):关联影片和放映厅,含放映时间
- 座位表(seat):标记行列位置及锁定状态
- 订单表(order):采用流水号+用户ID复合主键
注意:场次表与座位表建议使用逻辑外键而非物理外键,便于水平分库扩展。订单表需要添加create_time索引优化查询性能。
3. 核心业务实现
3.1 座位锁定机制
采用Redis分布式锁防止超卖,关键代码逻辑:
public boolean lockSeats(List<Integer> seatIds, Integer scheduleId) { String lockKey = "lock:" + scheduleId; // 获取分布式锁(设置3秒过期防止死锁) boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS); if (!locked) return false; try { // 检查座位是否可用 List<Seat> seats = seatMapper.selectByIds(seatIds); if (seats.stream().anyMatch(s -> s.getStatus() != 0)) { return false; } // 更新座位状态为锁定(1) seatMapper.updateStatus(seatIds, 1); return true; } finally { redisTemplate.delete(lockKey); } }3.2 支付超时处理
通过Spring Task实现定时任务,扫描超过15分钟未支付的订单:
@Scheduled(cron = "0 */5 * * * ?") public void cancelExpiredOrders() { List<Order> orders = orderMapper.selectExpiredOrders(); orders.forEach(order -> { // 释放座位 seatMapper.batchUnlock(order.getSeatIds()); // 更新订单状态 orderMapper.updateStatus(order.getId(), OrderStatus.CANCELLED); }); }4. 典型问题解决方案
4.1 并发冲突处理
问题现象:多个用户同时选择相同座位时出现数据不一致 解决方案:
- 数据库层面添加乐观锁版本号
<update id="updateSeatStatus"> UPDATE seat SET status = #{newStatus}, version = version + 1 WHERE id = #{id} AND version = #{version} </update>- 前端采用WebSocket实时推送座位状态变化
- 后端接口添加@CacheEvict注解保证缓存一致性
4.2 性能优化记录
通过Jmeter压测发现的问题及改进:
- 场次查询接口响应慢 → 添加schedule表的联合索引
- 座位状态检查频繁访问数据库 → 引入Redis缓存
- 报表统计耗时 → 使用定时任务预生成数据
优化前后对比(100并发):
| 接口名称 | 原响应时间(ms) | 优化后(ms) |
|---|---|---|
| 查询场次列表 | 1200 | 350 |
| 锁定座位 | 800 | 250 |
| 生成日报表 | 5000 | 300 |
5. 部署与扩展建议
5.1 生产环境部署
推荐使用Docker Compose编排服务:
version: '3' services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: 123456 volumes: - ./mysql/data:/var/lib/mysql redis: image: redis:6 ports: - "6379:6379" app: build: . ports: - "8080:8080" depends_on: - mysql - redis5.2 后续扩展方向
- 接入第三方支付(微信/支付宝)SDK
- 增加会员积分系统
- 实现分布式部署方案(Nginx+SpringCloud)
- 开发微信小程序端
我在开发过程中深刻体会到,事务的边界划分直接影响系统可靠性。例如选座-创建订单-支付应该拆分为独立的事务单元,避免长事务阻塞。另外,Lombok的@Builder注解虽然方便,但在继承场景下需要特别处理父类字段。