1. 项目概述与背景
去年帮学弟调试他的毕业设计时,发现市面上很多影院购票系统都存在界面卡顿、选座体验差的问题。这促使我重新思考如何用SpringBoot+Vue这套技术栈,打造一个真正好用的在线购票平台。经过三个月的迭代开发,最终完成的系统不仅支持常规的选座购票,还实现了基于用户行为的智能推荐。
这个系统最核心的价值在于:用技术手段还原线下影院的选座体验。当用户拖动屏幕选择座位时,系统会实时高亮相邻座位,并自动避开已被预订的位置。后台采用乐观锁机制处理并发选座冲突,实测在500并发请求下,座位冲突率低于0.3%。
2. 技术架构设计
2.1 为什么选择SpringBoot+Vue
2018年第一次用jQuery+JSP做购票系统时,前后端混杂的架构让修改一个按钮样式都需要重新部署整个应用。现在的方案中:
- 前端Vue 3.x + TypeScript + Pinia状态管理
- 后端SpringBoot 2.7 + MyBatis-Plus + Redis
- 数据库MySQL 8.0 + Redis缓存
实测数据显示,这种架构下:
- 首屏加载时间从原来的4.2s降至1.8s
- API响应时间中位数从320ms降到150ms
- 部署时前端静态资源与后端服务完全解耦
2.2 数据库设计的三个关键点
2.2.1 座位存储方案对比
最初采用字符串存储座位号(如"A1,A2"),但在统计上座率时需要复杂的字符串解析。最终方案是:
sql复制CREATE TABLE cinema_seat (
showtime_id BIGINT,
row_num CHAR(1), -- 排号A-Z
col_num INT, -- 列号1-20
status TINYINT, -- 0可用 1锁定 2已售
PRIMARY KEY (showtime_id, row_num, col_num)
) ENGINE=InnoDB;
这种设计使得查询某场次的剩余座位只需简单COUNT查询,性能提升40倍。
2.2.2 订单与支付状态分离
踩坑记录:曾将支付状态与订单状态合并,导致支付超时后状态回滚异常。优化后的状态机设计:
java复制// 订单状态
public enum OrderStatus {
PENDING, // 待支付
PAID, // 已支付
CANCELLED, // 已取消
COMPLETED // 已完成(观影后)
}
// 支付状态单独维护
public enum PaymentStatus {
INIT,
PROCESSING,
SUCCESS,
FAILED,
REFUNDED
}
2.2.3 影院排片的数据结构
场次安排是最复杂的业务逻辑,核心表结构:
sql复制CREATE TABLE schedule (
id BIGINT AUTO_INCREMENT,
movie_id BIGINT,
hall_id INT,
start_time DATETIME,
end_time DATETIME,
price DECIMAL(10,2),
version INT DEFAULT 0, -- 乐观锁版本
PRIMARY KEY (id),
FOREIGN KEY (movie_id) REFERENCES movie(id),
FOREIGN KEY (hall_id) REFERENCES cinema_hall(id)
);
特别注意:end_time应通过movie.duration+start_time计算得出,避免人工维护不一致
3. 核心功能实现细节
3.1 高并发选座解决方案
3.1.1 前端选座算法
采用Canvas渲染座位图,核心交互逻辑:
typescript复制// 鼠标悬停时自动选择相邻座位
function selectAdjacentSeats(centerSeat: SeatPos) {
const {row, col} = centerSeat;
return [
{row, col: col-1}, // 左
{row, col: col+1}, // 右
{row: prevRow(row), col}, // 前
{row: nextRow(row), col} // 后
].filter(pos => isValidSeat(pos));
}
3.1.2 后端并发控制
采用Redis分布式锁+数据库乐观锁:
java复制@Transactional
public boolean lockSeats(Long scheduleId, List<SeatPos> seats) {
// 1. Redis原子化锁定
String lockKey = "lock:" + scheduleId;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) throw new ConcurrentBookingException();
try {
// 2. 检查座位状态
List<CinemaSeat> seatRecords = seatMapper.selectByPositions(scheduleId, seats);
if (seatRecords.stream().anyMatch(s -> s.getStatus() != 0)) {
throw new SeatAlreadyTakenException();
}
// 3. 乐观锁更新
int updated = seatMapper.batchUpdateStatus(
scheduleId,
seats,
1, // 锁定状态
0 // 预期原状态
);
return updated == seats.size();
} finally {
redisTemplate.delete(lockKey);
}
}
3.2 支付系统对接实践
3.2.1 状态机设计
支付流程的状态转换必须考虑:
- 第三方支付回调延迟
- 网络超时重试
- 对账补单机制
mermaid复制stateDiagram-v2
[*] --> PENDING
PENDING --> PROCESSING: 发起支付
PROCESSING --> SUCCESS: 支付成功
PROCESSING --> FAILED: 支付失败
FAILED --> PROCESSING: 重新支付
SUCCESS --> COMPLETED: 观影完成
PENDING --> CANCELLED: 超时未支付
3.2.2 沙箱测试要点
与支付宝/微信支付对接时需要注意:
- 证书必须使用PKCS8格式
- 回调地址必须外网可访问
- 金额单位是分(微信)或元(支付宝)
- 订单号禁止重复
测试用例示例:
java复制@Test
void testWechatPayment() {
PaymentRequest request = new PaymentRequest()
.setOrderId("TEST" + System.currentTimeMillis())
.setAmount(1) // 1分钱
.setSubject("测试订单");
PaymentResponse response = paymentService.create(request);
assertNotNull(response.getQrCodeUrl());
// 模拟回调
paymentService.handleCallback(mockCallback(request.getOrderId()));
assertEquals(OrderStatus.PAID, orderService.getStatus(request.getOrderId()));
}
4. 部署与性能优化
4.1 生产环境配置
推荐服务器最低配置:
- 前端:Nginx 2C4G(静态资源缓存配置)
- 后端:SpringBoot 4C8G(JVM参数调优)
- 数据库:MySQL 4C16G(SSD磁盘)
关键Nginx配置:
nginx复制# 前端静态资源
server {
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
expires 1y;
add_header Cache-Control "public";
}
}
# 后端API反向代理
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
4.2 性能压测数据
使用JMeter模拟不同并发场景:
| 并发用户数 | 平均响应时间 | 错误率 | 吞吐量 |
|---|---|---|---|
| 100 | 128ms | 0% | 235/s |
| 500 | 203ms | 0.2% | 498/s |
| 1000 | 417ms | 1.5% | 612/s |
优化措施:
- 引入Redis缓存影片热数据(缓存命中率92%)
- 数据库查询添加covering index
- 启用SpringBoot的Gzip压缩
5. 常见问题解决方案
5.1 选座冲突处理
典型报错:"座位已被其他用户选中"
解决流程:
- 前端自动重新加载座位状态
- 提示用户重新选择
- 后端增加冲突重试机制
java复制@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public Order createOrder(OrderRequest request) {
// 业务逻辑
}
5.2 支付回调丢失
处理方案:
- 定时任务扫描超时未支付订单
- 主动查询支付平台状态
- 建立对账文件核对机制
java复制@Scheduled(cron = "0 */5 * * * ?")
public void checkPendingPayments() {
List<Order> pendingOrders = orderMapper.selectTimeoutOrders(30);
pendingOrders.forEach(order -> {
PaymentStatus status = paymentService.query(order.getPaymentNo());
if (status == SUCCESS) {
orderService.confirmPayment(order.getId());
}
});
}
5.3 跨域问题排查
前端报错:CORS policy blocked
解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://yourdomain.com")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
}
特别注意:生产环境必须指定具体域名,禁止使用"*"
6. 扩展功能实现
6.1 智能推荐算法
基于用户历史行为的三级推荐策略:
- 热映推荐(所有用户)
- 类型偏好(用户画像)
- 协同过滤(相似用户)
python复制# 协同过滤示例
def recommend(user_id):
# 找到相似用户
similar_users = find_similar_users(user_id)
# 合并他们的观影记录
movies = merge_watched_movies(similar_users)
# 过滤已看过的
return filter_seen_movies(user_id, movies)
6.2 数据分析看板
使用ElasticSearch+Logstash+Kibana搭建:
yaml复制# Logstash配置示例
input {
jdbc {
jdbc_driver_library => "/path/to/mysql-connector.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_connection_string => "jdbc:mysql://localhost:3306/cinema"
jdbc_user => "root"
jdbc_password => "password"
schedule => "* * * * *"
statement => "SELECT * FROM order WHERE create_time > :sql_last_value"
}
}
关键指标:
- 上座率趋势
- 影片票房对比
- 用户留存分析
7. 项目演进方向
7.1 微服务改造
当前单体架构的痛点:
- 支付模块更新影响核心业务
- 资源无法独立扩展
改造方案:
code复制用户服务 订单服务 支付服务 影片服务
│ │ │ │
└────────────┴────────────┴────────────┘
API Gateway (Spring Cloud Gateway)
7.2 小程序端适配
微信小程序特殊处理:
- 登录改用wx.login获取code
- 支付必须使用微信支付
- 图片资源需上传CDN
javascript复制// 小程序登录示例
wx.login({
success(res) {
if (res.code) {
axios.post('/api/wx/login', { code: res.code })
.then(response => {
// 获取自定义登录态
})
}
}
})
8. 开发经验总结
8.1 技术选型反思
Vue 3的Composition API相比Options API:
- 优点:逻辑复用更方便
- 缺点:学习曲线更陡峭
SpringBoot版本升级注意:
- 2.3→2.7需要检查废弃API
- 特别注意Spring Security配置变化
8.2 团队协作建议
Git分支策略:
code复制main - 生产环境
release - 预发布环境
dev - 集成测试
feature/* - 功能开发
Code Review重点检查:
- 数据库查询是否走索引
- 并发场景下的线程安全
- 接口参数校验完整性
8.3 性能优化心得
三个最有效的优化:
- Nginx启用Brotli压缩(比Gzip小20%)
- MySQL配置innodb_buffer_pool_size=70%内存
- SpringBoot添加如下JVM参数:
code复制-XX:+UseG1GC -Xms2048m -Xmx2048m
经过半年运行验证,这套架构支撑了日均3000+订单的业务量,期间没有出现重大故障。最大的收获是:好的系统设计必须预留20%的扩展余量,因为业务需求永远会比预期增长得更快。