# 内镜清洗流程管理系统 - DIY扩展指南 ## 一、扩展概述 本系统采用责任链模式 + 策略模式设计,提供了良好的扩展性。你可以: - 添加新的流程节点 - 添加新的策略 - 自定义流程配置 - 修改现有节点行为 ## 二、添加新的流程节点 ### 2.1 场景示例 假设医院需要一个"预处理"步骤,在清洗之前进行。 ### 2.2 创建节点类 ```php readerType !== '预处理') { return false; } // 上一个步骤必须是空或结束 $validSteps = ['', '结束', '内镜取出']; return in_array($context->currentStep, $validSteps); } /** * 具体处理逻辑 */ protected function doHandle(ProcessContext $context): ProcessContext { // 更新步骤 // $context->lastStep = $context->currentStep; $context->currentStep = '预处理'; // 记录操作时间 $context->setStepLastTime('预处理', date('Y-m-d H:i:s')); // TODO: 插入数据库记录 $context->needDatabaseOperation = true; $context->dbOperation = 'insert'; // TODO: 发送WebSocket通知 $context->needWebSocketNotify = true; return $context; } } ``` ### 2.3 注册节点 ```php use app\flow\ProcessEngine; use app\flow\nodes\PreprocessNode; $engine = ProcessEngine::createStandard(); // 创建节点实例 $preprocessNode = new PreprocessNode(); // 添加策略 $preprocessNode->addStrategy($engine->getStrategy('voice_generation')); // 插入到责任链中(在清洗节点之前) $washNode = $engine->getNode('清洗'); $preprocessNode->setNext($washNode); // 更新链头 // 注意:这里需要修改 ProcessEngine 的内部实现 ``` ### 2.4 通过配置添加 ```php use app\flow\config\ProcessConfig; $config = new ProcessConfig([ 'steps' => [ ['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true], ['code' => '预处理', 'class' => 'PreprocessNode', 'enabled' => true], // 新增 ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true], ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true], ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true], ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], ], ]); $engine = new ProcessEngine($config); ``` ## 三、添加新的策略 ### 3.1 场景示例 假设需要添加一个"操作员验证策略",验证是否已刷人员卡。 ### 3.2 创建策略类 ```php operatorId)) { $context->setError('请先刷人员卡'); return $context; } // 检查人员卡是否有效 if (!$this->isValidOperator($context->operatorId)) { $context->setError('人员卡无效'); return $context; } return $context; } /** * 验证操作员是否有效 * TODO: 从数据库查询 */ protected function isValidOperator(string $operatorId): bool { // TODO: 查询数据库验证操作员 // SQL: SELECT COUNT(*) FROM ect_user WHERE user_id = ? AND status = 1 return true; } /** * 判断策略是否适用 * 只有清洗步骤需要验证操作员 */ public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool { return $node->getCode() === '清洗'; } /** * 获取策略名称 */ public function getName(): string { return '操作员验证策略'; } } ``` ### 3.3 注册策略 ```php use app\flow\ProcessEngine; use app\flow\strategies\OperatorValidationStrategy; $engine = ProcessEngine::createStandard(); // 创建策略实例 $operatorStrategy = new OperatorValidationStrategy(); // 添加到引擎 $engine->addStrategy('operator_validation', $operatorStrategy); // 将策略添加到指定节点 $washNode = $engine->getNode('清洗'); $washNode->addStrategy($operatorStrategy); ``` ## 四、修改现有节点行为 ### 4.1 继承并覆盖 ```php logCustomInfo($context); return $context; } /** * 自定义验证逻辑 */ public function canHandle(ProcessContext $context): bool { // 先调用父类验证 if (!parent::canHandle($context)) { return false; } // 添加额外的验证条件 // 例如:某些类型的内镜不能在此节点处理 if ($context->endoscopeType === '特殊镜') { return false; } return true; } /** * 自定义日志 */ protected function logCustomInfo(ProcessContext $context): void { // 自定义日志逻辑 error_log("[CustomWash] 内镜: {$context->endoscopeName}, 时间: " . date('Y-m-d H:i:s')); } } ``` ### 4.2 使用配置替换节点 ```php $config = new ProcessConfig([ 'steps' => [ ['code' => '清洗', 'class' => 'app\flow\nodes\custom\CustomWashNode', 'enabled' => true], // ... 其他步骤 ], ]); ``` ## 五、自定义流程配置加载器 ### 5.1 从数据库加载配置 ```php [ 'mode' => 'daily_first', ], 'steps' => self::loadStepsFromDb($hospitalId), 'time_validation' => [ 'durations' => self::loadDurationsFromDb($hospitalId), ], ]; return new ProcessConfig($configData); } /** * 从数据库加载步骤配置 */ protected static function loadStepsFromDb(string $hospitalId): array { // TODO: 查询数据库 // SQL: SELECT * FROM ect_hospital_steps WHERE hospital_id = ? ORDER BY step_order return [ ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true], ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true], ['code' => '结束', 'class' => 'EndNode', 'enabled' => true], ]; } /** * 从数据库加载时长配置 */ protected static function loadDurationsFromDb(string $hospitalId): array { // TODO: 查询数据库 // SQL: SELECT * FROM ect_hospital_durations WHERE hospital_id = ? return [ '清洗' => 300, '消毒' => 300, ]; } } ``` ### 5.2 使用加载器 ```php use app\flow\config\DatabaseConfigLoader; // 从数据库加载配置 $config = DatabaseConfigLoader::load('hospital_001'); $processor = new FlowProcessor($config); ``` ## 六、自定义语音生成器 ### 6.1 创建语音生成器 ```php processType) { case '手工洗': $voice = $this->generateManualWashVoice($context); break; case '机洗': $voice = $this->generateMachineWashVoice($context); break; default: $voice = $this->generateDefaultVoice($context); } // 添加内镜名称 $name = str_replace(['北院', '南院', '电子'], '', $context->endoscopeName); return $name . $voice; } /** * 手工洗语音 */ protected function generateManualWashVoice(ProcessContext $context): string { $templates = [ '清洗' => '手工清洗开始,请认真清洗', '漂洗' => '漂洗开始', '消毒' => '消毒开始,请确保消毒时间', '终末漂洗' => '终末漂洗开始', '干燥' => '干燥开始', '结束' => '手工清洗流程结束', ]; return $templates[$context->currentStep] ?? $context->currentStep . '完成'; } /** * 机洗语音 */ protected function generateMachineWashVoice(ProcessContext $context): string { $templates = [ '机洗' => '机器清洗开始', '结束' => '机洗流程结束', ]; return $templates[$context->currentStep] ?? $context->currentStep . '完成'; } /** * 默认语音 */ protected function generateDefaultVoice(ProcessContext $context): string { return $context->currentStep . '完成'; } } ``` ### 6.2 集成到策略 ```php // 修改 VoiceGenerationStrategy protected function generateNormalVoice(ProcessContext $context, ProcessNodeInterface $node): string { // 使用自定义语音生成器 $generator = new CustomVoiceGenerator(); return $generator->generate($context); } ``` ## 七、自定义数据库操作 ### 7.1 创建数据库处理器 ```php dbOperation === 'insert') { $this->insertAction($context); } elseif ($context->dbOperation === 'update') { $this->updateAction($context); } } /** * 插入新记录 */ protected function insertAction(ProcessContext $context): void { // TODO: 实现插入逻辑 $sql = "INSERT INTO ect_actions ( batch_no, action_type, action_type_name, process_name, reader_id, reader_no, opuser_type, opuser_id, opuser_rfid, opuser_name, endoscope_id, endoscope_rfid, endoscope_name, created_at, op_starttime ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; $params = [ $context->batchNo, $this->getActionType($context->processType), $context->processType, $context->currentStep, $context->readerId, $context->readerNo, 2, // 清洗人员 $context->operatorId, $context->operatorRfid, $context->operatorName, $context->endoscopeId, $context->cardNo, $context->endoscopeName, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), ]; // Database::execute($sql, $params); } /** * 更新记录 */ protected function updateAction(ProcessContext $context): void { // TODO: 实现更新逻辑 $sql = "UPDATE ect_actions SET op_endtime = ? WHERE batch_no = ? ORDER BY action_id DESC LIMIT 1"; // Database::execute($sql, [date('Y-m-d H:i:s'), $context->batchNo]); } /** * 获取操作类型编码 */ protected function getActionType(string $processType): int { $mapping = [ '诊疗' => 0, '手工洗' => 1, '机洗' => 2, '测漏' => 7, '存储' => 8, ]; return $mapping[$processType] ?? 1; } } ``` ### 7.2 集成到 FlowProcessor ```php // 修改 FlowProcessor::saveToDatabase() protected function saveToDatabase(ProcessContext $context): void { $handler = new CustomDbHandler(); $handler->save($context); } ``` ## 八、扩展最佳实践 ### 8.1 命名规范 - 节点类:`XxxNode`,位于 `app/flow/nodes/` - 策略类:`XxxStrategy`,位于 `app/flow/strategies/` - 配置类:`XxxConfig`,位于 `app/flow/config/` ### 8.2 测试建议 ```php // 单元测试示例 class PreprocessNodeTest { public function testCanHandle() { $node = new PreprocessNode(); $context = ProcessContext::create([ 'readerType' => '预处理', 'currentStep' => '', ]); assert($node->canHandle($context) === true); } } ``` ### 8.3 调试技巧 ```php // 打印流程上下文 var_dump($context); // 打印责任链结构 $node = $engine->getChainHead(); while ($node !== null) { echo $node->getName() . ' -> '; $node = $node->getNext(); } ``` ## 九、扩展示例汇总 | 扩展类型 | 难度 | 参考章节 | |---------|------|---------| | 添加新节点 | 低 | 2.1-2.4 | | 添加新策略 | 低 | 3.1-3.3 | | 修改节点行为 | 中 | 4.1-4.2 | | 自定义配置加载 | 中 | 5.1-5.2 | | 自定义语音 | 低 | 6.1-6.2 | | 自定义数据库 | 中 | 7.1-7.2 | ## 十、常见问题 ### Q1: 如何调试新添加的节点? 在 `doHandle()` 方法中添加日志输出,查看上下文数据。 ### Q2: 策略执行顺序如何控制? 策略按照添加顺序执行,可以使用 `setPhase('before'/'after')` 控制执行阶段。 ### Q3: 如何禁用默认策略? 可以覆盖 `attachStrategies()` 方法或创建自定义引擎。 ### Q4: 如何处理数据库事务? 在 `FlowProcessor::handleResult()` 中添加事务控制逻辑。