城市运动场地预约系统是当前体育产业数字化转型的典型应用。随着全民健身意识提升,羽毛球馆、篮球场、游泳馆等运动场所的线下排队预约模式已经无法满足现代人的高效需求。去年我在协助某市级体育中心做信息化改造时,亲眼目睹管理员用纸质登记本处理预约冲突的窘境——每天下午4点后电话被打爆,重复预约率高达15%。
这个基于ThinkPHP5的预约系统正是为解决这类痛点而生。它实现了运动场馆的在线展示、时段选择、支付锁定、订单管理全流程数字化。相比市面通用预约工具,我们针对运动场景特别强化了三个特性:场地类型动态配置(适应不同运动规则)、时段冲突智能检测(精确到分钟级)、会员积分体系(促进用户粘性)。
选择ThinkPHP5而非Laravel或Yii主要基于三点考量:
系统采用经典的三层架构:
code复制public/ # 前端资源
application/ # 应用核心
├─ controller/ # 预约/支付/管理接口
├─ model/ # 数据模型
│ ├─ Court.php # 场地模型
│ └─ Order.php # 订单模型
└─ validate/ # 自定义验证器
场地表(court)设计示例:
sql复制CREATE TABLE `court` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`venue_id` int(11) COMMENT '所属场馆',
`type` tinyint(2) NOT NULL COMMENT '1羽毛球 2篮球...',
`name` varchar(50) NOT NULL COMMENT 'A区1号场',
`open_time` time NOT NULL COMMENT '08:00:00',
`close_time` time NOT NULL COMMENT '22:00:00',
`price_per_hour` decimal(10,2) NOT NULL,
`status` tinyint(1) DEFAULT '1' COMMENT '1可用 0维护',
PRIMARY KEY (`id`),
KEY `idx_venue` (`venue_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单表特别注意建立联合索引:
sql复制ALTER TABLE `order` ADD INDEX `idx_court_time` (`court_id`, `date`, `start_time`);
解决并发预约的核心方案:
php复制// 在Order模型中添加检查方法
public function checkAvailable($courtId, $date, $startTime, $endTime)
{
$exists = $this->where([
'court_id' => $courtId,
'date' => $date,
'status' => ['neq', -1] // 排除已取消订单
])->where(function($query) use ($startTime, $endTime) {
$query->where([
['start_time', '<', $endTime],
['end_time', '>', $startTime]
]);
})->find();
return is_null($exists);
}
重要提示:必须配合Redis乐观锁使用,防止超卖
php复制$redis->watch('court_'.$courtId); // ...业务逻辑... if($redis->multi()->exec() === false){ throw new Exception('当前时段已被预约'); }
通过策略模式支持不同定价规则:
php复制interface PriceStrategy {
public function calculate(Court $court, $startTime, $duration);
}
class WeekendStrategy implements PriceStrategy {
public function calculate($court, $startTime, $duration){
$base = $court->price_per_hour;
return date('N', strtotime($startTime)) >= 6
? $base * 1.5 * $duration
: $base * $duration;
}
}
在订单控制器中动态调用:
php复制$strategy = date('N') >= 6 ? new WeekendStrategy() : new NormalStrategy();
$price = $strategy->calculate($court, $input['start_time'], $input['hours']);
现象:系统显示时段可用,但提交时提示冲突
排查过程:
php复制// 修改为严格区间判断
->where('start_time < :end AND end_time > :start', [
'end' => $endTime,
'start' => $startTime
])
解决方案:
sql复制ALTER TABLE `order` ADD UNIQUE `udx_order` (`court_id`,`date`,`start_time`);
php复制try {
$order->save();
} catch (\think\exception\PDOException $e) {
if(strpos($e->getMessage(), 'Duplicate entry') !== false){
// 提示用户时段已被占用
}
}
推荐使用EasyWeChat组件:
php复制$app = Factory::miniProgram($config);
$app->template_message->send([
'touser' => $openid,
'template_id' => '预约成功通知ID',
'data' => [
'time' => $order->date.' '.$order->start_time,
'court' => $court->name
]
]);
使用ECharts实现场馆利用率报表:
javascript复制option = {
dataset: { source: [ ['9:00', 23], ['10:00', 45]... ] },
xAxis: { type: 'category' },
yAxis: { type: 'value' },
series: [{ type: 'bar', encode: { x:0, y:1 } }]
};
修改ThinkPHP配置:
php复制// config.php
return [
'app_debug' => false,
'app_trace' => false,
'sql_explain' => false,
'database' => [
'fields_cache' => true
]
];
必做三项防护:
php复制Db::name('order')
->where('user_id', $userId)
->bind(['start'=>$startTime])
->select();
这个项目最让我意外的是用户对"预约提醒"功能的需求强度——85%的用户希望能在开场前30分钟收到推送。后来我们增加了基于Swoole的定时任务系统,用最小成本实现了这个高价值功能。如果你要二次开发,建议优先考虑这类提升用户体验的细节。