/** * ============================================================================ * 住院状态机集成指南 * ============================================================================ * * 一、状态机文件清单 * ============================================================================ * * 1. 核心枚举 * └─ com.smartor.common.enums.InhospStateEnum * 定义状态及转换规则 * * 2. 异常类 * └─ com.smartor.common.exception.StateTransitionException * 处理非法状态转换 * * 3. 处理器接口 * └─ com.smartor.statemachine.handler.InhospStateHandler * 状态处理器接口 * * 4. 处理器实现 * ├─ com.smartor.statemachine.handler.impl.PreAdmissionHandler * │ 处理预入院(state=3) * ├─ com.smartor.statemachine.handler.impl.InHospitalHandler * │ 处理入院(state=0) * └─ com.smartor.statemachine.handler.impl.DischargeHandler * 处理出院(state=1) * * 5. 状态机管理器 * └─ com.smartor.statemachine.InhospStateMachine * 协调状态转换、管理分布式锁 * * 6. 门面服务 * └─ com.smartor.statemachine.InhospStateMachineService * 对外API接口 * * 7. 使用示例 * └─ com.smartor.statemachine.InhospStateMachineExample * 集成示例代码 * * * 二、如何在现有代码中集成 * ============================================================================ * * 原代码位置: * ServiceSLTDHealthcareRecordServiceImpl.processPatientInhospInfo (L561-677) * * 【方案1】完全替换(推荐用于新功能) * ----------------------------------------- * * @Autowired * private InhospStateMachineService stateMachineService; * * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) { * PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry); * stateMachineService.processStateChange(patMedInhosp, cry); * } * * * 【方案2】双写模式(推荐用于生产环境验证) * ----------------------------------------- * * @Autowired * private InhospStateMachineService stateMachineService; * * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) { * PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry); * * // 方式A:保留原有逻辑 * // processPatientInhospInfoOld(dto, patArchive, cry); * * // 方式B:使用状态机 * try { * boolean success = stateMachineService.processStateChange(patMedInhosp, cry); * log.info("【状态机】处理结果:{}, serialnum={}, cry={}", success, patMedInhosp.getSerialnum(), cry); * } catch (Exception e) { * log.error("【状态机】处理异常:serialnum={}, cry={}, 错误:{}", patMedInhosp.getSerialnum(), cry, e.getMessage()); * } * } * * * 【方案3】开关控制(推荐用于灰度发布) * ----------------------------------------- * * @Value("${inhosp.statemachine.enabled:false}") * private boolean stateMachineEnabled; * * @Autowired * private InhospStateMachineService stateMachineService; * * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) { * PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry); * * if (stateMachineEnabled) { * // 使用状态机 * stateMachineService.processStateChange(patMedInhosp, cry); * } else { * // 使用原有逻辑 * processPatientInhospInfoOld(dto, patArchive, cry); * } * } * * 配置文件 application.yml: * inhosp: * statemachine: * enabled: true # 开启状态机 * * * 三、数据库索引建议 * ============================================================================ * * 【选项1】允许多状态记录(推荐,与现有逻辑兼容) * ----------------------------------------- * CREATE UNIQUE INDEX uk_serialnum_orgid_state * ON pat_med_inhosp(serialnum, orgid, inhospstate); * * 优点: * - 与现有数据兼容 * - 只拦截相同状态的重复插入 * - 状态机处理器可以决定是新增还是更新 * * * 【选项2】强制单记录(需数据迁移) * ----------------------------------------- * CREATE UNIQUE INDEX uk_serialnum_orgid * ON pat_med_inhosp(serialnum, orgid); * * 优点: * - 数据一致性更强 * - 避免一患多记录问题 * * 缺点: * - 需要清理历史数据 * - 所有状态变更必须走更新 * * 数据清理脚本: * -- 保留每个患者最新状态的记录,删除其他记录 * DELETE t1 FROM pat_med_inhosp t1 * INNER JOIN ( * SELECT serialnum, orgid, MAX(inhospid) as max_id * FROM pat_med_inhosp * GROUP BY serialnum, orgid * HAVING COUNT(*) > 1 * ) t2 ON t1.serialnum = t2.serialnum * AND t1.orgid = t2.orgid * AND t1.inhospid < t2.max_id; * * * 四、状态机核心优势 * ============================================================================ * * 1. 并发安全 * ✅ 自动加分布式锁(Redis) * ✅ 锁粒度:serialnum + orgid * ✅ 超时时间:10秒(可配置) * ✅ Redis不可用时自动降级 * * 2. 状态校验 * ✅ 强制单向流转:3(预入院) → 0(入院) → 1(出院) * ✅ 阻止非法转换(如:预入院→出院) * ✅ 阻止重复操作(如:重复入院) * * 3. 代码简化 * ✅ 100+行if-else → 10行状态机调用 * ✅ 策略模式,易扩展 * ✅ 集中式状态管理 * * 4. 日志完善 * ✅ 每个状态转换都有日志 * ✅ 异常信息详细 * ✅ 便于问题排查 * * * 五、测试验证建议 * ============================================================================ * * 1. 单元测试 * - 测试状态枚举的转换规则 * - 测试每个Handler的逻辑 * - 测试状态机的锁机制 * * 2. 集成测试 * - 正常流程:预入院→入院→出院 * - 跳过预入院:直接入院→出院 * - 异常流程:重复操作、非法转换 * * 3. 并发测试 * - 使用JMeter模拟并发请求 * - 验证同一患者100并发不会重复插入 * - 验证Redis锁的有效性 * * 4. 生产验证 * - 先开启双写模式 * - 对比原逻辑与状态机结果 * - 验证1-2周后完全切换 * * * 六、配置参数(可选) * ============================================================================ * * application.yml 可添加以下配置: * * inhosp: * statemachine: * enabled: true # 是否启用状态机 * lock-timeout: 10 # 分布式锁超时时间(秒) * allow-direct-discharge: false # 是否允许无入院记录直接出院 * * * 七、常见问题 * ============================================================================ * * Q1: 状态机是否会影响性能? * A: 不会。状态机内部逻辑与原代码相似,只是结构更清晰。Redis锁的获取耗时在毫秒级。 * * Q2: Redis不可用时会怎样? * A: 状态机会自动降级为无锁模式,依然可以执行,但并发安全依赖数据库唯一索引。 * * Q3: 如何扩展新状态? * A: * 1. 在InhospStateEnum中添加新状态 * 2. 创建对应的Handler实现 * 3. 状态机会自动注册并识别 * * Q4: 状态机是否支持事务? * A: 支持。状态机调用本身可以在@Transactional方法中使用。 * * Q5: 出院后能否再入院? * A: 目前逻辑不允许。如果需要支持,建议在数据模型中增加"就诊批次"字段。 * * * 八、后续优化方向 * ============================================================================ * * 1. 状态变更历史表 * - 记录每次状态转换 * - 支持状态回溯 * - 便于审计 * * 2. 状态机可视化 * - 提供状态流转图 * - 实时监控状态分布 * * 3. 补偿机制 * - 支持状态回滚 * - 异常状态修复 * * 4. 配置化状态规则 * - 从数据库读取状态转换规则 * - 动态调整不需要重启 * * * ============================================================================ * 联系人:smartor开发团队 * 创建日期:2026-01-29 * ============================================================================ */ package com.smartor.statemachine;