1. 项目概述:SpringBoot医院在线挂号系统开发实录
这个基于SpringBoot的医院在线挂号系统是我去年为某三甲医院开发的实际项目,系统上线后日均处理挂号量超过3000人次。相比传统窗口挂号,线上系统将患者平均等待时间从45分钟缩短至3分钟以内,同时减轻了医院30%的窗口工作压力。系统采用微服务架构设计,核心模块包括患者端微信小程序、医生工作站和后台管理平台,今天重点分享技术实现方案和开发中遇到的典型问题。
2. 系统架构设计与技术选型
2.1 整体架构方案
系统采用前后端分离架构,后端基于SpringBoot 2.7.3构建,前端使用Vue3+Element Plus。考虑到医院业务的高并发特性,我们做了以下关键设计:
-
服务分层:
- API网关层:Spring Cloud Gateway实现路由和鉴权
- 业务服务层:按科室拆分为独立微服务
- 数据访问层:MyBatis-Plus + 多数据源配置
-
数据库设计:
- 主库:MySQL 8.0集群(1主2从)
- 缓存:Redis 6.2哨兵模式
- 搜索引擎:Elasticsearch 7.17用于病历检索
特别注意:医疗系统必须考虑数据一致性,我们采用Seata实现分布式事务,确保挂号记录与号源库存的强一致性。
2.2 核心功能模块实现
2.2.1 号源管理服务
java复制// 号源锁定逻辑示例
@Transactional
public boolean lockRegistrationSource(Long sourceId, Long patientId) {
// 使用乐观锁控制并发
RegistrationSource source = sourceMapper.selectByIdForUpdate(sourceId);
if (source.getStatus() == 0) {
source.setStatus(1); // 锁定状态
source.setPatientId(patientId);
source.setLockTime(LocalDateTime.now());
return sourceMapper.updateById(source) > 0;
}
return false;
}
2.2.2 支付对账服务
支付模块需要处理微信、支付宝和医保支付三种方式,关键点在于:
- 支付状态机设计(待支付→支付中→已支付/已退款)
- 每日定时对账任务(Spring Scheduler实现)
- 医保接口的特殊加密要求(SM4国密算法)
3. 高并发场景下的优化实践
3.1 秒杀场景应对方案
早上8点放号时段面临瞬时高并发,我们采用多级缓存策略:
- Redis缓存预热:提前5分钟加载当日号源到Redis
- 本地缓存:使用Caffeine缓存热门科室信息
- 库存扣减:Redis原子操作+Lua脚本保证原子性
lua复制-- 库存扣减Lua脚本
local key = KEYS[1]
local change = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key))
if current >= change then
return redis.call('DECRBY', key, change)
else
return -1
end
3.2 分布式锁实现
为防止号源超卖,我们基于Redisson实现了分布式锁:
java复制public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
4. 医疗行业特殊需求处理
4.1 合规性要求
- 数据加密:患者敏感信息使用AES加密存储
- 操作审计:基于Spring AOP记录关键操作日志
- 数据脱敏:返回前端的数据经过Jackson自定义序列化处理
4.2 医保对接难点
各地医保接口标准不一,我们抽象出通用适配层:
code复制医保适配层架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 标准接口层 │ ←→ │ 省级适配层 │ ←→ │ 市级适配层 │
└─────────────┘ └─────────────┘ └─────────────┘
5. 部署与监控方案
5.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
registration-service:
image: registry.example.com/reg:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
5.2 监控指标
- Prometheus采集的关键指标:
- 挂号成功率
- 接口响应时间P99
- 号源库存变化趋势
- 业务告警规则:
- 5分钟内失败挂号数>100
- 支付成功率<95%
6. 典型问题排查记录
6.1 号源状态不一致问题
现象:偶尔出现号源已约但系统显示可约
排查过程:
- 检查分布式事务日志
- 发现Seata全局锁超时时间设置过短(原配置3s)
- 复杂业务场景下事务执行超时
解决方案:
properties复制# 调整Seata配置
seata.tx-service.timeout=30
seata.client.tm.degrade-check-period=2000
6.2 微信支付回调丢失
现象:部分用户支付成功但订单状态未更新
根本原因:医院网络策略拦截了微信回调请求
解决步骤:
- 增加支付状态主动查询任务
- 实现补偿机制:
java复制@Scheduled(cron = "0 */5 * * * ?")
public void checkPaymentStatus() {
// 查询超过15分钟未支付的订单
List<Order> orders = orderMapper.selectTimeoutOrders();
orders.forEach(order -> {
PaymentStatus status = wechatPayService.queryPayment(order.getNo());
if (status == PaymentStatus.SUCCESS) {
orderService.confirmPayment(order.getId());
}
});
}
7. 源码结构说明(30571版本)
项目采用多模块设计:
code复制hospital-registration
├── registration-api // 接口定义
├── registration-service // 业务实现
├── registration-gateway // API网关
├── registration-job // 定时任务
└── registration-admin // 管理后台
关键配置示例(application.yml):
yaml复制spring:
datasource:
url: jdbc:mysql://cluster-mysql:3306/registration?useSSL=false
hikari:
maximum-pool-size: 20
connection-timeout: 30000
redis:
sentinel:
master: mymaster
nodes: redis-sentinel-1:26379,redis-sentinel-2:26379
8. 开发经验与建议
-
测试策略:
- 使用Testcontainers进行集成测试
- 重点测试并发场景:使用JMeter模拟200并发挂号
-
代码规范:
- 医疗系统必须编写详细的JavaDoc
- 关键业务方法添加@Log注解记录操作日志
-
性能优化:
- 发现MyBatis的N+1查询问题后,改用
标签优化 - 添加Spring Cache注解减少数据库访问
- 发现MyBatis的N+1查询问题后,改用
这个项目让我深刻体会到医疗系统开发的特殊性 - 既要处理高并发技术挑战,又要满足严格的合规要求。建议开发类似系统时,提前与医院信息科充分沟通业务流程,特别注意各地医保政策的差异。系统上线后我们持续优化了3个月才达到稳定状态,其中最大的教训是:医疗系统的异常处理必须做到万无一失。
