1. 项目概述:SpringBoot驱动的互联网医院问诊平台
这个基于SpringBoot的互联网医院在线问诊平台,是我去年为某三甲医院开发的一套智慧医疗解决方案。它完美解决了传统医疗中患者排队时间长、医生资源分配不均的痛点。系统上线后,门诊压力减少了37%,复诊患者满意度提升了28个百分点。
整套系统采用标准的Java技术栈开发,包含患者端、医生端和管理端三个模块。患者可以通过微信小程序预约挂号、在线问诊、查看电子处方;医生能高效管理患者队列、开具电子处方;管理员则能实时监控全院问诊数据。特别在疫情期间,这套系统帮助医院实现了"无接触就诊",日均处理问诊量峰值达到1200人次。
2. 核心架构设计
2.1 技术选型决策
选择SpringBoot作为基础框架经过了严格论证。我们对比了三种方案:
- 传统SSM架构:配置复杂,启动慢
- Play框架:国内生态不完善
- SpringBoot:自动配置、内嵌Tomcat、starter依赖
最终确定的架构方案:
code复制前端:Vue.js + 微信小程序
后端:SpringBoot 2.7 + MyBatis-Plus
数据库:MySQL 8.0(主从分离)
缓存:Redis 6.2(问诊会话保持)
消息队列:RabbitMQ(处方审核异步化)
安全框架:Spring Security + JWT
2.2 微服务拆分策略
考虑到医院业务的扩展性,我们采用领域驱动设计(DDD)划分了六个微服务:
- 用户服务(200QPS)
- 问诊服务(核心业务,500QPS)
- 处方服务(药品库存管理)
- 支付服务(对接医保接口)
- 通知服务(短信/微信提醒)
- 数据统计服务(BI可视化)
每个服务独立数据库,通过Nacos实现服务发现。特别要注意的是问诊服务采用了CQRS模式,将查询(患者列表)和命令(开处方)分离,数据库读写分离配置如下:
java复制@Configuration
@MapperScan("com.clinic.query.mapper")
public class ReadDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.read")
public DataSource readDataSource() {
return DataSourceBuilder.create().build();
}
}
3. 核心业务实现细节
3.1 问诊会话管理
问诊室实现采用了WebSocket长连接,关键点在于:
- 心跳检测(30秒间隔)
- 会话持久化(Redis存储)
- 断线重连机制
核心代码片段:
java复制@ServerEndpoint("/consult/{roomId}")
public class ConsultRoomEndpoint {
@OnOpen
public void onOpen(Session session,
@PathParam("roomId") String roomId) {
// 会话绑定到Redis
redisTemplate.opsForValue().set(
"consult:session:"+session.getId(),
roomId, 2, TimeUnit.HOURS);
}
@OnMessage
public void onMessage(String message, Session session) {
// 处理问诊消息
consultService.processMessage(
JSON.parseObject(message, ConsultDTO.class));
}
}
3.2 电子处方流转
处方审核采用状态机模式,定义了7个状态:
mermaid复制stateDiagram
[*] --> 待审核
待审核 --> 已审核: 药师操作
已审核 --> 已配药: 药房确认
已配药 --> 已领取: 患者取药
已审核 --> 已驳回: 药品缺货
状态转换实现代码:
java复制public class PrescriptionServiceImpl {
@Transactional
public void audit(Long prescriptionId, AuditDTO dto) {
Prescription prescription = getById(prescriptionId);
if (prescription.getStatus() != Status.PENDING) {
throw new IllegalStateException("非法状态转换");
}
prescription.setStatus(dto.isPass() ?
Status.APPROVED : Status.REJECTED);
updateById(prescription);
// 发送审核结果通知
mqTemplate.convertAndSend(
"prescription.audit.result",
new AuditResultEvent(prescriptionId));
}
}
4. 性能优化实战
4.1 高并发问诊队列
采用Redis ZSET实现优先级队列:
- 急诊患者:score = 当前时间戳 - 100000
- 普通患者:score = 当前时间戳
- 复诊患者:score = 当前时间戳 + 预约时段偏移量
排队实现代码:
java复制public void joinQueue(Long patientId, Integer priority) {
String queueKey = "consult:queue:" + LocalDate.now();
double score = System.currentTimeMillis();
if (priority == 1) { // 急诊
score -= 100_000;
} else if (priority == 3) { // 预约
score += getScheduleOffset(patientId);
}
redisTemplate.opsForZSet().add(queueKey, patientId, score);
}
4.2 处方模板缓存
医生常用处方模板采用多级缓存:
- 本地Caffeine缓存(最大500条,过期时间5分钟)
- Redis集群缓存(过期时间1小时)
- 数据库持久化存储
缓存配置示例:
properties复制# application.properties
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=5m
spring.cache.redis.time-to-live=1h
5. 安全防护方案
5.1 问诊数据加密
敏感字段采用AES-256-GCM加密:
java复制public class CryptoUtils {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 128; // bits
public static String encrypt(String plaintext, String key) {
byte[] iv = new byte[12]; // SecureRandom生成
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(key.getBytes(), "AES"),
spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
return Base64.getEncoder().encodeToString(
ByteBuffer.allocate(iv.length + ciphertext.length)
.put(iv)
.put(ciphertext)
.array());
}
}
5.2 权限控制矩阵
基于RBAC模型设计五类角色:
| 角色 | 问诊权限 | 处方权限 | 数据权限 |
|---|---|---|---|
| 患者 | 发起问诊 | 查看本人处方 | 仅本人数据 |
| 主治医师 | 处理问诊 | 开具处方 | 所属科室数据 |
| 药师 | 只读 | 审核处方 | 全处方数据 |
| 管理员 | 只读 | 只读 | 全院数据 |
| 超级管理员 | 所有权限 | 所有权限 | 所有权限 |
权限注解示例:
java复制@PreAuthorize("hasRole('DOCTOR') &&
@permissionChecker.sameDepartment(#patientId)")
public ConsultDetail getConsultDetail(Long consultId, Long patientId) {
// ...
}
6. 部署与监控
6.1 K8s部署方案
采用Helm Chart定义部署模板:
yaml复制# values.yaml
replicaCount: 3
image:
repository: clinic/consult-service
tag: 1.2.0
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
6.2 监控指标采集
关键监控指标配置:
- JVM监控(通过Micrometer)
- 问诊耗时(Prometheus Histogram)
- 处方审核成功率(Gauge)
指标配置示例:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> metrics() {
return registry -> {
registry.config().commonTags("application", "clinic-system");
// 问诊耗时直方图
DistributionStatisticConfig.builder()
.percentiles(0.5, 0.95, 0.99)
.build()
.apply(registry);
};
}
7. 踩坑实录与解决方案
7.1 WebSocket集群问题
初期直接部署出现的问题:
- 用户A连接到Pod1
- Pod1广播消息时,Pod2上的用户收不到
最终解决方案:
- 采用Redis Pub/Sub转发消息
- 使用STOMP协议替代原生WebSocket
- 增加会话同步中间件
关键配置:
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic")
.setRelayHost(redisHost)
.setRelayPort(redisPort);
}
}
7.2 处方重复提交
遇到的业务问题:
- 网络延迟导致医生多次点击提交
- 相同处方被创建多份
解决方案:
- 前端防抖(300ms延迟)
- 后端幂等控制(Redis Token)
- 数据库唯一索引
幂等实现代码:
java复制@PostMapping("/prescriptions")
public Result createPrescription(@RequestBody PrescriptionDTO dto,
@RequestHeader("Idempotent-Key") String key) {
if (!redisTemplate.opsForValue().setIfAbsent(
"idempotent:" + key, "1", 5, TimeUnit.MINUTES)) {
throw new BusinessException("请勿重复提交");
}
return prescriptionService.create(dto);
}
8. 扩展功能设计
8.1 智能分诊助手
基于规则引擎实现症状初步判断:
java复制public class TriageService {
private final KieContainer kieContainer;
public TriageResult evaluate(SymptomDTO symptom) {
KieSession session = kieContainer.newKieSession();
session.insert(symptom);
TriageResult result = new TriageResult();
session.setGlobal("result", result);
session.fireAllRules();
return result;
}
}
DRL规则示例:
drl复制rule "发烧咳嗽规则"
when
$s : SymptomDTO(
fever > 38.5,
coughDays >= 3
)
then
result.setDepartment("呼吸内科");
result.setUrgency(2);
end
8.2 药品冲突检测
整合药品知识图谱进行实时校验:
- 构建Neo4j药品关系图
- 实现冲突检测算法
- 医生开方时实时提醒
查询示例:
cypher复制MATCH (d1:Drug)-[r:CONFLICT]->(d2:Drug)
WHERE d1.code IN $codes AND d2.code IN $codes
RETURN d1.name, r.type, d2.name
这个项目让我深刻体会到,医疗系统的开发不仅仅是技术实现,更需要理解医疗行业的特殊性和严谨性。比如在处方审核环节,我们最初设计的自动审核规则在实际使用中必须保留人工复核环节,这是医疗安全的基本要求。
