feat: 实现TCP Server

This commit is contained in:
zimoyin
2026-03-02 21:59:43 +08:00
parent 043306819b
commit a79dfae57d
144 changed files with 15785 additions and 140 deletions
+307
View File
@@ -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);
```