本文目录导读:

使用PHP搭建医院挂号系统是一个典型的Web开发项目,下面我会为你梳理一个从零开始的完整架构、技术选型和核心功能实现方案,并附上关键代码示例。
技术栈推荐
| 层级 | 技术选型 | 说明 |
|---|---|---|
| 后端语言 | PHP 8.x | 性能好,语法现代化 |
| 框架 | Laravel 10 / ThinkPHP 8 | 选其一,Laravel生态更好,ThinkPHP上手快 |
| 数据库 | MySQL 8.x + Redis | MySQL存业务数据,Redis存Token和缓存 |
| 前端 | Bootstrap 5 + jQuery | 快速搭建响应式后台 |
| 模板引擎 | Blade (Laravel) | 或 ThinkPHP自带的模板 |
| API格式 | RESTful JSON | 前后端分离或混合开发用 |
| 部署 | Nginx + PHP-FPM | 高并发支持 |
数据库设计(核心表结构)
挂号系统至少需要以下7张核心表:
用户表 (users)
CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '姓名', `phone` varchar(11) NOT NULL COMMENT '手机号', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `password` varchar(255) NOT NULL, `role` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1:患者 2:医生 3:管理员', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
科室表 (departments)
CREATE TABLE `departments` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '科室名称', `description` text COMMENT '科室介绍', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1:启用 0:停用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
医生表 (doctors)
CREATE TABLE `doctors` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) unsigned NOT NULL, `department_id` int(11) unsigned NOT NULL, varchar(50) DEFAULT NULL COMMENT '职称:主任医师/副主任医师等', `intro` text COMMENT '医生简介', `consultation_fee` decimal(8,2) NOT NULL DEFAULT 0.00 COMMENT '挂号费', `max_appointments` int(4) NOT NULL DEFAULT 30 COMMENT '每日最大预约数', PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `users`(`id`), FOREIGN KEY (`department_id`) REFERENCES `departments`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
排班表 (schedules)
CREATE TABLE `schedules` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `doctor_id` int(11) unsigned NOT NULL, `schedule_date` date NOT NULL COMMENT '出诊日期', `time_slot` varchar(20) NOT NULL COMMENT '时段:morning/afternoon/evening', `max_appointments` int(4) NOT NULL DEFAULT 30, `booked_count` int(4) NOT NULL DEFAULT 0 COMMENT '已预约数', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1:可预约 0:停诊', PRIMARY KEY (`id`), UNIQUE KEY `uk_doctor_date_slot` (`doctor_id`,`schedule_date`,`time_slot`), FOREIGN KEY (`doctor_id`) REFERENCES `doctors`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
挂号单表 (appointments) - 关键表
CREATE TABLE `appointments` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) unsigned NOT NULL COMMENT '患者ID', `schedule_id` int(11) unsigned NOT NULL COMMENT '排班ID', `appointment_no` varchar(20) NOT NULL COMMENT '挂号单号(如:GH20240305001)', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1:待就诊 2:已就诊 3:已取消 4:爽约', `booking_time` datetime NOT NULL COMMENT '预约时间', `visit_time` datetime DEFAULT NULL COMMENT '实际就诊时间', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_appointment_no` (`appointment_no`), KEY `idx_user_id` (`user_id`), KEY `idx_schedule_id` (`schedule_id`), FOREIGN KEY (`user_id`) REFERENCES `users`(`id`), FOREIGN KEY (`schedule_id`) REFERENCES `schedules`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
系统配置表 (configs)
- 存放医院名称、科室数量等配置
核心功能模块实现(Laravel示例)
挂号流程(用户端 - 预约接口)
路由 (routes/api.php)
Route::post('/appointments', [AppointmentController::class, 'store'])->middleware('auth:sanctum');
Route::get('/doctors/{departmentId}', [DoctorController::class, 'listByDepartment']);
Route::get('/schedules/{doctorId}', [ScheduleController::class, 'availableSlots']);
控制器核心方法 (AppointmentController@store)
<?php
namespace App\Http\Controllers\Api;
use App\Models\Appointment;
use App\Models\Schedule;
use App\Models\Doctor;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AppointmentController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'schedule_id' => 'required|exists:schedules,id',
'patient_name' => 'required|string',
'phone' => 'required|string'
]);
$schedule = Schedule::with('doctor')->findOrFail($validated['schedule_id']);
// 1. 校验排班是否有效
if ($schedule->status != 1) {
return response()->json(['message' => '该时段已停诊'], 400);
}
// 2. 校验号源是否充足(乐观锁防超售)
$affected = Schedule::where('id', $schedule->id)
->where('booked_count', '<', $schedule->max_appointments)
->update(['booked_count' => DB::raw('booked_count + 1')]);
if (!$affected) {
return response()->json(['message' => '号源已满,请选择其他时段'], 409);
}
// 3. 生成预约单号
$appointmentNo = 'GH' . date('Ymd') . str_pad(Appointment::max('id') + 1, 3, '0', STR_PAD_LEFT);
// 4. 创建挂号记录
DB::beginTransaction();
try {
$appointment = Appointment::create([
'user_id' => auth()->id(),
'schedule_id' => $schedule->id,
'appointment_no' => $appointmentNo,
'booking_time' => Carbon::now(),
'status' => 1
]);
// 5. 可选:发送短信通知(队列异步)
// SendSmsJob::dispatch($validated['phone'], $appointment);
DB::commit();
return response()->json(['data' => $appointment, 'message' => '预约成功'], 201);
} catch (\Exception $e) {
DB::rollBack();
// 回滚号源计数
Schedule::where('id', $schedule->id)->decrement('booked_count');
return response()->json(['message' => '预约失败,请稍后重试'], 500);
}
}
}
医生排班管理(管理端)
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Schedule;
use Carbon\Carbon;
use Illuminate\Http\Request;
class ScheduleController extends Controller
{
/**
* 生成医生未来7天排班
*/
public function generate(Doctor $doctor)
{
$startDate = Carbon::now()->addDay();
$endDate = Carbon::now()->addDays(7);
$dates = [];
while ($startDate <= $endDate) {
// 假设周一至周五出诊
if (!in_array($startDate->dayOfWeek, [0, 6])) {
$dates[] = $startDate->format('Y-m-d');
}
$startDate->addDay();
}
foreach ($dates as $date) {
// 上午时段
Schedule::firstOrCreate([
'doctor_id' => $doctor->id,
'schedule_date' => $date,
'time_slot' => 'morning',
], [
'max_appointments' => $doctor->max_appointments,
'booked_count' => 0,
'status' => 1
]);
// 下午时段
Schedule::firstOrCreate([
'doctor_id' => $doctor->id,
'schedule_date' => $date,
'time_slot' => 'afternoon',
], [
'max_appointments' => $doctor->max_appointments,
'booked_count' => 0,
'status' => 1
]);
}
return response()->json(['message' => '排班生成成功']);
}
}
取消预约(带锁机制)
public function cancel(Appointment $appointment)
{
// 仅允许取消未就诊的预约
if ($appointment->status != 1) {
return response()->json(['message' => '当前状态不可取消'], 400);
}
// 距离预约时间少于1小时不可取消
if ($appointment->booking_time->diffInHours(Carbon::now()) < 1) {
return response()->json(['message' => '距预约时间不足1小时,不可取消'], 400);
}
DB::transaction(function () use ($appointment) {
// 回滚号源
Schedule::where('id', $appointment->schedule_id)->decrement('booked_count');
// 更新状态
$appointment->update(['status' => 3]);
});
return response()->json(['message' => '取消成功']);
}
关键安全与并发处理
- 号源并发控制:使用
DB::raw('booked_count + 1')配合where条件实现行级乐观锁,避免超售。 - 防重复预约:在
appointments表增加(user_id, schedule_id)唯一索引,或按业务要求限制当天同一医生只能预约一次。 - 接口限流:使用 Laravel 的
throttle中间件,限制用户每分钟请求次数。 - 敏感操作日志:记录取消、修改等操作日志到
logs表(user_id,action,target_type,target_id)。
前端界面简要设计
- 患者端(手机适配)
- 首页:选择科室 → 选择医生 → 查看排班 → 选择时段 → 确认支付(可选)→ 生成挂号单
- 我的挂号:查看历史挂号、取消、支付
- 医生端
- 查看今日待诊列表
- 点击“开始就诊”更改状态为“已就诊”
- 管理员端
- 医生管理、科室管理、排班生成
- 挂号数据统计(日/月报表)
部署与优化建议
-
Redis缓存:
- 缓存科室列表
cache:departments - 缓存各医生未来7天排班
cache:doctor:{id}:schedule
- 缓存科室列表
-
队列处理:
- 短信发送、邮件通知使用 Laravel Queue + Redis
- 号源释放后异步通知排队用户(如微信通知)
-
Nginx配置优化:
server { listen 80; server_name hospital.example.com; root /var/www/hospital/public; location / { try_files $uri $uri/ /index.php?$query_string; } 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; } }
可扩展功能(高级)
- 在线支付:接入支付宝/微信支付,支付成功后锁定号源。
- 排队叫号系统:实时显示当前叫号(WebSocket 或 SSE)。
- 电子病历关联:挂号后自动关联患者的电子病历。
- 多语言支持:使用 Laravel 的 多语言函数。
一个可用的医院挂号系统,核心在于号源的并发控制和数据库的索引优化,建议先搭建最小可行产品(MVP):患者注册 → 选择科室医生 → 预约成功,后续再逐步加入支付、通知、统计功能。
如果你需要更详细的代码(例如完整的用户认证、支付回调、报表生成),请告诉我,我可以单独为你提供某个模块的完整实现。