1. 项目概述
作为一名经历过多次医疗系统开发的Java工程师,我想分享一个基于SpringBoot的医院预约挂号系统的完整实现方案。这个系统是我在指导某三甲医院信息化改造时的实战项目,目前已在三家医院稳定运行超过两年。
医疗预约系统看似简单,实则暗藏诸多技术挑战。比如高峰期每秒数百次的并发预约请求如何处理?如何确保医生排班变更时不影响已预约患者?这些都是在教科书里找不到答案的实战问题。接下来,我将从架构设计到代码实现,详细拆解这个系统的技术要点。
2. 技术选型与架构设计
2.1 技术栈决策
核心框架选择SpringBoot 2.3.4(现可升级到2.7.x),这是经过多个医疗项目验证的稳定组合:
- Web层:Spring MVC + Thymeleaf(兼顾前后端分离和传统模板需求)
- 持久层:MyBatis-Plus 3.4.0(比原生MyBatis开发效率提升40%)
- 安全控制:Spring Security + JWT(双认证机制保障医疗数据安全)
- 缓存:Redis 6.x(应对预约高峰期的秒杀场景)
- 消息队列:RabbitMQ 3.8(异步处理预约短信通知)
关键提示:医疗系统必须考虑等保三级要求,我们特别增加了审计日志模块,所有数据修改操作都会留痕。
2.2 分布式架构设计
系统采用分层微服务架构:
code复制[网关层]
↓
[预约服务] ←→ [排班服务]
↓ ↑
[患者服务] [医生服务]
↓ ↑
[统一认证中心]←→[Redis集群]
这种设计带来三个核心优势:
- 预约服务可独立扩容应对挂号高峰
- 排班变更通过事件通知机制保证数据一致性
- 服务故障隔离,单个模块问题不影响整体
3. 核心功能实现
3.1 预约挂号模块
挂号流程的并发控制是最大难点,我们采用"Redis分布式锁+乐观锁"双重保障:
java复制// 伪代码展示核心预约逻辑
public AppointmentResult makeAppointment(AppointmentRequest request) {
// 1. Redis分布式锁防止重复提交
String lockKey = "lock:appt:" + request.getScheduleId();
try {
if (!redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
throw new BusException("操作太频繁,请稍后再试");
}
// 2. 验证排班余量
Schedule schedule = scheduleService.getById(request.getScheduleId());
if (schedule.getRemain() <= 0) {
throw new BusException("该时段已约满");
}
// 3. 乐观锁更新
boolean updated = scheduleService.update()
.setSql("remain = remain - 1")
.eq("id", schedule.getId())
.eq("remain", schedule.getRemain()) // 版本控制
.update();
if (!updated) {
throw new BusException("名额已被占用,请重新选择");
}
// 4. 创建预约记录
return createAppointment(request);
} finally {
redisLock.unlock(lockKey);
}
}
3.2 医生排班管理
排班系统采用"模板化+例外处理"的设计模式:
- 基础排班规则(如每周一上午门诊)
- 特殊调整(如节假日停诊)
- 临时变更(如紧急会议)
数据库设计关键表:
sql复制CREATE TABLE `doctor_schedule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`doctor_id` bigint NOT NULL COMMENT '医生ID',
`dept_id` int NOT NULL COMMENT '科室ID',
`schedule_date` date NOT NULL COMMENT '排班日期',
`time_period` varchar(20) NOT NULL COMMENT '时段(morning/afternoon/night)',
`total` int DEFAULT '30' COMMENT '总号源',
`remain` int DEFAULT '30' COMMENT '剩余号源',
`version` int DEFAULT '0' COMMENT '乐观锁版本',
PRIMARY KEY (`id`),
UNIQUE KEY `udx_doctor_time` (`doctor_id`,`schedule_date`,`time_period`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 安全与性能优化
4.1 医疗数据安全
- 敏感数据加密:患者身份证、手机号采用AES加密存储
- 操作审计:关键表增加create_by、create_time、update_by、update_time字段
- 接口防刷:预约接口采用滑动窗口限流(Guava RateLimiter)
4.2 高并发优化方案
通过压力测试发现三个性能瓶颈及解决方案:
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 早8点放号 | MySQL连接池耗尽 | 增加HikariCP最大连接数到200 |
| 热门专家号预约 | 超卖问题 | Redis分布式锁+数据库乐观锁 |
| 排班查询 | 响应时间>2s | 增加二级缓存(Caffeine+Redis) |
5. 部署与监控
5.1 生产环境配置
推荐使用Docker Compose部署:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
app:
image: hospital-booking:1.0
depends_on:
- mysql
- redis
ports:
- "8080:8080"
5.2 监控方案
- Spring Boot Admin:监控服务健康状态
- Prometheus+Grafana:构建业务指标看板
- 实时预约量
- 接口响应时间
- 异常请求统计
6. 踩坑经验分享
-
时区问题:医院排班涉及跨天业务(如夜班),必须统一使用UTC时间存储,前端按当地时区显示
-
退号逻辑:退号后号源应返回池中,但要设置15分钟冻结期防止恶意刷号
-
短信轰炸:预约成功通知要加频控,同一患者10分钟内只发1条
-
缓存一致:医生停诊时,需要同时清理Redis中的排班缓存
这个项目让我深刻体会到,医疗系统开发不仅是技术活,更需要理解医疗行业的特殊业务流程。比如急诊科和普通门诊的预约规则就完全不同,这些业务细节往往比技术实现更具挑战性。