1. 项目背景与核心价值
电影院售票系统从传统柜台走向移动互联网是行业必然趋势。去年帮本地影院做数字化改造时,发现他们还在用Excel排片、手工画座位图,观众购票要现场排队选座。这种低效模式直接导致非黄金时段上座率不足30%,而热门场次又因人工操作慢引发顾客投诉。
我们设计的这套SpringBoot+Vue3+Android原生APP解决方案,实现了三大突破:
- 动态排片管理:影院经理用后台5分钟就能完成一周排期
- 可视化选座:观众像选飞机座位一样直观操作
- 实时数据同步:余票更新延迟控制在200ms内
2. 技术架构设计
2.1 后端SpringBoot服务
采用三层架构设计:
java复制// 核心票务服务示例
@Transactional
public TicketOrder createOrder(OrderDTO dto) {
// 1. 库存预扣减
SeatLock lock = seatService.lockSeats(dto.getShowId(), dto.getSeats());
// 2. 支付预处理
PaymentRequest payment = paymentService.createRequest(
dto.getUserId(),
lock.getTotalAmount()
);
// 3. 生成订单
return orderRepository.save(
new TicketOrder(dto.getUserId(), lock, payment)
);
}
关键配置:
yaml复制# 高并发场景特别配置
spring:
redis:
lettuce:
pool:
max-active: 200 # 应对选座高峰
jpa:
properties:
hibernate:
order_updates: true # 避免死锁
2.2 前端Vue3实现
选座核心逻辑:
vue复制<script setup>
// 使用Canvas渲染座位图
const drawSeats = () => {
const ctx = canvas.value.getContext('2d');
seats.value.forEach(seat => {
ctx.fillStyle = seat.status === 'available' ? '#4CAF50' : '#F44336';
ctx.fillRect(seat.x, seat.y, 30, 30);
});
};
// WebSocket实时更新
const ws = new WebSocket('wss://api.example.com/seats');
ws.onmessage = (event) => {
updateSeats(JSON.parse(event.data));
};
</script>
性能优化点:
- 使用Vue3的shallowRef处理座位数据
- WebSocket心跳间隔设置为25秒
- 座位图采用Canvas而非DOM渲染
2.3 Android端关键技术
混合开发架构:
- 核心功能用Kotlin实现
- 选座模块嵌入Vue3 WebView
kotlin复制class SeatActivity : AppCompatActivity() {
private lateinit var binding: ActivitySeatBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_seat)
binding.webView.apply {
settings.javaScriptEnabled = true
loadUrl("https://m.example.com/seats")
}
}
}
3. 核心业务实现
3.1 排片管理
数据库设计:
sql复制CREATE TABLE schedule (
id BIGINT PRIMARY KEY,
movie_id BIGINT,
hall_id INT,
start_time DATETIME,
end_time DATETIME,
price DECIMAL(10,2),
INDEX idx_hall_time (hall_id, start_time) -- 防止场次冲突
);
排片冲突检测算法:
java复制public boolean checkScheduleConflict(Schedule newSchedule) {
return scheduleRepository.exists(
hallId == newSchedule.getHallId()
&& (
(startTime <= newSchedule.getStartTime() && endTime > newSchedule.getStartTime())
|| (startTime < newSchedule.getEndTime() && endTime >= newSchedule.getEndTime())
)
);
}
3.2 选座锁定机制
分布式锁实现:
java复制public SeatLock lockSeats(Long showId, List<String> seatNos) {
String lockKey = "lock:" + showId;
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 5, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(locked)) {
// 执行座位锁定逻辑
return doLockSeats(showId, seatNos);
}
throw new BusyException("系统繁忙,请稍后重试");
} finally {
// 确保释放自己的锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
3.3 支付对账流程
mermaid复制graph TD
A[用户支付] --> B{支付成功?}
B -->|是| C[更新订单状态]
C --> D[释放座位锁]
B -->|否| E[通知用户]
D --> F[生成观影凭证]
4. 性能优化实战
4.1 座位数据缓存
采用二级缓存策略:
- Redis缓存热点场次座位数据
- 本地缓存最近查询记录
java复制@Cacheable(value = "seats", key = "#showId")
public List<Seat> getSeats(Long showId) {
// 数据库查询
return seatRepository.findByShowId(showId);
}
4.2 订单分库分表
按用户ID哈希分片:
java复制// 分片键计算
public String determineCurrentLookupKey() {
Long userId = UserContext.getCurrentUserId();
return "order_db_" + (userId % 4);
}
4.3 前端性能指标
优化前后对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首屏加载 | 2.8s | 1.2s |
| 选座响应延迟 | 500ms | 120ms |
| 内存占用 | 85MB | 42MB |
5. 踩坑实录
5.1 WebView白屏问题
现象:Android端偶尔加载不出选座页面
解决方案:
kotlin复制webView.settings.apply {
domStorageEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
}
5.2 座位锁失效
场景:高并发下出现超卖
改进方案:
- 改用Redisson分布式锁
- 添加数据库乐观锁
sql复制UPDATE seats SET status='locked'
WHERE id IN (1,2,3) AND status='available'
5.3 支付回调重复
问题:第三方支付可能重复通知
防护措施:
java复制@Transactional
public void handlePaymentNotify(String orderNo) {
Order order = orderRepository.findByOrderNoForUpdate(orderNo);
if (order.getStatus() != OrderStatus.UNPAID) {
return; // 已处理直接返回
}
// 正常处理逻辑
}
6. 安全防护要点
- 选座API防刷:
java复制@RateLimiter(value = 10, key = "#userId")
public List<Seat> querySeats(Long userId, Long showId) {
// 查询逻辑
}
- 观影凭证防伪:
- 采用RSA签名
- 动态二维码有效期15分钟
- 敏感数据脱敏:
java复制public String getMaskedPhone(String phone) {
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
7. 监控体系搭建
关键监控指标:
- 座位锁定成功率
- 支付回调延迟
- API响应时间P99
Prometheus配置示例:
yaml复制- pattern: '/api/orders'
metrics:
- name: order_api_duration
help: Order API latency
type: histogram
labels:
method: $1
status: $2
8. 项目演进方向
- 智能推荐:
- 基于用户历史推荐场次
- 好友连座功能
- 影院运营:
- 动态定价算法
- 空场次自动调整
- 用户体验:
- AR影院导航
- 开场前提醒
这套系统在本地影院上线后,非黄金时段上座率提升到65%,人工售票窗口减少60%,观众投诉率下降82%。特别提醒:Android端WebView兼容性问题需要持续关注,建议建立版本兼容性矩阵表。
