1. 项目背景与核心需求
篮球场馆预约系统是体育场馆数字化转型的典型应用场景。传统场馆管理普遍存在电话预约效率低、人工记录易出错、空闲时段利用率不均衡等问题。我们团队开发的智慧篮球馆预约系统,正是为了解决这些痛点而生。
系统采用B/S架构设计,主要实现以下核心功能:
- 多终端预约:支持Web端和微信小程序双渠道访问
- 实时状态展示:图形化展示场馆空闲时段和预约情况
- 智能支付:集成微信/支付宝支付接口
- 设备联动:与场馆灯光、计分器等硬件设备对接
- 数据统计:为管理者提供可视化运营报表
技术选型关键点:选择SpringBoot 1.4.2版本主要考虑其轻量级特性和快速开发能力,同时该版本已通过生产环境验证,稳定性有保障。
2. 系统架构设计
2.1 技术栈组成
系统采用分层架构设计,各层技术选型如下:
| 层级 | 技术组件 | 版本 | 作用 |
|---|---|---|---|
| 前端 | HTML5/CSS3 | - | 响应式页面布局 |
| JavaScript | ES6 | 动态交互实现 | |
| LayUI | 2.5.6 | 后台管理界面框架 | |
| 后端 | SpringBoot | 1.4.2 | 核心框架 |
| MyBatis | 3.4.6 | ORM框架 | |
| Redis | 3.2 | 缓存/分布式锁 | |
| 数据库 | MySQL | 5.7 | 数据存储 |
| 中间件 | Quartz | 2.3.0 | 定时任务调度 |
2.2 关键架构决策
- 分布式锁设计:
java复制// Redis分布式锁实现示例
public boolean tryLock(String lockKey, String requestId, int expireTime) {
return redisTemplate.opsForValue().setIfAbsent(
lockKey,
requestId,
expireTime,
TimeUnit.SECONDS
);
}
采用Redis的SETNX命令实现分布式锁,解决高并发场景下的超卖问题,确保同一时段只能被一个用户成功预约。
- 无状态认证方案:
使用JWT替代传统Session,减轻服务器存储压力。Token中携带用户角色信息,前端每次请求都在Header中携带:
code复制Authorization: Bearer <token>
- 缓存策略:
- 场馆信息:Redis缓存,过期时间2小时
- 预约记录:MySQL主存储,Redis做查询缓存
- 热门时段:本地缓存+Redis二级缓存
3. 核心功能实现
3.1 预约业务流程
完整预约流程包含以下步骤:
- 用户查询可预约时段
- 选择时段并提交预约申请
- 系统校验时段可用性
- 生成待支付订单
- 用户完成支付
- 系统确认订单并更新场馆状态
- 同步通知硬件设备
关键异常处理:支付超时未完成的订单,通过Quartz定时任务每30分钟扫描一次,自动释放被占用的时段。
3.2 数据库设计
主要数据表结构设计:
场馆表(venue)
sql复制CREATE TABLE `venue` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '场馆名称',
`location` varchar(100) NOT NULL COMMENT '具体位置',
`status` tinyint(4) DEFAULT '1' COMMENT '1-可用 0-维护',
`price_per_hour` decimal(10,2) NOT NULL COMMENT '每小时价格',
`device_id` varchar(32) DEFAULT NULL COMMENT '关联设备ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
预约订单表(booking_order)
sql复制CREATE TABLE `booking_order` (
`id` varchar(32) NOT NULL COMMENT '订单号',
`user_id` int(11) NOT NULL,
`venue_id` int(11) NOT NULL,
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-待支付 1-已支付 2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`),
KEY `idx_venue_time` (`venue_id`,`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.3 支付模块集成
支付流程关键代码示例:
java复制@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private WechatPayService wechatPayService;
@PostMapping("/create")
public Result createPayment(@Valid @RequestBody PaymentCreateDTO dto) {
// 1. 校验订单是否存在且属于当前用户
BookingOrder order = orderService.getById(dto.getOrderId());
if(order == null || !order.getUserId().equals(currentUserId())){
return Result.fail("订单不存在");
}
// 2. 调用支付平台接口
PaymentResponse response = wechatPayService.createOrder(
order.getId(),
order.getTotalAmount(),
"篮球馆预约-" + order.getVenue().getName()
);
// 3. 记录支付流水
paymentService.recordPaymentFlow(order.getId(), response);
return Result.success(response);
}
}
4. 性能优化实践
4.1 高并发处理方案
- 缓存预热:
- 每日凌晨加载热门场馆信息到Redis
- 使用Guava Cache做本地缓存,减少Redis访问
- 数据库优化:
sql复制-- 添加复合索引提升查询性能
ALTER TABLE `booking_order` ADD INDEX `idx_venue_time` (`venue_id`, `start_time`, `end_time`);
- 接口限流:
java复制@RestController
@Slf4j
public class VenueController {
// 每秒钟最多处理50个请求
@RateLimiter(value = 50)
@GetMapping("/available")
public Result listAvailableVenues(@RequestParam String date) {
// 查询逻辑...
}
}
4.2 压力测试结果
使用JMeter进行模拟测试,关键指标:
| 并发用户数 | 平均响应时间 | 错误率 | QPS |
|---|---|---|---|
| 100 | 238ms | 0% | 198 |
| 200 | 417ms | 0.2% | 205 |
| 500 | 1.2s | 1.5% | 193 |
测试环境配置:
- CPU: 4核
- 内存: 8GB
- JDK: 1.8
- MySQL: 5.7 主从架构
5. 部署与运维
5.1 生产环境部署
推荐部署架构:
code复制[Nginx] → [SpringBoot应用集群] → [Redis哨兵] → [MySQL主从]
关键配置项:
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://master.db:3306/venue_booking?useSSL=false
username: prod_user
password: ${DB_PASSWORD}
redis:
sentinel:
master: mymaster
nodes: redis1:26379,redis2:26379,redis3:26379
5.2 监控方案
- 健康检查端点:
java复制@RestController
@RequestMapping("/actuator")
public class HealthController {
@GetMapping("/health")
public ResponseEntity<Health> health() {
// 检查数据库连接
// 检查Redis连接
// 返回综合健康状态
}
}
- Prometheus监控指标:
java复制@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "venue-booking",
"region", "east-1"
);
}
6. 踩坑经验分享
- 时区问题:
MySQL默认时区与应用服务器不一致会导致预约时间错乱。解决方案:
sql复制SET GLOBAL time_zone = '+8:00';
- 微信支付回调:
微信支付要求回调接口必须在5秒内响应,否则会重复通知。建议:
- 收到通知后立即返回success
- 异步处理业务逻辑
- 分布式锁误用:
早期版本未设置锁的过期时间,导致系统异常时锁无法释放。正确做法:
java复制// 设置合理的过期时间(单位:秒)
redisLock.tryLock("lock_key", "request_id", 30);
- 前端时间处理:
JavaScript的Date对象在不同浏览器表现不一致,建议使用moment.js统一处理:
javascript复制// 统一使用ISO8601格式传输时间
const startTime = moment("2023-06-01T14:00:00+08:00");
这个项目从技术选型到最终上线历时3个月,最大的收获是认识到完善的异常处理机制比实现核心功能更重要。特别是在支付和硬件对接场景下,必须考虑各种边界条件和失败情况。后续计划加入智能推荐算法,根据用户历史预约习惯推荐合适时段,进一步提升场馆利用率。