9.5 KiB
9.5 KiB
内镜清洗流程管理系统 - 设计思路
一、项目背景
医院内镜清洗流程复杂多变,不同医院有不同的业务需求:
- 有的医院需要晨洗,有的不需要
- 有的医院机洗后可以直接结束,有的必须继续手工流程
- 有的医院对步骤时间有特殊要求
- 流程步骤可能随时调整
二、核心问题
2.1 旧系统的问题
- 硬编码流程:流程步骤写死在代码中,修改困难
- 状态黑盒:使用数组存储状态,不知道在哪里被修改
- 无法扩展:新增流程类型需要修改大量代码
- 分布式问题:多台机器部署时,批次号无法保持一致
2.2 需要支持的灵活场景
- 医院A:不需要晨洗
- 医院B:只有部分镜子需要晨洗
- 医院C:机洗后不允许刷终末漂洗,只能刷干燥或结束
- 医院D:只需要清洗和结束两个步骤
- 医院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 │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────┘
策略执行顺序:
-
前置策略(按配置顺序执行)
MorningWashStrategy:判断是否需要晨洗TimeValidationStrategy:验证步骤时间间隔
-
节点核心逻辑
- 调用
canHandle()判断当前节点是否能处理 - 调用
handle()执行业务逻辑
- 调用
-
后置策略
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);