1. 项目背景与核心价值
小徐影城管理系统是一个典型的影院业务管理解决方案,它解决了传统影院手工排片、纸质票务、人工统计带来的效率瓶颈问题。我在实际开发中发现,这类系统最核心的价值在于将影院日常运营中的三大高频场景数字化:影片信息管理、场次排期优化和票务销售追踪。
这个全栈项目采用SpringBoot+Vue的技术组合并非偶然。后端选用SpringBoot是因为它能够快速整合影院业务所需的各类组件(比如MySQL事务管理、Redis缓存、JWT鉴权),而Vue.js的响应式特性特别适合处理影院选座这类需要高频交互的前端场景。整套系统从技术选型上就充分考虑到了影院业务的实际需求特点。
2. 系统架构设计解析
2.1 技术栈选型依据
后端采用SpringBoot 2.7.x版本,这个选择经过了实际验证:
- 内置Tomcat简化部署
- Starter依赖快速集成MyBatis
- Actuator提供运维监控端点
- 与MySQL 8.0的事务兼容性良好
前端选用Vue 3组合式API开发,主要考虑:
- Element Plus组件库开箱即用
- Axios拦截器统一处理影院API鉴权
- Vuex管理影院全局状态(如用户登录态)
2.2 数据库关键设计
影城系统的MySQL设计有几个需要特别注意的点:
sql复制CREATE TABLE `schedule` (
`id` int NOT NULL AUTO_INCREMENT,
`movie_id` int NOT NULL COMMENT '影片ID',
`hall_id` int NOT NULL COMMENT '影厅ID',
`start_time` datetime NOT NULL COMMENT '开场时间',
`end_time` datetime NOT NULL COMMENT '散场时间',
`price` decimal(10,2) NOT NULL COMMENT '票价',
`status` tinyint DEFAULT '1' COMMENT '1可售 0停售',
PRIMARY KEY (`id`),
KEY `idx_movie` (`movie_id`),
KEY `idx_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
场次表(schedule)的设计要点:
- 建立影片和影厅的双重外键约束
- 时间字段精确到分钟级
- 价格字段使用DECIMAL避免浮点误差
- 联合索引优化查询性能
3. 核心业务模块实现
3.1 动态场次排期算法
影院排片是个典型的约束满足问题,我们实现了智能排片算法:
java复制public List<Schedule> autoArrange(Movie movie, Hall hall, LocalDate date) {
// 获取影片标准时长(含广告)
int duration = movie.getDuration() + 15;
// 计算当日可排时段(早9点至晚11点)
List<TimeSlot> slots = calculateTimeSlots(date);
// 冲突检测规则
Predicate<Schedule> conflictCheck = s ->
!s.getEndTime().isBefore(movie.getStartTime())
&& !s.getStartTime().isAfter(movie.getEndTime());
// 贪心算法排片
return slots.stream()
.filter(slot -> hall.getSchedules().stream()
.noneMatch(conflictCheck))
.map(slot -> new Schedule(movie, hall, slot))
.collect(Collectors.toList());
}
3.2 选座锁座并发控制
票务模块最关键的并发控制方案:
- 使用Redis SETNX实现分布式锁
- 座位状态机设计:
- 0=可售
- 1=锁定中
- 2=已售出
- 前端通过WebSocket实时同步座位状态
java复制@Transactional
public boolean lockSeats(List<Integer> seatIds, Integer userId) {
String lockKey = "seat_lock:" + String.join(",", seatIds);
try {
// 获取分布式锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, userId, 5, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(locked)) {
// 验证座位状态
List<Seat> seats = seatMapper.selectBatchIds(seatIds);
if (seats.stream().anyMatch(s -> s.getStatus() != 0)) {
throw new BusinessException("座位已被占用");
}
// 批量更新状态
seatMapper.updateStatusBatch(seatIds, 1);
return true;
}
return false;
} finally {
// 无论成功失败都释放锁
redisTemplate.delete(lockKey);
}
}
4. 典型问题排查实录
4.1 场次时间重叠BUG
我们曾遇到排片时间冲突的严重问题,排查过程如下:
- 现象:新排场次与已有场次时间重叠
- 排查:
- 检查数据库约束(发现未设置时间约束)
- 验证业务层校验逻辑(发现只校验了开始时间)
- 解决方案:
sql复制ALTER TABLE schedule ADD CONSTRAINT chk_time CHECK (
NOT EXISTS (
SELECT 1 FROM schedule s
WHERE s.hall_id = hall_id
AND s.id != id
AND s.start_time < end_time
AND s.end_time > start_time
)
);
4.2 选座并发超卖问题
压力测试时发现的典型问题:
- 使用JMeter模拟100并发选座
- 出现同一座位被多个用户购买
- 原因:MySQL默认隔离级别不可见其他事务未提交的修改
- 最终方案:
- 升级为Serializable隔离级别
- 配合Redis分布式锁
- 添加数据库唯一索引
5. 部署与运维实践
5.1 生产环境配置要点
application-prod.yml关键配置:
yaml复制spring:
datasource:
url: jdbc:mysql://cluster-mysql:3306/cinema?useSSL=false&serverTimezone=Asia/Shanghai
hikari:
maximum-pool-size: 20
connection-timeout: 30000
redis:
cluster:
nodes: redis-node1:6379,redis-node2:6379,redis-node3:6379
lettuce:
pool:
max-active: 16
5.2 性能优化方案
针对影城系统的特殊优化手段:
- 影片信息缓存:使用Redis Hash存储热门影片
- 场次查询优化:添加covering index
- 票务统计预计算:每日凌晨跑批生成报表
- 前端静态资源:配置Nginx gzip压缩
6. 扩展开发建议
基于现有系统的改进方向:
- 接入第三方支付(微信/支付宝)
- 实现会员积分体系
- 开发数据分析看板
- 增加影院卖品管理模块
这套系统在实际部署时,建议先在小规模影厅试运行两周,重点观察排片算法的合理性和票务模块的并发承载能力。我们在初期就发现影厅清洁时间未被纳入排片计算,后来在算法中增加了30分钟的清场时间缓冲期。