633 lines
15 KiB
Markdown
633 lines
15 KiB
Markdown
# 内镜清洗流程管理系统 - DIY扩展指南
|
|
|
|
## 一、扩展概述
|
|
|
|
本系统采用责任链模式 + 策略模式设计,提供了良好的扩展性。你可以:
|
|
- 添加新的流程节点
|
|
- 添加新的策略
|
|
- 自定义流程配置
|
|
- 修改现有节点行为
|
|
|
|
## 二、添加新的流程节点
|
|
|
|
### 2.1 场景示例
|
|
假设医院需要一个"预处理"步骤,在清洗之前进行。
|
|
|
|
### 2.2 创建节点类
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace app\flow\nodes;
|
|
|
|
use app\flow\ProcessContext;
|
|
|
|
/**
|
|
* 预处理节点
|
|
* 在正式清洗前进行预处理
|
|
*/
|
|
class PreprocessNode extends AbstractProcessNode
|
|
{
|
|
/**
|
|
* 获取节点名称
|
|
*/
|
|
public function getName(): string
|
|
{
|
|
return '预处理';
|
|
}
|
|
|
|
/**
|
|
* 获取节点编码
|
|
*/
|
|
public function getCode(): string
|
|
{
|
|
return '预处理';
|
|
}
|
|
|
|
/**
|
|
* 判断当前节点是否能处理该步骤
|
|
*/
|
|
public function canHandle(ProcessContext $context): bool
|
|
{
|
|
// 检查当前读卡器类型
|
|
if ($context->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
|
|
<?php
|
|
|
|
namespace app\flow\strategies;
|
|
|
|
use app\flow\ProcessContext;
|
|
use app\flow\nodes\ProcessNodeInterface;
|
|
|
|
/**
|
|
* 操作员验证策略
|
|
* 验证是否已刷人员卡
|
|
*/
|
|
class OperatorValidationStrategy extends AbstractStrategy
|
|
{
|
|
protected string $phase = 'before';
|
|
|
|
/**
|
|
* 执行策略
|
|
*/
|
|
protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext
|
|
{
|
|
// 检查是否已刷人员卡
|
|
if (empty($context->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
|
|
<?php
|
|
|
|
namespace app\flow\nodes\custom;
|
|
|
|
use app\flow\nodes\WashNode;
|
|
use app\flow\ProcessContext;
|
|
|
|
/**
|
|
* 自定义清洗节点
|
|
* 覆盖父类的某些行为
|
|
*/
|
|
class CustomWashNode extends WashNode
|
|
{
|
|
/**
|
|
* 覆盖处理逻辑
|
|
*/
|
|
protected function doHandle(ProcessContext $context): ProcessContext
|
|
{
|
|
// 调用父类逻辑
|
|
$context = parent::doHandle($context);
|
|
|
|
// 添加自定义逻辑
|
|
// 例如:记录额外的日志
|
|
$this->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
|
|
<?php
|
|
|
|
namespace app\flow\config;
|
|
|
|
/**
|
|
* 数据库配置加载器
|
|
*/
|
|
class DatabaseConfigLoader
|
|
{
|
|
/**
|
|
* 从数据库加载医院配置
|
|
*/
|
|
public static function load(string $hospitalId): ProcessConfig
|
|
{
|
|
// TODO: 从数据库查询医院配置
|
|
// SQL: SELECT * FROM ect_hospital_config WHERE hospital_id = ?
|
|
|
|
$configData = [
|
|
'morning_wash' => [
|
|
'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
|
|
<?php
|
|
|
|
namespace app\flow\voice;
|
|
|
|
use app\flow\ProcessContext;
|
|
|
|
/**
|
|
* 自定义语音生成器
|
|
*/
|
|
class CustomVoiceGenerator
|
|
{
|
|
/**
|
|
* 生成语音
|
|
*/
|
|
public function generate(ProcessContext $context): string
|
|
{
|
|
$voice = '';
|
|
|
|
// 根据医院配置选择语音模板
|
|
switch ($context->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
|
|
<?php
|
|
|
|
namespace app\flow\db;
|
|
|
|
use app\flow\ProcessContext;
|
|
|
|
/**
|
|
* 自定义数据库处理器
|
|
*/
|
|
class CustomDbHandler
|
|
{
|
|
/**
|
|
* 保存操作记录
|
|
*/
|
|
public function save(ProcessContext $context): void
|
|
{
|
|
if ($context->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()` 中添加事务控制逻辑。
|