/**
|
* ============================================================================
|
* 住院状态机集成指南
|
* ============================================================================
|
*
|
* 一、状态机文件清单
|
* ============================================================================
|
*
|
* 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;
|