大型赛事门票预订系统一直是体育娱乐行业的技术难点。去年我参与开发某国际羽毛球公开赛的票务系统时,深刻体会到传统购票方式的痛点:高峰期服务器崩溃、选座界面卡顿、支付超时等问题频发。这正是SpringBoot技术栈能大显身手的场景。
这个系统需要解决三个核心问题:
我们采用分层架构设计:
code复制前端:Vue.js + ElementUI
网关:Spring Cloud Gateway
业务层:SpringBoot 2.7 + MyBatis Plus
缓存:Redis Cluster
数据库:MySQL 8.0 + 分库分表
选择SpringBoot的核心考量:
针对开票时的瞬时高峰,我们做了三级防护:
关键代码示例:
java复制@RestController
@Slf4j
public class TicketController {
@RateLimiter(value = 1000, key = "#concertId")
@PostMapping("/lockSeat")
public Result lockSeat(@PathVariable String concertId) {
// 座位锁定逻辑
}
}
采用Canvas+WebSocket实现:
状态同步流程:
code复制用户A选座 → WS消息 → 服务端 → Redis更新 → WS广播 → 其他用户界面更新
订单创建涉及多个微服务:
采用Seata的AT模式解决:
java复制@GlobalTransactional
public void createOrder(OrderDTO dto) {
accountService.debit(dto.getUserId(), dto.getAmount());
ticketService.lockSeats(dto.getShowId(), dto.getSeats());
deliveryService.generateETicket(dto);
}
针对座位查询的慢SQL:
sql复制-- 优化前
SELECT * FROM seats WHERE venue_id=? AND status=0;
-- 优化后
CREATE INDEX idx_venue_status ON seats(venue_id, status)
INCLUDE (row_num, col_num, zone_type);
采用Hash结构存储场馆座位状态:
code复制key: venue:{venueId}:shows:{showId}
field: seat_{row}_{col}
value: JSON(status, price, lockExpire)
设置双重过期策略:
现象:多个用户同时看到空座,提交后只有一人成功
解决方案:
java复制public boolean lockSeat(Long seatId) {
String lockKey = "seat:" + seatId;
return redisTemplate.opsForValue()
.setIfAbsent(lockKey, "locked", 30, TimeUnit.MINUTES);
}
支付回调可能因网络问题延迟,我们设计了:
敏感字段加密处理:
java复制@Column
@Convert(converter = CryptoConverter.class)
private String idCardNumber;
审计日志记录所有关键操作,采用ELK栈实现日志分析。
核心监控项:
通过Nginx流量切分:
code复制location /api/ticket {
proxy_pass http://ticket-service-canary;
proxy_set_header X-Version "canary";
}
配合SpringBoot的Profile实现配置隔离。
这个项目让我深刻体会到,好的票务系统不仅要技术过硬,更要理解业务场景。比如体育赛事和演唱会的选座策略就完全不同——前者注重区域视野,后者追求前排互动。下次如果再开发类似系统,我会提前用AB测试验证不同座位展示方式的转化率。