Files
tcpserver-flow/配置自定义节点.md
T
zimoyin 9ddec3dfb9 refactor(flow): 优化流程配置及相关测试
- 在测试代码中统一使用 setPreviousAction 与 setDuration 方法,规范方法调用
- VirtualContextBuilder 允许 setDuration 接收 null,增强灵活性
- VirtualityFlowProcessor 计算步骤持续时间时改进判断逻辑,避免错误持续时间值
- 增加日志记录上一步骤开始时间,方便调试流程状态
- 自定义流程配置文件大幅重构,新增多种晨洗模式配置
- 新增机洗单步骤流程、无晨洗流程、每日首次晨洗、存储超时晨洗等多种流程样例
- 自定义流程支持 override_steps 为 true/false 的不同合并策略说明与示例
- 详细补充配置节点说明,增强配置文档准确性和用户理解
- 自定义语音模板扩展,支持多种语音播报及错误提示文本
- 更新时间验证配置,统一各流程中各步骤时间要求
- 补充“配置自定义节点流程指南”文档,详细说明配置原则、合并策略与使用建议
2026-03-13 18:59:36 +08:00

16 KiB
Raw Blame History

配置自定义节点流程指南

概述

本项目支持通过配置文件自定义洗消流程,可以灵活控制每个节点的前置条件、启用状态等。

配置文件位置

  • 文件路径./custom_process_config.php
  • 配置键选择:通过环境变量 FLOW_PROCESS_CONFIG_KEY 指定使用的配置
  • 配置键:通过环境变量 FLOW_USE_CUSTOM_PROCESS 来开启允许使用自定义配置

override_steps 参数详解

作用:控制自定义步骤与默认步骤的合并策略。

1. override_steps = true(完全覆盖模式)

行为

  • 完全使用配置文件中的 steps 列表
  • 不会创建配置中不存在的节点
  • 如果配置为空数组,则使用默认步骤列表
  • 适合:定义全新的流程、大幅简化流程

示例:机洗单步骤流程

'machine_wash' => [
    'override_steps' => true,  // 完全覆盖
    'steps' => [
        // 只定义需要的节点,其他节点不会创建
        ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
        ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
        ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
        ['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
    ],
]
// 结果:流程中只有这 4 个节点,清洗/漂洗/消毒等节点不会被创建

注意事项

  • ⚠️ 必须显式列出所有需要的节点
  • ⚠️ 漏掉的节点将不存在于流程中(不是禁用,是不存在)
  • ⚠️ 如果某个节点没配置但后续逻辑需要,会导致错误

2. override_steps = false(增量合并模式)

行为

  • 使用默认步骤列表作为基础
  • 用自定义步骤覆盖相同 code 的默认步骤
  • 自定义步骤中不存在的节点保持默认配置
  • 适合:微调特定节点的属性(如 enabled、required

示例:仅调整晨洗节点

'custom_morning' => [
    'override_steps' => false,  // 增量合并
    'steps' => [
        // 只配置晨洗节点,其他节点使用默认值
        ['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => true, 'required' => []],
    ],
]
// 结果:
// - 晨洗节点:使用自定义配置(enabled=true, required=[]
// - 清洗/漂洗/消毒等其他节点:使用默认配置

合并逻辑(伪代码):

// 1. 将默认步骤转为「code => StepConfig」的关联数组
$defaultStepsMap = [];
foreach (self::defaultSteps() as $defaultStep) {
    $defaultStepsMap[$defaultStep->code] = $defaultStep;
}

// 2. 遍历自定义步骤,覆盖默认步骤中相同 code 的元素
foreach ($customSteps as $customStep) {
    $defaultStepsMap[$customStep->code] = $customStep;  // 覆盖
}

// 3. 转回索引数组
$this->steps = array_values($defaultStepsMap);

注意事项

  • ⚠️ 只能修改已存在于默认列表中的节点
  • ⚠️ 无法添加默认列表中不存在的新节点(会被忽略)
  • ⚠️ 适合小范围调整,不适合大规模改动

3. 对比总结

特性 override_steps = true override_steps = false
基础列表 自定义 steps 默认 steps
未配置的节点 不存在 使用默认配置
相同 code 的节点 以自定义为准 以自定义为准
适用场景 全新流程、大幅简化 微调特定节点
风险 可能遗漏必需节点 无法添加新节点

4. 使用建议

使用 override_steps = true 的场景

  • 机洗单步骤流程(只需 4 个节点)
  • 无干燥流程(去掉干燥和终末漂洗)
  • 特殊医院定制的简化流程

使用 override_steps = false 的场景

  • 仅调整晨洗规则
  • 仅修改某个节点的 enabled 状态
  • 仅添加 required 约束
  • 临时测试某个节点的配置变更

默认推荐

  • 📌 大多数情况使用 true,明确列出所有需要的节点
  • 📌 只在确实只需要微调时使用 false

配置结构说明

完整配置示例

'custom_flow' => [
    'name' => '自定义流程名称',
    'description' => '流程描述',
    'override_steps' => true,  // 是否覆盖默认步骤
    
    // 晨洗配置
    'morning_wash' => [
        'mode' => 'none',  // none | daily_first | all | storage_time | specific_types
        'storage_threshold' => 4,  // 存储时间阈值(小时)- storage_time 模式使用
        'morning_start_time' => '00:00:00',  // 每日开始时间 - daily_first 模式使用
        'specific_types' => ['胃镜', '十二指肠镜'],  // 特定类型 - specific_types 模式使用
    ],
    
    // 步骤配置(核心)
    'steps' => [
        [
            'code' => '清洗',           // 节点标识码(必须与 ProcessNode 的 getCode() 一致)
            'class' => 'WashNode',      // 节点类名(不含命名空间)
            'enabled' => true,          // 是否启用该节点
            'required' => ['结束', '晨洗'],  // 可选:指定前置节点列表
        ],
        // ... 其他节点
    ],
    
    // 时间验证配置(秒)
    'time_validation' => [
        'durations' => [
            '晨洗' => 60,        // 晨洗至少 60 秒
            '清洗' => 120,       // 清洗至少 120 秒
            '漂洗' => 60,        // 漂洗至少 60 秒
            '消毒' => 300,       // 消毒至少 300 秒
            '终末漂洗' => 120,   // 终末漂洗至少 120 秒
            '干燥' => 30,        // 干燥至少 30 秒
            '机洗' => 360,       // 机洗至少 360 秒
        ],
        'enabled' => true,  // 是否启用时间验证
    ],
    
    // 语音模板配置
    'voice_templates' => [
        // 正常洗消流程语音
        'normal_wash' => [
            '晨洗' => '晨洗开始,请进行每日首次清洗准备',
            '清洗' => '第一步清洗开始,请认真清洗内镜外表面和管道',
            '漂洗' => '第二步漂洗开始,用流动水冲洗残留酶液',
            '消毒' => '第三步消毒开始,请确保消毒剂浓度和时间',
            '终末漂洗' => '第四步终末漂洗开始,用无菌水彻底冲洗',
            '干燥' => '最后一步干燥开始,用压力气枪吹干管道',
            '结束' => '清洗流程全部完成,请妥善保管内镜',
            '机洗' => '机洗开始,请将内镜放入清洗机',
        ],
        
        // 晨洗流程语音
        'morning_wash' => [
            '晨洗' => '晨洗模式启动,今日首次清洗需额外注意',
            '清洗' => '晨洗后的第一次清洗,请按标准流程操作',
        ],
        
        // 错误提示语音
        'error_messages' => [
            'NOT_ENOUGH_TIME' => '{step}时间不足,还需等待{time}秒',
            'PLEASE_SWIPE_OPERATOR' => '请先刷人员卡',
            'PLEASE_SWIPE_ENDOSCOPE' => '请刷内镜卡',
            'PLEASE_SWIPE_READER' => '请刷读卡器',
        ],
        
        // 重复刷卡提示
        'duplicate_check' => [
            'operator' => '您已刷过人员卡,无需重复刷卡',
            'endoscope' => '您已刷过内镜卡,无需重复刷卡',
            'reader' => '您已刷过读卡器,无需重复刷卡',
        ],
    ],
],

核心参数详解

1. steps[].code(节点标识码)

作用:唯一标识一个节点,必须与对应的 ProcessNode 子类的 getCode() 方法返回值一致。

可用节点码

  • 晨洗 - MorningWashNode
  • 重复刷卡 - DuplicateCheckNode
  • 清洗 - WashNode
  • 漂洗 - RinseNode
  • 消毒 - DisinfectNode
  • 终末漂洗 - FinalRinseNode
  • 干燥 - DryNode
  • 结束 - EndNode
  • 机洗 - MachineWashNode
  • 虚拟清洗机 - VirtualWashMachineNode
  • 存储 - StorageNode
  • 内镜放入 - StorageInNode
  • 内镜取出 - StorageOutNode
  • Close - CloseNode

2. steps[].class(节点类名)

作用:指定处理该节点的类名(不含命名空间)。

命名空间:所有节点类都在 app\flow\nodes\ 命名空间下。

示例

'class' => 'WashNode'  // 对应 \app\flow\nodes\WashNode

3. steps[].enabled(启用状态)

作用:控制节点是否启用。

  • true - 启用该节点
  • false - 禁用/跳过该节点

示例

// 机洗单步骤流程中,跳手工洗节点
['code' => '清洗', 'class' => 'WashNode', 'enabled' => false],
['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],

4. steps[].required(前置节点要求)

作用:指定当前节点的前一个节点可以是哪些。用于实现特殊流程控制。

类型:字符串数组(允许的前置节点 code 列表)

工作原理

  • 当流程执行到某个节点时,会检查上一个节点的 code 是否在 required 列表中
  • 如果不在列表中,流程会拒绝执行并提示错误
  • 如果不设置 required,则使用默认的配置

使用场景

场景 1:强制流程顺序

'steps' => [
    ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true, 'required' => ['结束', '晨洗']],
    ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true, 'required' => ['清洗']],
    ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true, 'required' => ['漂洗']],
    ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['消毒']],
    ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
    ['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥']],
]

这样确保必须按顺序执行:清洗 → 漂洗 → 消毒 → 终末漂洗 → 干燥 → 结束

场景 2:机洗介入

'steps' => [
    ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
    ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
    ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
    // 机洗可以在手工洗的任意阶段介入
    ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒']],
    ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['机洗']],
    ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
    ['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
]

场景 3:多路径汇聚

'steps' => [
    ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
    ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
    // 结束可以从干燥或机洗过来
    ['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
]

注意事项

  1. required 中的节点码必须在 steps 中存在
  2. 循环依赖可能会导致死锁(如 A 要求 B,B 要求 A)
  3. 如果节点设置了 required,则只能从指定的前驱节点过来

常用流程配置示例

1. 标准完整流程(不设置 required)

'standard' => [
    'name' => '标准完整流程',
    'override_steps' => true,
    'morning_wash' => ['mode' => 'daily_first'],
    'steps' => [
        ['code' => '晨洗', 'class' => 'MorningWashNode', '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],
        ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true],
    ],
]

2. 机洗单步骤流程(设置 required)

'machine_wash' => [
    'name' => '机洗单步骤流程',
    'override_steps' => true,
    'morning_wash' => ['mode' => 'none'],
    'steps' => [
        ['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
        ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true, 'required' => ['结束']],
        ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true, 'required' => ['清洗']],
        ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true, 'required' => ['漂洗']],
        ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒']],
        ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true, 'required' => ['机洗']],
        ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true, 'required' => ['终末漂洗']],
        ['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥']],
    ],
]

3. 无干燥流程

'no_dry' => [
    'name' => '无干燥流程',
    'override_steps' => true,
    'morning_wash' => ['mode' => 'none'],
    'steps' => [
        ['code' => '晨洗', 'class' => 'MorningWashNode', 'enabled' => false],
        ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
        ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
        ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
        ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => false],
        ['code' => '干燥', 'class' => 'DryNode', 'enabled' => false],
        ['code' => '结束', 'class' => 'EndNode', 'enabled' => true],
    ],
]

调试技巧

1. 查看当前使用的配置

# 在 .env 文件中查看
FLOW_PROCESS_CONFIG_KEY=standard

2. 日志输出

流程引擎会在日志中输出加载的配置信息:

ProcessConfig 初始化中...
加载 [steps] 步骤配置,来源:自定义配置

3. 测试配置有效性

运行单元测试验证配置:

phpunit tests/flow/node_flow/

常见问题

Q1: 配置后流程不生效?

检查清单

  1. 确认 .env 中的 FLOW_PROCESS_CONFIG_KEY 正确
  2. 确认 steps[].code 与节点类的 getCode() 一致
  3. 确认 steps[].class 类名存在且正确
  4. 清除缓存(如果有)

Q2: required 设置后流程卡住?

可能原因

  • 循环依赖:A 要求 BB 要求 A
  • 前置节点不存在于 steps 中
  • 前置节点被禁用(enabled=false

解决方法

  1. 检查 required 数组中的节点码是否拼写正确
  2. 确保前置节点在同一个配置的 steps 中且启用
  3. 暂时移除 required 测试基础流程

Q3: 如何同时支持手工洗和机洗?

方案:在 steps 中同时启用两种节点,通过 required 控制介入时机:

'steps' => [
    ['code' => '清洗', 'class' => 'WashNode', 'enabled' => true],
    ['code' => '漂洗', 'class' => 'RinseNode', 'enabled' => true],
    // 机洗可以在任意阶段介入
    ['code' => '机洗', 'class' => 'MachineWashNode', 'enabled' => true, 'required' => ['清洗', '漂洗', '消毒', '终末漂洗']],
    ['code' => '消毒', 'class' => 'DisinfectNode', 'enabled' => true],
    ['code' => '终末漂洗', 'class' => 'FinalRinseNode', 'enabled' => true],
    // 结束可以从干燥或机洗过来
    ['code' => '结束', 'class' => 'EndNode', 'enabled' => true, 'required' => ['干燥', '机洗']],
    ['code' => '干燥', 'class' => 'DryNode', 'enabled' => true],
]

相关文件

  • 配置文件app/config/custom_process_config.php
  • 配置加载类app/flow/config/ProcessConfig.php
  • 步骤配置类app/flow/config/StepConfig.php
  • 步骤集合类app/flow/config/StepsConfig.php
  • 流程引擎app/flow/ProcessEngine.php
  • 节点基类app/flow/nodes/ProcessNodeInterface.php
  • 抽象节点app/flow/nodes/AbstractProcessNode.php

开发建议

  1. 标准流程保持简单standard 配置不设置 required,保持灵活性
  2. 特殊流程才用 required:只在需要严格控制顺序时使用
  3. 充分测试:修改配置后运行完整的流程测试
  4. 注释清晰:为每个自定义配置添加详细的用途说明
  5. 版本管理:配置变更要在 Git 中记录清楚