HIS(Hospital Information System)作为医院运营的核心大脑,对系统的稳定性、高并发处理能力及数据一致性有着极高的要求。本文将基于 Spring Cloud Alibaba + MySQL + Redis 架构,深入探讨现代HIS系统的核心模块划分、诊间结算高并发应对方案以及电子病历(EMR)版本控制实现,并附关键业务代码。
一、 HIS 系统业务全景与核心架构
医院信息系统(HIS)涵盖门诊、住院、药房、药库、财务等多个子系统。在传统单体架构下,高高峰期的挂号与缴费极易导致数据库死锁甚至宕机。因此,现代HIS普遍采用**微服务拆分+读写分离**的架构思路。
1.1 业务模块拆分
根据业务边界,我们将HIS拆分为以下核心微服务:
-
his-gateway:统一 API 网关,负责鉴权、限流(Sentinel)、路由转发。
-
his-patient:患者服务,管理建档、卡号映射、医保身份校验。
-
his-outpatient:门诊服务,涵盖挂号、排班、诊间开单、医嘱管理。
-
his-pharmacy:药房药库服务,处理发药、扣减库存、盘点。
-
his-finance:财务结算服务,负责日结、对接医保局/第三方支付。
二、 核心技术难点与解决方案
2.1 门诊高并发抢号与库存扣减
在早高峰挂号/预约阶段,号源属于典型的"高并发热点资源"。为了防止超卖,我们引入 Redis + Lua 脚本 进行原子化号源扣减。
号源扣减 Lua 脚本实现
```lua
-- KEYS1: 号源 Key, ARGV1: 扣减数量 (通常为1)
local quotaKey = KEYS1
local decrementNum = tonumber(ARGV1)
local currentQuota = tonumber(redis.call('get', quotaKey) or "0")
if currentQuota < decrementNum then
return -1 -- 号源不足
else
redis.call('decrby', quotaKey, decrementNum)
return currentQuota - decrementNum -- 返回剩余号源
end
```
在 Java 端调用:
```java
@Service
@Slf4j
public class ScheduleServiceImpl implements ScheduleService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String SCRIPT_LUA =
"local quotaKey = KEYS1\n" +
"local decrementNum = tonumber(ARGV1)\n" +
"local currentQuota = tonumber(redis.call('get', quotaKey) or '0')\n" +
"if currentQuota < decrementNum then return -1\n" +
"else redis.call('decrby', quotaKey, decrementNum) return currentQuota - decrementNum end";
@Override
public boolean lockScheduleQuota(String scheduleId) {
String key = "his:schedule:quota:" + scheduleId;
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(SCRIPT_LUA);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), "1");
if (result != null && result >= 0) {
// 发送异步 RocketMQ 消息,持久化订单数据至 MySQL
return true;
}
return false;
}
}
```
2.2 电子病历(EMR)的版本控制与修改痕迹留存
根据医疗行业规范(如三级医院评审标准),电子病历必须记录修改痕迹,严禁直接 UPDATE 覆盖原数据。
我们采用 **快照表 + 增量版本号** 的设计模式:
```sql
CREATE TABLE `emr_record_history` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`emr_id` bigint(20) NOT NULL COMMENT '病历主表ID',
`patient_id` bigint(20) NOT NULL COMMENT '患者ID',
`version` int(11) NOT NULL COMMENT '版本号',
`doctor_id` bigint(20) NOT NULL COMMENT '修改医生ID',
`content_json` json NOT NULL COMMENT '病历结构化数据快照',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_emr_version` (`emr_id`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电子病历历史修改痕迹表';
```
结合 Java 代码,在医生每次保存病历时,采用乐观锁提交更新,同时插入历史痕迹:
```java
@Transactional(rollbackFor = Exception.class)
public void updateEmrRecord(EmrDTO emrDTO) {
// 1. 查询当前最新版本
EmrRecord currentRecord = emrRepository.findById(emrDTO.getEmrId());
// 2. 保存历史快照
EmrRecordHistory history = new EmrRecordHistory();
BeanUtils.copyProperties(currentRecord, history);
history.setVersion(currentRecord.getVersion());
emrHistoryRepository.save(history);
// 3. 更新主表并将版本号 + 1
currentRecord.setContentJson(emrDTO.getContentJson());
currentRecord.setVersion(currentRecord.getVersion() + 1);
currentRecord.setUpdateTime(new Date());
emrRepository.save(currentRecord);
}
```
三、 HIS系统部署与运维优化建议
-
**医保接口容错**:由于医保局网关响应时间不可控,调用医保接口必须配置 Resilience4j / Sentinel熔断降级机制,超时时间建议控制在 3000ms 以内,避免拉垮本地门诊队列。
-
读写分离机制:药房排队大屏、患者叫号大屏查询频率极高,务必将此类查询路由至 MySQL 从库或 Elasticsearch,严禁占用主库资源。
-
日志审计:医疗系统对操作日志要求极高(如处方修改、退费操作),必须通过 Spring AOP 统一收集日志并推送到 ELK 堆栈做安全审计。
四、 总结与展望
HIS 系统的搭建不仅是简单的 CRUD,更涉及到**高并发号源管理、严格的医疗审计控制以及复杂的外部接口对接**(医保、PACS、LIS 等)。本文介绍的微服务拆分及热点扣减思路,已在实际项目中得到验证。
后续文章将继续分享《HIS 医疗保险结算接口(医保贯标)踩坑全记录》,欢迎关注!
```