markdown复制## 1. 项目背景与核心需求
演唱会订票系统作为典型的在线票务平台,需要解决高并发抢票、座位锁定、支付时效等核心问题。这个Java毕业设计项目完整实现了从选座到出票的全流程,采用B/S架构降低客户端部署成本。我在实际开发中发现,这类系统最关键的三个技术难点在于:
1. 座位状态的实时同步(避免超卖)
2. 订单的时效性控制(防止占座不付款)
3. 支付结果的一致性保证(资金对账)
系统采用SpringBoot+MyBatis主流技术栈,前端使用Thymeleaf模板引擎。数据库设计上特别设置了seat_lock表处理选座锁定,通过status字段的4种状态(0可售/1锁定/2已售/3维修)实现座位状态机管理。
## 2. 系统架构设计解析
### 2.1 技术选型依据
选择SpringBoot而非传统SSM框架主要基于:
- 内嵌Tomcat简化部署(特别适合毕业设计演示)
- 自动配置减少XML编写(开发效率提升40%以上)
- Actuator端点方便监控(演示时能直观展示健康状态)
数据库选用MySQL 5.7而非8.0版本,因为:
- 学校实验室环境普遍支持5.7
- 事务隔离级别REPEATABLE-READ已满足需求
- 避免新版本可能出现的驱动兼容问题
### 2.2 核心表结构设计
```sql
CREATE TABLE `seat_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`seat_id` varchar(32) NOT NULL COMMENT '座位编码',
`show_id` bigint(20) NOT NULL COMMENT '演出场次ID',
`lock_time` datetime NOT NULL COMMENT '锁定时间',
`expire_time` datetime NOT NULL COMMENT '过期时间',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_seat_show` (`seat_id`,`show_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这个设计的关键点在于:
java复制@Transactional
public Result lockSeat(Long userId, String seatId, Long showId) {
// 检查座位是否可售
SeatLock lock = seatLockMapper.selectBySeatAndShow(seatId, showId);
if (lock != null && lock.getStatus() != 0) {
return Result.error("该座位已被锁定");
}
// 创建锁定记录
SeatLock newLock = new SeatLock();
newLock.setSeatId(seatId);
newLock.setShowId(showId);
newLock.setUserId(userId);
newLock.setLockTime(new Date());
newLock.setExpireTime(DateUtils.addMinutes(new Date(), 15)); // 15分钟支付时限
newLock.setStatus(1);
seatLockMapper.insert(newLock);
// 更新座位状态缓存
redisTemplate.opsForValue().set(
"seat:" + showId + ":" + seatId,
"locked",
15, TimeUnit.MINUTES);
return Result.success();
}
关键注意事项:
使用枚举实现订单状态流转:
java复制public enum OrderStatus {
UNPAID(0, "待支付") {
@Override
public boolean canChangeTo(OrderStatus status) {
return status == PAID || status == CANCELLED;
}
},
PAID(1, "已支付") {
@Override
public boolean canChangeTo(OrderStatus status) {
return status == COMPLETED || status == REFUNDED;
}
},
// 其他状态...
private final int code;
private final String desc;
public abstract boolean canChangeTo(OrderStatus status);
}
这种设计的好处:
| 组件 | 版本要求 | 备注 |
|---|---|---|
| JDK | 1.8+ | 建议使用Oracle JDK |
| MySQL | 5.7 | 需开启binlog用于Canal数据同步 |
| Redis | 5.0+ | 集群模式需修改配置 |
| Nginx | 1.18+ | 用于静态资源代理 |
重要提示:MySQL必须配置transaction-isolation=REPEATABLE-READ,这是系统正常工作的关键
sql/tables.sql建表sql/data.sqlsql复制CREATE USER 'ticket_user'@'%' IDENTIFIED BY 'Ticket123!';
GRANT SELECT,INSERT,UPDATE,DELETE ON ticket_db.* TO 'ticket_user'@'%';
问题1:启动时报数据库连接失败
问题2:选座后页面不刷新
@{}路径写法问题3:支付回调接收不到
java复制// 初始化布隆过滤器
BloomFilter<String> seatFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000,
0.01);
// 查询前先过滤
if(!seatFilter.mightContain(seatId)){
return Result.error("座位不存在");
}
java复制String tableSuffix = userId % 10;
String sql = "SELECT * FROM order_" + tableSuffix + " WHERE...";
java复制@RateLimiter(value = 10, key = "#userId")
public Result querySeats(Long userId) {
//...
}
yaml复制mybatis-plus:
encryptor:
password: ${DB_ENCRYPT_KEY} # 从环境变量读取
algorithm: AES/CBC/PKCS5Padding
我在实际测试中发现,当并发超过500请求/秒时,需要引入Redis分布式锁替代数据库锁。这里分享一个Redisson实现示例:
java复制RLock lock = redissonClient.getLock("seat:"+seatId);
try {
if(lock.tryLock(1, 15, TimeUnit.SECONDS)) {
// 业务处理
}
} finally {
lock.unlock();
}
这个项目最值得关注的其实是事务边界的把控——什么时候该用数据库事务,什么时候该用分布式锁,需要根据业务场景仔细权衡。经过三次重构后,我们最终确定了这样的原则:涉及资金的操作必须用事务,高并发查询用缓存,状态变更用锁。```