Files
tcpserver-flow/app/flow/docs/01-设计思路.md
T
2026-03-08 22:58:56 +08:00

9.5 KiB
Raw Blame History

内镜清洗流程管理系统 - 设计思路

一、项目背景

医院内镜清洗流程复杂多变,不同医院有不同的业务需求:

  • 有的医院需要晨洗,有的不需要
  • 有的医院机洗后可以直接结束,有的必须继续手工流程
  • 有的医院对步骤时间有特殊要求
  • 流程步骤可能随时调整

二、核心问题

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(流程上下文)

class ProcessContext
{
    // 明确字段,避免黑盒
    public string $endoscopeId = '';
    public string $currentStep = '';
    public string $batchNo = '';
    public array $stepLastTimes = [];
    // ...
}

设计原则

  • 所有状态都是明确的属性
  • 不使用 getExtra/setExtra 黑盒方法
  • 支持从 PacketContext 初始化

ProcessNode(流程节点)

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(流程策略)

interface ProcessStrategyInterface
{
    public function execute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext;
}

策略列表

  • MorningWashStrategy:晨洗判断(5种模式)
  • TimeValidationStrategy:时间验证
  • VoiceGenerationStrategy:语音生成

3.3 分布式批次号一致性

问题:机器A处理清洗,机器B处理消毒,如何保证批次号相同?

解决方案

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:生成语音播报内容

代码示例

// 在 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;
}

策略配置示例

// 在 ProcessConfig 中配置策略
'nodes' => [
    'wash' => [
        'class' => WashNode::class,
        'pre_strategies' => [
            MorningWashStrategy::class,      // 晨洗判断
            TimeValidationStrategy::class,   // 时间验证
        ],
        'post_strategies' => [
            VoiceGenerationStrategy::class,  // 语音生成
        ],
    ],
],

五、配置化设计

5.1 流程配置

$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 医院特殊配置

// FinalRinseNode:机洗后不允许刷终末漂洗
$node->setAllowAfterMachineWash(false);

// DryNode:机洗后必须直接刷干燥
$node->setRequireDirectAfterMachineWash(true);