1. 项目背景与需求分析
医院挂号预约管理系统是医疗机构信息化建设的重要组成部分。随着互联网医疗的快速发展,传统的窗口排队挂号模式已经无法满足现代患者的需求。我们团队最近完成了一个基于ThinkPHP和Laravel双框架的医院挂号预约系统开发项目,这套系统目前已在三家二甲医院稳定运行超过半年。
从实际需求来看,这类系统需要解决几个核心痛点:
- 患者端:需要提供7×24小时在线挂号服务,支持科室查询、医生排班查看、预约时间段选择等功能
- 管理端:需要处理号源分配、停诊调整、预约统计等复杂业务逻辑
- 系统层面:要应对挂号高峰期的并发压力,保证系统稳定性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 框架选择考量
我们采用ThinkPHP 6.0和Laravel 8.0双框架混合开发,主要基于以下考虑:
ThinkPHP优势:
- 中文文档完善,学习曲线平缓
- 内置的ORM和验证器非常适合快速开发业务表单
- 对微信生态的集成支持较好
Laravel优势:
- 队列系统和任务调度更适合处理预约超时未支付等延时业务
- Eloquent ORM在复杂关联查询时更灵活
- 测试工具链更完善,适合核心业务逻辑的单元测试
2.2 系统架构设计
整体采用分层架构:
code复制表现层:Vue.js + Element UI
应用层:ThinkPHP(患者端) + Laravel(管理端)
服务层:统一RESTful API
数据层:MySQL 8.0 + Redis
关键设计决策:
- 患者端使用ThinkPHP开发,因其表单处理效率更高
- 管理端使用Laravel开发,利用其更完善的权限管理和审计功能
- 通过JWT实现跨框架身份认证
- 使用Redis实现分布式锁解决号源超卖问题
3. 核心功能实现细节
3.1 号源管理模块
这是系统的核心难点,我们设计了三级号源池:
php复制// ThinkPHP中的号源模型
class RegistrationSource extends Model
{
// 号源状态常量
const STATUS_AVAILABLE = 1;
const STATUS_LOCKED = 2;
const STATUS_BOOKED = 3;
// 使用Redis分布式锁
public function lock($userId)
{
$lockKey = "reg_lock:".$this->id;
if (Redis::setnx($lockKey, $userId)) {
Redis::expire($lockKey, 300); // 5分钟锁定
return true;
}
return false;
}
}
3.2 预约业务流程
完整预约流程包含11个状态转换:
code复制[可选] -> [已锁定] -> [待支付]
-> ([已取消] 或 [已完成])
-> ([已就诊] 或 [已爽约])
关键实现技巧:
- 使用Laravel的状态机包(laravel-statemachine)管理复杂状态流转
- 支付超时检查通过Laravel队列的延迟任务实现
- 就诊前2小时发送提醒使用Laravel的任务调度
3.3 高并发处理方案
在618大促期间我们遭遇了3000+ QPS的挂号请求,通过以下优化保障系统稳定:
- 使用Nginx限流模块限制单个IP请求频率
- 热点数据(科室列表、医生信息)使用Redis缓存
- 号源库存采用Redis原子计数器实现
- 数据库查询使用ThinkPHP的缓存查询功能
4. 数据库设计与优化
4.1 核心表结构
sql复制CREATE TABLE `registration_orders` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`patient_id` bigint(20) NOT NULL,
`doctor_id` bigint(20) NOT NULL,
`schedule_id` bigint(20) NOT NULL COMMENT '排班ID',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1待支付 2已取消 3已完成',
`lock_expire` datetime DEFAULT NULL COMMENT '锁定过期时间',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_patient` (`patient_id`),
KEY `idx_doctor` (`doctor_id`),
KEY `idx_schedule` (`schedule_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 查询优化实践
- 医生排班查询使用覆盖索引:
sql复制ALTER TABLE `doctor_schedules`
ADD INDEX `idx_dept_date` (`department_id`, `work_date`, `doctor_id`);
- 使用Laravel的查询构造器优化关联查询:
php复制$schedules = DoctorSchedule::with(['doctor' => function($query) {
$query->select('id','name','title','specialty');
}])
->where('work_date', '>=', now())
->where('department_id', $deptId)
->orderBy('work_date')
->paginate(10);
5. 安全防护方案
医疗系统对安全性要求极高,我们实施了以下措施:
-
数据加密:
- 使用ThinkPHP的加密工具对患者敏感信息加密存储
- Laravel的加密器处理支付相关数据
-
防黄牛机制:
- 基于用户行为的风险评分系统
- 同一IP/设备短时间内多次预约触发验证码
-
审计日志:
- 使用Laravel的activitylog包记录关键操作
- 管理员操作需要二次验证
6. 部署与运维实践
6.1 生产环境部署
采用Docker Swarm集群部署:
code复制version: '3.8'
services:
thinkphp:
image: registry.example.com/reg-php:1.2
deploy:
replicas: 3
environment:
- DB_HOST=mysql-master
laravel:
image: registry.example.com/reg-laravel:1.4
deploy:
replicas: 2
mysql-master:
image: mysql:8.0
volumes:
- mysql-data:/var/lib/mysql
6.2 监控方案
-
使用Prometheus监控:
- 关键指标:预约创建成功率、支付转化率、接口响应时间
- 自定义指标:号源库存余量、预约取消率
-
日志收集:
- ThinkPHP日志通过Filebeat收集
- Laravel日志使用Monolog推送到ELK
7. 踩坑与经验总结
7.1 跨框架会话共享问题
初期两个框架的session无法共享,最终解决方案:
- 使用Redis作为统一session存储
- 自定义session驱动确保加解密方式一致
- 在Nginx层设置统一的session cookie参数
7.2 支付状态同步延迟
遇到支付回调延迟导致号源占用问题,改进方案:
- 引入支付中间状态"处理中"
- 使用Laravel Horizon监控队列积压
- 增加补偿查询任务定期修复状态
7.3 医生停诊处理
实际运营中发现停诊处理流程需要优化:
- 提前24小时停诊需短信通知已预约患者
- 紧急停诊时自动推荐同科室其他医生
- 在管理后台增加批量停诊功能
这套系统上线后,将医院的平均挂号时间从45分钟缩短到3分钟,预约成功率提升至98%。最大的收获是认识到医疗系统对稳定性和数据一致性的极端要求,任何业务流程都需要考虑异常情况和回退方案。
