# 内镜清洗流程管理系统 - 设计思路 ## 一、项目背景 医院内镜清洗流程复杂多变,不同医院有不同的业务需求: - 有的医院需要晨洗,有的不需要 - 有的医院机洗后可以直接结束,有的必须继续手工流程 - 有的医院对步骤时间有特殊要求 - 流程步骤可能随时调整 ## 二、核心问题 ### 2.1 旧系统的问题 1. **硬编码流程**:流程步骤写死在代码中,修改困难 2. **状态黑盒**:使用数组存储状态,不知道在哪里被修改 3. **无法扩展**:新增流程类型需要修改大量代码 4. **分布式问题**:多台机器部署时,批次号无法保持一致 ### 2.2 需要支持的灵活场景 1. 医院A:不需要晨洗 2. 医院B:只有部分镜子需要晨洗 3. 医院C:机洗后不允许刷终末漂洗,只能刷干燥或结束 4. 医院D:只需要清洗和结束两个步骤 5. 医院E:自定义每个步骤的语音播报 ## 三、设计方案 ### 3.1 架构模式 #### 责任链模式 (Chain of Responsibility) ``` Request → Node1 → Node2 → Node3 → ... → Response ↓ ↓ ↓ 处理或传递 处理或传递 处理或传递 ``` **优点**: - 每个节点只关心自己能否处理 - 节点可以动态添加/删除/禁用 - 流程顺序可配置 #### 策略模式 (Strategy) ``` Context → StrategyA → StrategyB → StrategyC ``` **优点**: - 晨洗判断逻辑可替换 - 时间验证规则可配置 - 语音生成模板可定制 ### 3.2 核心组件 #### ProcessContext(流程上下文) ```php class ProcessContext { // 明确字段,避免黑盒 public string $endoscopeId = ''; public string $currentStep = ''; public string $batchNo = ''; public array $stepLastTimes = []; // ... } ``` **设计原则**: - 所有状态都是明确的属性 - 不使用 `getExtra/setExtra` 黑盒方法 - 支持从 PacketContext 初始化 #### ProcessNode(流程节点) ```php interface ProcessNodeInterface { public function canHandle(ProcessContext $context): bool; public function handle(ProcessContext $context): ProcessContext; public function setNext(ProcessNodeInterface $next): ProcessNodeInterface; } ``` **节点列表**: - MorningWashNode:晨洗节点 - WashNode:清洗节点 - RinseNode:漂洗节点 - DisinfectNode:消毒节点 - FinalRinseNode:终末漂洗节点 - DryNode:干燥节点 - EndNode:结束节点 - MachineWashNode:机洗节点 #### ProcessStrategy(流程策略) ```php interface ProcessStrategyInterface { public function execute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext; } ``` **策略列表**: - MorningWashStrategy:晨洗判断(5种模式) - TimeValidationStrategy:时间验证 - VoiceGenerationStrategy:语音生成 ### 3.3 分布式批次号一致性 **问题**:机器A处理清洗,机器B处理消毒,如何保证批次号相同? **解决方案**: ```php public function getOrCreateBatchNo(bool $forceNew = false): string { // 1. 首先查询数据库获取该内镜未完成的批次号 $existingBatchNo = Database::query( "SELECT op_batchno FROM ect_actions WHERE endoscope_id = ? AND process_name != '结束' AND created_at >= CURDATE() ORDER BY action_id DESC LIMIT 1" ); // 2. 如果存在,使用已有的;如果不存在,生成新的 if ($existingBatchNo) { return $existingBatchNo; } return $this->generateBatchNo(); } ``` **批次号格式**:`YYYYMMDD + HHMMSS + 内镜ID(6位) + 随机数(4位)` - 示例:`202603031230450000011234` - 长度固定:24位 - 包含时间戳,便于排序 ## 四、流程执行流程 ### 4.1 整体流程图 ``` ┌─────────────────┐ │ 接收刷卡数据 │ │ PacketContext │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 查询数据库 │ │ - 内镜信息 │ │ - 历史记录 │ │ - 当前批次号 │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 创建ProcessContext│ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 执行责任链 │ │ - 晨洗判断 │ │ - 时间验证 │ │ - 节点处理 │ │ - 语音生成 │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 处理结果 │ │ - 插入数据库 │ │ - 语音播报 │ │ - WebSocket通知 │ └─────────────────┘ ``` ### 4.2 策略执行详解 责任链中的每个节点都可以配置多个策略,策略在节点处理前后执行: ``` ┌─────────────────────────────────────────┐ │ 节点处理流程 │ ├─────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 前置策略执行 │ → │ MorningWash │ │ │ │ │ │ Strategy │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 前置策略执行 │ → │ TimeValidat │ │ │ │ │ │ ionStrategy │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ 节点核心逻辑处理 │ │ │ │ (canHandle + handle) │ │ │ └─────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 后置策略执行 │ → │ VoiceGenerat│ │ │ │ │ │ ionStrategy │ │ │ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────┘ ``` **策略执行顺序**: 1. **前置策略**(按配置顺序执行) - `MorningWashStrategy`:判断是否需要晨洗 - `TimeValidationStrategy`:验证步骤时间间隔 2. **节点核心逻辑** - 调用 `canHandle()` 判断当前节点是否能处理 - 调用 `handle()` 执行业务逻辑 3. **后置策略** - `VoiceGenerationStrategy`:生成语音播报内容 **代码示例**: ```php // 在 AbstractProcessNode 中执行策略 final public function process(ProcessContext $context): ProcessContext { // 1. 执行前置策略 foreach ($this->preStrategies as $strategy) { $context = $strategy->execute($context, $this); } // 2. 检查是否能处理 if (!$this->canHandle($context)) { // 传递给下一个节点 if ($this->next !== null) { return $this->next->process($context); } return $context; } // 3. 执行节点核心逻辑 $context = $this->handle($context); // 4. 执行后置策略 foreach ($this->postStrategies as $strategy) { $context = $strategy->execute($context, $this); } return $context; } ``` **策略配置示例**: ```php // 在 ProcessConfig 中配置策略 'nodes' => [ 'wash' => [ 'class' => WashNode::class, 'pre_strategies' => [ MorningWashStrategy::class, // 晨洗判断 TimeValidationStrategy::class, // 时间验证 ], 'post_strategies' => [ VoiceGenerationStrategy::class, // 语音生成 ], ], ], ``` ## 五、配置化设计 ### 5.1 流程配置 ```php $config = [ 'steps' => [ ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], ], 'morning_wash' => [ 'mode' => 'none', // 不需要晨洗 ], ]; ``` ### 5.2 晨洗模式 - `none`:不需要晨洗 - `all`:所有镜子都需要 - `storage_time`:根据存储时间判断(义乌模式) - `daily_first`:每天第一次(忠县模式) - `specific_types`:特定类型镜子需要 ### 5.3 医院特殊配置 ```php // FinalRinseNode:机洗后不允许刷终末漂洗 $node->setAllowAfterMachineWash(false); // DryNode:机洗后必须直接刷干燥 $node->setRequireDirectAfterMachineWash(true); ```