城市运动空间场地预约系统是当前体育场馆数字化管理的典型解决方案。这个基于ThinkPHP5框架开发的毕业设计项目,实际上解决的是城市公共体育资源高效分配的现实问题。随着全民健身意识的提升,传统电话预约、现场登记的方式已经无法满足现代城市居民对运动场地即时性、透明化的需求。
我去年参与过某区体育中心的系统升级项目,亲眼看到一套好的预约系统如何将场地利用率从43%提升到78%。这个毕业设计选题的巧妙之处在于,它既包含了Web开发的完整技术栈实践,又具备真实的社会应用场景。ThinkPHP5作为国内流行的PHP框架,其优雅的代码结构和丰富的扩展性,特别适合这类中小型管理系统的快速开发。
在技术选型阶段,我对比过Laravel、Yii等主流PHP框架。最终选择ThinkPHP5主要基于三点考虑:首先,它的中文文档完善,学习曲线平缓,特别适合毕业设计这类有时间限制的项目;其次,内置的ORM和路由系统足够应对场地预约这类常规业务场景;最重要的是,ThinkPHP5的缓存机制和队列处理能很好应对预约高峰期的并发请求。
实际开发中,我特别利用了ThinkPHP5的以下特性:
场地预约系统的核心在于时间片管理。我的数据库设计采用了"场地-时段"二维模型,主要包含以下关键表:
sql复制CREATE TABLE `venue` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '场地名称',
`type` tinyint(4) NOT NULL COMMENT '1篮球场 2羽毛球场...',
`price_per_hour` decimal(10,2) NOT NULL,
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1可用 0维护中',
`cover_img` varchar(255) DEFAULT NULL,
`description` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `time_slot` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`start_time` time NOT NULL COMMENT '08:00:00',
`end_time` time NOT NULL COMMENT '09:00:00',
`day_type` tinyint(4) NOT NULL COMMENT '0工作日 1周末 2节假日',
`price_coefficient` decimal(3,1) DEFAULT '1.0' COMMENT '时段价格系数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `schedule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`venue_id` int(11) NOT NULL,
`time_slot_id` int(11) NOT NULL,
`date` date NOT NULL COMMENT '2023-08-20',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0可预约 1已预约 2已取消',
`order_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `venue_time_date` (`venue_id`,`time_slot_id`,`date`),
KEY `idx_date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意:schedule表建立了(venue_id, time_slot_id, date)的联合唯一索引,这是防止同一时段被重复预约的关键设计。
预约功能看似简单,实则包含多个需要精确处理的子流程:
php复制public function checkAvailable($venueId, $date, $timeSlotId)
{
return Db::name('schedule')
->where([
'venue_id' => $venueId,
'date' => $date,
'time_slot_id' => $timeSlotId,
'status' => 0
])
->find();
}
php复制Db::startTrans();
try {
$schedule = Db::name('schedule')
->where([
'id' => $scheduleId,
'status' => 0
])
->lock(true)
->find();
if (!$schedule) {
throw new Exception('该时段已被预约');
}
// 生成订单
$orderId = Db::name('order')->insertGetId([
'user_id' => $userId,
'total_price' => $price,
'create_time' => date('Y-m-d H:i:s')
]);
// 更新排期状态
Db::name('schedule')
->where('id', $scheduleId)
->update([
'status' => 1,
'order_id' => $orderId
]);
Db::commit();
return $orderId;
} catch (Exception $e) {
Db::rollback();
throw $e;
}
不同时段、不同日期类型的价格浮动是商业系统的常见需求。我通过组合策略模式实现了灵活的价格计算:
php复制interface PriceStrategy {
public function calculate(Venue $venue, TimeSlot $timeSlot): float;
}
class WeekendStrategy implements PriceStrategy {
public function calculate(Venue $venue, TimeSlot $timeSlot): float {
return $venue->base_price * $timeSlot->coefficient * 1.5;
}
}
class HolidayStrategy implements PriceStrategy {
public function calculate(Venue $venue, TimeSlot $timeSlot): float {
return $venue->base_price * $timeSlot->coefficient * 2.0;
}
}
class PriceCalculator {
public static function getPrice($date, Venue $venue, TimeSlot $timeSlot): float {
$strategy = self::getStrategy($date);
return $strategy->calculate($venue, $timeSlot);
}
private static function getStrategy($date): PriceStrategy {
if (self::isHoliday($date)) {
return new HolidayStrategy();
} elseif (self::isWeekend($date)) {
return new WeekendStrategy();
}
return new DefaultStrategy();
}
}
基于用户历史预约数据,实现了个性化场地推荐:
php复制public function recommendVenues($userId, $limit = 3)
{
// 获取用户常预约的场地类型
$favType = Db::name('order')
->alias('o')
->join('schedule s', 'o.id = s.order_id')
->join('venue v', 's.venue_id = v.id')
->where('o.user_id', $userId)
->group('v.type')
->order('COUNT(*) DESC')
->value('v.type');
// 获取同类型场地空闲时段
return Db::name('venue')
->where('type', $favType)
->where('status', 1)
->order('RAND()')
->limit($limit)
->select();
}
采用官方SDK实现微信支付,关键点在于:
核心代码结构:
php复制class WxPayService
{
public function unifiedOrder($orderId)
{
$order = Db::name('order')->find($orderId);
$notifyUrl = url('/pay/notify', '', true, true);
$input = new WxPayUnifiedOrder();
$input->SetBody("场地预约-{$order['venue_name']}");
$input->SetOut_trade_no($order['order_no']);
$input->SetTotal_fee($order['total_price'] * 100);
$input->SetTime_start(date("YmdHis"));
$input->SetTime_expire(date("YmdHis", time() + 600));
$input->SetNotify_url($notifyUrl);
$input->SetTrade_type("JSAPI");
$input->SetOpenid($order['openid']);
return WxPayApi::unifiedOrder($input);
}
public function handleNotify($xml)
{
$notify = WxPayNotifyResults::Init($xml);
$orderNo = $notify->GetOut_trade_no();
Db::startTrans();
try {
$order = Db::name('order')
->where('order_no', $orderNo)
->lock(true)
->find();
if ($order['status'] != 0) {
throw new Exception('订单状态异常');
}
Db::name('order')
->where('id', $order['id'])
->update([
'status' => 1,
'pay_time' => date('Y-m-d H:i:s')
]);
Db::commit();
return true;
} catch (Exception $e) {
Db::rollback();
return false;
}
}
}
php复制// 每天凌晨生成未来30天的排期数据
public function generateSchedules()
{
$venues = Db::name('venue')->where('status', 1)->select();
$timeSlots = Db::name('time_slot')->select();
$startDate = date('Y-m-d');
$endDate = date('Y-m-d', strtotime('+30 days'));
foreach ($venues as $venue) {
foreach ($timeSlots as $slot) {
$dates = $this->getDateRange($startDate, $endDate);
foreach ($dates as $date) {
$exists = Db::name('schedule')
->where([
'venue_id' => $venue['id'],
'time_slot_id' => $slot['id'],
'date' => $date
])
->find();
if (!$exists) {
Db::name('schedule')->insert([
'venue_id' => $venue['id'],
'time_slot_id' => $slot['id'],
'date' => $date,
'status' => 0
]);
}
}
}
}
}
php复制// 错误示范
Db::query("SELECT * FROM venue WHERE id = ".$_GET['id']);
// 正确做法
Db::name('venue')->where('id', input('id'))->find();
php复制// 在模板中使用
{$venue.description|htmlspecialchars}
推荐使用LNMP环境:
nginx复制location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
}
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
使用Linux crontab处理:
bash复制# 每天凌晨生成排期数据
0 3 * * * /usr/bin/php /path/to/project/artisan schedule:generate >> /dev/null 2>&1
# 每10分钟检查超时未支付订单
*/10 * * * * /usr/bin/php /path/to/project/artisan order:check_timeout >> /dev/null 2>&1
php复制try {
// 业务代码
} catch (Exception $e) {
Log::error('预约失败', [
'user_id' => $userId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
php复制'log' => [
'type' => 'File',
'path' => LOG_PATH,
'level' => ['error', 'info', 'sql'],
'apart_level' => ['error', 'sql']
]
"为什么选择ThinkPHP5而不是其他框架?"
回答要点:开发效率、中文社区支持、适合中小型项目
"如何处理高并发场景下的超卖问题?"
展示你的乐观锁实现和事务处理代码
"系统有哪些扩展性考虑?"
可以从以下角度回答:
这个项目最让我有成就感的是看到它从设计稿变成真正可运行的系统。特别是在测试阶段,当并发预约、支付回调这些关键流程全部跑通时,那种解决问题的快感是难以形容的。建议学弟学妹们在做类似项目时,一定要尽早把核心业务流程跑起来,不要陷入无止境的美化界面中。