作为一名经历过多个企业级项目开发的老码农,今天想和大家分享一个基于SpringBoot+Vue的影院购票系统的完整实现方案。这个系统是我去年带队为某连锁影院集团开发的线上票务平台,上线后日均订单量稳定在5000+,经受住了节假日高峰期的流量考验。
这个系统最核心的价值在于:通过前后端分离架构,实现了影院业务的全面数字化管理。后端采用SpringBoot+MyBatis+MySQL技术栈保证系统稳定性和扩展性,前端使用Vue.js+ElementUI构建响应式界面,让用户在任何设备上都能流畅完成购票操作。下面我就从架构设计、核心功能实现到部署运维,详细拆解这个项目的技术要点。
提示:本文所有代码示例和配置都经过生产环境验证,可以直接用于你的项目。但要注意根据实际业务需求调整参数,特别是数据库连接池和缓存配置。
在项目启动阶段,我们花了2周时间进行技术评估。最终确定的方案如下:
后端技术栈:
前端技术栈:
选择这套组合主要基于以下考虑:
系统采用经典的三层架构,但针对影院业务做了特殊优化:
code复制com.cinema
├── config # 配置层(安全、Swagger等)
├── controller # 表现层(REST API)
├── service # 业务层(核心逻辑)
│ ├── impl # 实现类
│ └── task # 定时任务
├── dao # 持久层(MyBatis Mapper)
├── entity # 实体类(与DB表对应)
├── dto # 数据传输对象
├── vo # 视图对象
├── util # 工具类
└── exception # 异常处理
特别说明几个关键设计:
采用JWT+Spring Security实现安全的认证流程:
java复制// 安全配置核心代码
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
关键实现细节:
java复制@PreAuthorize("hasAnyRole('ADMIN', 'CINEMA_MANAGER')")
@PostMapping("/movies")
public Result addMovie(@Valid @RequestBody MovieDTO dto) {
//...
}
排片功能需要考虑影院影厅、时间段冲突等业务规则:
java复制public class ScheduleServiceImpl implements ScheduleService {
@Transactional
public void addSchedule(ScheduleDTO dto) {
// 检查影厅时间冲突
Integer count = scheduleMapper.checkConflict(
dto.getHallId(),
dto.getStartTime(),
dto.getEndTime());
if (count > 0) {
throw new BusinessException("该影厅时间段已被占用");
}
// 生成场次
Schedule schedule = new Schedule();
BeanUtils.copyProperties(dto, schedule);
scheduleMapper.insert(schedule);
// 初始化座位状态
initSeats(schedule.getId(), dto.getHallId());
}
private void initSeats(Long scheduleId, Long hallId) {
List<Seat> seats = seatMapper.selectByHall(hallId);
List<ScheduleSeat> scheduleSeats = seats.stream()
.map(seat -> new ScheduleSeat(scheduleId, seat.getId(), SeatStatus.AVAILABLE))
.collect(Collectors.toList());
scheduleSeatMapper.batchInsert(scheduleSeats);
}
}
避坑指南:
这是系统最复杂的模块,需要考虑高并发下的座位锁定:
java复制public class OrderServiceImpl implements OrderService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Transactional
public OrderVO createOrder(OrderDTO dto) {
// 1. 校验座位是否可用(Redis原子操作)
String lockKey = "schedule:" + dto.getScheduleId();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "locked", 30, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("当前有其他用户正在选座,请稍后重试");
}
try {
// 2. 检查座位状态
List<ScheduleSeat> seats = scheduleSeatMapper.selectByIds(dto.getSeatIds());
if (seats.stream().anyMatch(s -> s.getStatus() != SeatStatus.AVAILABLE)) {
throw new BusinessException("所选座位已被占用");
}
// 3. 创建订单
Order order = new Order();
// ...订单数据组装
orderMapper.insert(order);
// 4. 更新座位状态
scheduleSeatMapper.batchUpdateStatus(dto.getSeatIds(), SeatStatus.LOCKED);
// 5. 发送延迟消息(15分钟未支付自动释放座位)
rabbitTemplate.convertAndSend(
"order.delay.exchange",
"order.delay.key",
order.getId(),
message -> {
message.getMessageProperties()
.setDelay(15 * 60 * 1000); // 15分钟
return message;
});
return convertToVO(order);
} finally {
redisTemplate.delete(lockKey);
}
}
}
高并发处理技巧:
根据项目描述中的表结构,我们在生产环境中做了以下优化:
用户表(user)新增字段:
sql复制ALTER TABLE user ADD COLUMN `avatar` VARCHAR(255) COMMENT '头像URL';
ALTER TABLE user ADD COLUMN `member_level` TINYINT DEFAULT 0 COMMENT '会员等级';
订单表(order)优化索引:
sql复制CREATE INDEX idx_user_id ON `order` (user_id);
CREATE INDEX idx_session_id ON `order` (session_id);
CREATE INDEX idx_order_time ON `order` (order_time);
当订单量超过500万时,我们实施了分表方案:
java复制// 订单分表路由(按用户ID取模)
public class OrderTableSharding implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> shardingValue) {
long userId = shardingValue.getValue();
return "order_" + (userId % 4);
}
}
分表后查询注意事项:
生产环境使用Docker Swarm编排服务:
yaml复制version: '3.8'
services:
cinema-app:
image: registry.example.com/cinema:1.0.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql-master:3306/cinema
- REDIS_HOST=redis-sentinel
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
mysql-master:
image: mysql:8.0
volumes:
- mysql-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=yourstrongpassword
- MYSQL_DATABASE=cinema
redis-sentinel:
image: redis:7.0
command: redis-server --sentinel
我们采用Prometheus+Grafana实现全方位监控:
在项目开发过程中,我们遇到了几个典型问题:
问题1:座位锁定并发冲突
java复制// 乐观锁更新
UPDATE schedule_seat
SET status = 'LOCKED', version = version + 1
WHERE id IN (1,2,3) AND version = #{oldVersion}
问题2:订单超时处理延迟
properties复制spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.max-concurrency=10
问题3:影片详情页加载慢
这个项目让我深刻体会到,一个好的影院系统不仅需要扎实的技术实现,更要深入理解影院业务场景。比如节假日排片策略、会员积分规则、退改签政策等,都需要在技术方案中灵活支持。