电影院购票系统是典型的O2O电商场景,需要同时处理高并发选座请求和复杂的场次排期逻辑。这个基于SpringBoot+Vue的全栈项目,完整实现了从影片管理、排期设置到在线选座、支付结算的闭环流程。作为Java Web方向的毕业设计选题,它既涵盖了CRUD基础操作,又涉及分布式锁、事务管理等进阶知识点,能够全面展示候选人的技术栈深度。
我在实际开发中发现,这类系统最关键的难点在于座位状态的实时同步——既要保证用户选座时看到最新数据,又要防止超卖。传统的AJAX轮询方案在影院场景下会产生大量无效请求,而本项目采用WebSocket+Redis的分布式锁方案,将座位状态变更的延迟控制在200ms以内。
SpringBoot 2.7作为基础框架,其自动配置特性大幅简化了MyBatis、Redis等组件的集成。数据层采用多数据源配置:
java复制// 多数据源配置示例
@Configuration
@MapperScan(basePackages = "com.cinema.mapper.mysql", sqlSessionTemplateRef = "mysqlTemplate")
public class MysqlDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
}
Vue 3组合式API配合TypeScript强化类型检查,关键组件设计:
typescript复制// 座位状态同步逻辑
const ws = new WebSocket('wss://api.example.com/seats')
ws.onmessage = ({ data }) => {
const payload = JSON.parse(data)
if(payload.type === 'LOCK') {
store.commit('updateSeatStatus', {
seatId: payload.seatId,
status: 'locking'
})
}
}
采用Redis的Hash结构存储影厅座位状态,通过SETNX实现分布式锁:
code复制HMSET hall:1
"A1" "available"
"A2" "locked:user123"
"B5" "sold"
关键操作步骤:
lua复制if redis.call('HGET', KEYS[1], ARGV[1]) == 'available' then
return redis.call('HSET', KEYS[1], ARGV[1], 'locked:'..ARGV[2])
end
注意:必须用Lua脚本保证操作的原子性,普通的多命令操作会导致竞态条件
避免使用数据库自增ID,采用以下规则生成订单号:
code复制影院代码(2位) + 年月日(8位) + Redis原子计数器(6位)
通过Redis的INCR命令保证分布式环境下ID唯一:
java复制public String generateOrderNo(String cinemaCode) {
String date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
Long seq = redisTemplate.opsForValue().increment("order:seq:" + date);
return String.format("%s%s%06d", cinemaCode, date, seq);
}
sql复制CREATE TABLE `schedule` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`movie_id` BIGINT 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;
订单表按影院ID分表存储,通过ShardingSphere实现路由:
yaml复制spring:
shardingsphere:
sharding:
tables:
order:
actual-data-nodes: ds.order_$->{0..9}
table-strategy:
inline:
sharding-column: cinema_id
algorithm-expression: order_$->{cinema_id % 10}
采用OpenAPI 3.0标准,关键接口示例:
yaml复制/api/seats/lock:
post:
tags: [Seat]
summary: 锁定座位
parameters:
- $ref: '#/components/parameters/X-User-ID'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
scheduleId:
type: integer
seatNos:
type: array
items: string
responses:
200:
description: 锁定成功
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
在application.yml中配置Tomcat连接池:
yaml复制server:
tomcat:
max-threads: 200
min-spare-threads: 20
max-connections: 1000
accept-count: 100
connection-timeout: 5000
WebSocket连接失败:
proxy_set_header Upgrade $http_upgrade跨域问题处理:
java复制@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
演示重点准备:
技术深挖方向:
项目扩展建议:
在开发过程中,我特别建议在座位选择模块加入压力测试。使用JMeter模拟500并发请求时,发现直接操作数据库的方案QPS仅能到120左右,而引入Redis缓存后提升到2300+。但要注意缓存一致性问题——我们在更新数据库后,会通过Redis Pub/Sub通知所有节点清除缓存