feat: 实现TCP Server
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
# 内镜清洗流程管理系统 - 设计思路
|
||||
|
||||
## 一、项目背景
|
||||
|
||||
医院内镜清洗流程复杂多变,不同医院有不同的业务需求:
|
||||
- 有的医院需要晨洗,有的不需要
|
||||
- 有的医院机洗后可以直接结束,有的必须继续手工流程
|
||||
- 有的医院对步骤时间有特殊要求
|
||||
- 流程步骤可能随时调整
|
||||
|
||||
## 二、核心问题
|
||||
|
||||
### 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);
|
||||
```
|
||||
@@ -0,0 +1,470 @@
|
||||
# 内镜清洗流程管理系统 - 使用与配置
|
||||
|
||||
## 一、快速开始
|
||||
|
||||
### 1.1 基础使用示例
|
||||
|
||||
```php
|
||||
use app\flow\FlowProcessor;
|
||||
use app\flow\config\ProcessConfig;
|
||||
use app\net\PacketContext;
|
||||
|
||||
// 接收刷卡数据
|
||||
public function onMessage(TcpConnection $connection, $data): void
|
||||
{
|
||||
// 1. 解析数据包
|
||||
$packet = PacketParserFactory::parse($data);
|
||||
$packetContext = new PacketContext($connection, $packet);
|
||||
|
||||
// 2. 创建流程处理器(使用标准配置)
|
||||
$config = ProcessConfig::createStandard();
|
||||
$processor = new FlowProcessor($config);
|
||||
|
||||
// 3. 处理流程
|
||||
$result = $processor->process($packetContext);
|
||||
|
||||
// 4. 输出结果
|
||||
if ($result->success) {
|
||||
echo "语音播报: " . $result->getFullVoice();
|
||||
echo "当前步骤: " . $result->currentStep;
|
||||
echo "批次号: " . $result->batchNo;
|
||||
} else {
|
||||
echo "错误: " . $result->errorMessage;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 二、配置方式
|
||||
|
||||
### 2.1 预置配置
|
||||
|
||||
```php
|
||||
use app\flow\config\ProcessConfig;
|
||||
|
||||
// 标准完整流程
|
||||
$config = ProcessConfig::createStandard();
|
||||
|
||||
// 无晨洗流程
|
||||
$config = ProcessConfig::createNoMorningWash();
|
||||
|
||||
// 简化流程(只清洗)
|
||||
$config = ProcessConfig::createSimple();
|
||||
|
||||
// 机洗流程
|
||||
$config = ProcessConfig::createMachineWash();
|
||||
|
||||
// 无干燥流程
|
||||
$config = ProcessConfig::createNoDry();
|
||||
|
||||
// 仅干燥流程
|
||||
$config = ProcessConfig::createDryOnly();
|
||||
```
|
||||
|
||||
### 2.2 自定义配置
|
||||
|
||||
```php
|
||||
use app\flow\config\ProcessConfig;
|
||||
|
||||
$config = new ProcessConfig([
|
||||
// 定义的 steps 是带有顺序的
|
||||
// 流程步骤配置
|
||||
'steps' => [
|
||||
['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
|
||||
['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
|
||||
['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
|
||||
],
|
||||
|
||||
// 晨洗配置
|
||||
'morning_wash' => [
|
||||
'mode' => 'storage_time', // 根据存储时间判断
|
||||
'storage_threshold' => 4, // 4小时阈值
|
||||
'morning_start_time' => '06:00:00',
|
||||
],
|
||||
|
||||
// 时间验证配置
|
||||
'time_validation' => [
|
||||
'durations' => [
|
||||
'清洗' => 300, // 5分钟
|
||||
'消毒' => 600, // 10分钟(自定义)
|
||||
],
|
||||
],
|
||||
|
||||
// 语音模板配置
|
||||
'voice_templates' => [
|
||||
'custom' => [
|
||||
'清洗' => '第一步清洗开始',
|
||||
'消毒' => '第三步消毒开始,请确保消毒时间',
|
||||
],
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### 2.3 动态调整配置
|
||||
|
||||
```php
|
||||
use app\flow\ProcessEngine;
|
||||
|
||||
$engine = ProcessEngine::createStandard();
|
||||
|
||||
// 禁用某些步骤
|
||||
$engine->disableNode('干燥');
|
||||
$engine->disableNode('终末漂洗');
|
||||
|
||||
// 启用步骤
|
||||
$engine->enableNode('干燥');
|
||||
|
||||
// 设置自定义语音
|
||||
$engine->setStepVoice('清洗', '请开始清洗');
|
||||
|
||||
// 设置晨洗模式
|
||||
$engine->setMorningWashMode('none');
|
||||
```
|
||||
|
||||
## 三、晨洗模式配置
|
||||
|
||||
### 3.1 不需要晨洗
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'none',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### 3.2 所有镜子都需要晨洗
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'all',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### 3.3 根据存储时间判断(义乌模式)
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'storage_time',
|
||||
'storage_threshold' => 4, // 超过4小时需要晨洗
|
||||
'morning_start_time' => '06:00:00',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### 3.4 每天第一次需要晨洗(忠县模式)
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'daily_first',
|
||||
'morning_start_time' => '06:00:00',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### 3.5 特定类型镜子需要晨洗
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'specific_types',
|
||||
'specific_types' => ['胃镜', '十二指肠镜'],
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
## 四、医院特殊流程配置
|
||||
|
||||
### 4.1 机洗后不允许刷终末漂洗
|
||||
|
||||
```php
|
||||
use app\flow\ProcessEngine;
|
||||
|
||||
$engine = ProcessEngine::createStandard();
|
||||
|
||||
// 获取终末漂洗节点并配置
|
||||
$finalRinseNode = $engine->getNode('终末漂洗');
|
||||
$finalRinseNode->setAllowAfterMachineWash(false);
|
||||
```
|
||||
|
||||
**效果**:机洗完成后,刷卡到终末漂洗读卡器会提示错误,只能刷干燥或结束。
|
||||
|
||||
### 4.2 机洗后必须直接刷干燥
|
||||
|
||||
```php
|
||||
$dryNode = $engine->getNode('干燥');
|
||||
$dryNode->setRequireDirectAfterMachineWash(true);
|
||||
```
|
||||
|
||||
**效果**:机洗完成后,必须刷干燥或结束,不能刷其他步骤。
|
||||
|
||||
### 4.3 自定义步骤时间
|
||||
|
||||
```php
|
||||
use app\flow\strategies\TimeValidationStrategy;
|
||||
|
||||
$strategy = new TimeValidationStrategy([
|
||||
'durations' => [
|
||||
'清洗' => 600, // 10分钟
|
||||
'消毒' => 900, // 15分钟
|
||||
],
|
||||
]);
|
||||
|
||||
$engine->addStrategy('time_validation', $strategy);
|
||||
```
|
||||
|
||||
### 4.4 自定义语音模板
|
||||
|
||||
```php
|
||||
$engine->setStepVoice('清洗', '第一步:请开始清洗内镜');
|
||||
$engine->setStepVoice('消毒', '第三步:消毒时间必须达到5分钟');
|
||||
$engine->setStepVoice('结束', '清洗流程完成,请妥善保管');
|
||||
```
|
||||
|
||||
## 五、从配置文件加载
|
||||
|
||||
### 5.1 创建配置文件
|
||||
|
||||
```php
|
||||
// config/flow/hospital_a.php
|
||||
return [
|
||||
'name' => '医院A-无晨洗流程',
|
||||
'morning_wash' => [
|
||||
'mode' => 'none',
|
||||
],
|
||||
'steps' => [
|
||||
['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],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 5.2 加载配置
|
||||
|
||||
```php
|
||||
$config = ProcessConfig::fromFile(__DIR__ . '/config/flow/hospital_a.php');
|
||||
$processor = new FlowProcessor($config);
|
||||
```
|
||||
|
||||
## 六、全局配置类(Config.php)
|
||||
|
||||
### 6.1 配置类说明
|
||||
|
||||
系统提供全局配置单例类 `app\config\Config`,用于统一管理数据库配置和自定义流程配置:
|
||||
|
||||
```php
|
||||
use app\config\Config;
|
||||
|
||||
// 获取配置实例
|
||||
$config = Config::getInstance();
|
||||
|
||||
// 获取数据库配置
|
||||
$databaseConfig = $config->database;
|
||||
|
||||
// 获取自定义流程配置(从 app/config/custom_process_config.php 加载)
|
||||
$customProcess = $config->customProcess;
|
||||
```
|
||||
|
||||
### 6.2 配置文件位置
|
||||
|
||||
自定义流程配置文件位于:`app/config/custom_process_config.php`
|
||||
|
||||
该文件返回一个数组,包含多种预设的医院流程配置:
|
||||
|
||||
```php
|
||||
return [
|
||||
'standard' => [...], // 标准完整流程
|
||||
'no_morning_wash' => [...], // 无晨洗流程
|
||||
'partial_morning_wash' => [...], // 部分镜子晨洗(义乌模式)
|
||||
'no_dry' => [...], // 无干燥流程
|
||||
'dry_only' => [...], // 仅干燥流程
|
||||
'machine_wash' => [...], // 机洗流程
|
||||
'simple' => [...], // 简化流程
|
||||
'custom_voice' => [...], // 自定义语音流程
|
||||
];
|
||||
```
|
||||
|
||||
### 6.3 使用全局配置创建流程
|
||||
|
||||
```php
|
||||
use app\config\Config;
|
||||
use app\flow\ProcessConfig;
|
||||
use app\flow\FlowProcessor;
|
||||
|
||||
// 获取全局配置
|
||||
$globalConfig = Config::getInstance();
|
||||
|
||||
// 从全局配置中获取特定医院配置
|
||||
$hospitalConfig = $globalConfig->customProcess['no_morning_wash'] ?? null;
|
||||
|
||||
if ($hospitalConfig) {
|
||||
// 创建流程配置
|
||||
$processConfig = ProcessConfig::fromArray($hospitalConfig);
|
||||
|
||||
// 创建流程处理器
|
||||
$processor = new FlowProcessor($processConfig);
|
||||
|
||||
// 处理刷卡请求...
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 配置优先级
|
||||
|
||||
配置加载优先级(从高到低):
|
||||
1. 代码中动态设置的配置(`$engine->setStepVoice()`)
|
||||
2. 从配置文件加载的配置(`ProcessConfig::fromFile()`)
|
||||
3. 全局配置类中的配置(`Config::getInstance()->customProcess`)
|
||||
4. 默认配置(`ProcessConfig::createStandard()`)
|
||||
|
||||
## 七、多医院配置示例
|
||||
|
||||
```php
|
||||
class HospitalFlowManager
|
||||
{
|
||||
protected array $processors = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// 医院A:标准流程
|
||||
$this->processors['hospital_a'] = new FlowProcessor(
|
||||
ProcessConfig::createStandard()
|
||||
);
|
||||
|
||||
// 医院B:无晨洗
|
||||
$this->processors['hospital_b'] = new FlowProcessor(
|
||||
ProcessConfig::createNoMorningWash()
|
||||
);
|
||||
|
||||
// 医院C:义乌模式
|
||||
$configC = new ProcessConfig([
|
||||
'morning_wash' => [
|
||||
'mode' => 'storage_time',
|
||||
'storage_threshold' => 4,
|
||||
],
|
||||
]);
|
||||
$this->processors['hospital_c'] = new FlowProcessor($configC);
|
||||
|
||||
// 医院D:机洗流程
|
||||
$this->processors['hospital_d'] = new FlowProcessor(
|
||||
ProcessConfig::createMachineWash()
|
||||
);
|
||||
}
|
||||
|
||||
public function process(string $hospitalId, PacketContext $packetContext): ProcessContext
|
||||
{
|
||||
$processor = $this->processors[$hospitalId] ?? $this->processors['hospital_a'];
|
||||
return $processor->process($packetContext);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 八、处理结果
|
||||
|
||||
### 8.1 成功结果
|
||||
|
||||
```php
|
||||
$result = $processor->process($packetContext);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
// 基础信息
|
||||
$result->endoscopeId; // 内镜ID
|
||||
$result->endoscopeName; // 内镜名称
|
||||
$result->currentStep; // 当前步骤
|
||||
$result->processType; // 流程类型
|
||||
$result->batchNo; // 批次号
|
||||
|
||||
// 语音播报
|
||||
$voice = $result->getFullVoice(); // 完整语音(含内镜名称)
|
||||
$voice = $result->voiceMessage; // 仅流程语音
|
||||
|
||||
// 数据库标记
|
||||
$result->needDbInsert; // 是否需要插入数据库
|
||||
$result->dbOperation; // insert / update
|
||||
|
||||
// WebSocket标记
|
||||
$result->needWebSocketNotify; // 是否需要发送通知
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 失败结果
|
||||
|
||||
```php
|
||||
if (!$result->isSuccess()) {
|
||||
$errorMessage = $result->errorMessage; // 错误信息
|
||||
$voice = $result->voiceMessage; // 错误语音
|
||||
|
||||
// 常见错误:
|
||||
// - "刷错,清洗剩余180秒"(时间未到)
|
||||
// - "刷卡错误,请刷消毒"(步骤错误)
|
||||
// - "内镜未绑定"(数据错误)
|
||||
}
|
||||
```
|
||||
|
||||
## 九、数据库表结构参考
|
||||
|
||||
### 9.1 流程配置表(ect_meta_process)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `ect_meta_process` (
|
||||
`process_id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`process_name` varchar(255) COMMENT '流程名称(清洗/消毒/干燥等)',
|
||||
`process_duration` int COMMENT '流程时长(秒)',
|
||||
`process_type` varchar(20) COMMENT '洗消类型(手工洗/机洗等)',
|
||||
`optional` int COMMENT '0=选配 1=必配',
|
||||
`process_order` int COMMENT '顺序',
|
||||
`status` int COMMENT '1=启用 0=禁用',
|
||||
PRIMARY KEY (`process_id`)
|
||||
);
|
||||
```
|
||||
|
||||
### 9.2 操作记录表(ect_actions)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `ect_actions` (
|
||||
`action_id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`batch_no` varchar(50) COMMENT '批次号(同一次清洗流程相同)',
|
||||
`action_type` int COMMENT '操作类型',
|
||||
`action_type_name` varchar(50) COMMENT '操作类型名称',
|
||||
`process_name` varchar(50) COMMENT '流程名称(清洗/消毒等)',
|
||||
`reader_id` bigint COMMENT '读卡器ID',
|
||||
`reader_no` varchar(50) COMMENT '读卡器编号',
|
||||
`endoscope_id` bigint COMMENT '内镜ID',
|
||||
`endoscope_rfid` varchar(50) COMMENT '内镜RFID',
|
||||
`endoscope_name` varchar(100) COMMENT '内镜名称',
|
||||
`op_starttime` datetime COMMENT '操作开始时间',
|
||||
`op_endtime` datetime COMMENT '操作结束时间',
|
||||
`created_at` datetime,
|
||||
PRIMARY KEY (`action_id`)
|
||||
);
|
||||
```
|
||||
|
||||
## 十、常见问题
|
||||
|
||||
### Q1: 如何跳过某个步骤?
|
||||
```php
|
||||
$engine->disableNode('干燥');
|
||||
```
|
||||
|
||||
### Q2: 如何修改步骤时间?
|
||||
```php
|
||||
$config = new ProcessConfig([
|
||||
'time_validation' => [
|
||||
'durations' => ['消毒' => 600],
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
### Q3: 批次号如何保证分布式一致?
|
||||
系统会从数据库查询该内镜未完成的流程批次号,如果存在则使用已有批次号,不存在则生成新的。
|
||||
|
||||
### Q4: 如何添加自定义语音?
|
||||
```php
|
||||
$engine->setStepVoice('清洗', '自定义语音内容');
|
||||
```
|
||||
|
||||
### Q5: 如何支持新的流程类型?
|
||||
1. 创建新的节点类继承 `AbstractProcessNode`
|
||||
2. 在配置中添加步骤
|
||||
3. 实现 `canHandle()` 和 `doHandle()` 方法
|
||||
@@ -0,0 +1,632 @@
|
||||
# 内镜清洗流程管理系统 - 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()` 中添加事务控制逻辑。
|
||||
Reference in New Issue
Block a user