Files
tcpserver-flow/app/flow/context/ProcessContextBuilder.php
T
zimoyin f2ff4ae123 ai-chore(config): 调整流程配置及改进测试代码
- 将 FLOW_USE_CUSTOM_PROCESS 从 true 改为 false,禁用自定义流程
- 在 BlockTest 测试用例中改用 setBlockMode 方法设置阻断模式
- 设置统一的错误处理,将错误转为异常抛出
- 重命名 BlockTest 测试文件路径,优化测试组织结构
- 更新 IDE php include paths,调整依赖包引用顺序
- 删除无用的 tests/flow/Test.php 测试文件
- 微调 start.php、webman、windows.php 配置或代码模块
2026-03-11 13:48:40 +08:00

610 lines
19 KiB
PHP

<?php
namespace app\flow\context;
use app\config\Config;
use app\flow\config\ProcessConfig;
use app\flow\context\bean\EndoscopeInfo;
use app\flow\context\bean\ExecutionResult;
use app\flow\context\bean\MorningWashStatus;
use app\flow\context\bean\OperatorInfo;
use app\flow\context\bean\ProcessStatus;
use app\flow\context\bean\ReaderInfo;
use app\flow\context\bean\ReminderStatus;
use app\flow\context\bean\StorageStatus;
use app\flow\context\bean\VoiceState;
use app\flow\enum\DbOperationType;
use app\flow\enum\VoiceMessage;
use app\flow\context\bean\BatchNo;
use app\model\EctActions;
use app\net\PacketContext;
use app\repository\EctActionsRepository;
use app\repository\EndoscopeRepository;
use app\repository\ReaderRepository;
use app\repository\UserRepository;
use app\utils\Logger;
use Exception;
/**
* 流程上下文构造器
* 负责从各种数据源加载数据并构建不可变的 ProcessContext 实例
*/
class ProcessContextBuilder
{
private Config $config;
// ==================== 值对象 ====================
private EndoscopeInfo $endoscope;
private ReaderInfo $reader;
private OperatorInfo $operator;
private StorageStatus $storage;
private MorningWashStatus $morningWash;
private VoiceState $voice;
private ExecutionResult $result;
private ProcessStatus $processStatus;
private ReminderStatus $reminder;
// ==================== 原始数据 ====================
private ?PacketContext $packetContext = null;
private ?ProcessConfig $engineConfig = null;
private array $rawData = [];
// ==================== 标记 ====================
private bool $isOperatorCard = false;
private array $stepDurations = [];
// 晨洗开始时间(用于加载过程)
private string $morningStartTime = '00:00:00';
public function __construct()
{
$this->config = Config::getInstance();
// 初始化值对象为空对象
$this->endoscope = EndoscopeInfo::empty();
$this->reader = ReaderInfo::empty();
$this->operator = OperatorInfo::empty();
$this->storage = StorageStatus::notInStorage();
$this->morningWash = MorningWashStatus::notRequired();
$this->voice = VoiceState::empty();
$this->result = ExecutionResult::success();
$this->processStatus = ProcessStatus::empty();
$this->reminder = ReminderStatus::none();
}
/**
* 从 PacketContext 创建构造器
*/
public static function fromPacketContext(PacketContext $packetContext, array $additionalData = []): self
{
$builder = new self();
$builder->packetContext = $packetContext;
$builder->engineConfig = $additionalData['engineConfig'] ?? null;
if (empty($builder->engineConfig)) Logger::warn("fromPacketContext 需要通过 additionalData 传递 engineConfig");
// 获取原始卡号和读卡器编号
$cardNo = $packetContext->packet->card ?? '';
$readerNo = $packetContext->packet->reader ?? '';
$builder->rawData = $packetContext->packet->toArray();
// 获取晨洗开始时间
if ($builder->engineConfig !== null) {
$builder->morningStartTime = $builder->engineConfig->getMorningWashConfig()->morningStartTime;
}
// 1. 加载内镜/操作员信息
$builder->loadEndoscopeOrOperatorInfo($cardNo);
// 2. 加载读卡器信息
$builder->loadReaderInfo($readerNo);
// 3. 加载内镜操作记录相关信息(仅当内镜ID存在时执行)
if (!$builder->endoscope->isEmpty()) {
$builder->loadEndoscopeActionInfo();
$builder->loadStorageStatus();
}
// 4. 初始化晨洗状态
$builder->morningWash = new MorningWashStatus(
needMorningWash: false, // TODO: 由策略计算
morningWashed: false,
startTime: $builder->morningStartTime,
todayWashRecords: $builder->morningWash->todayWashRecords
);
// 5. 合并额外数据
foreach ($additionalData as $key => $value) {
if (property_exists($builder, $key) && $value !== null) {
$builder->$key = $value;
}
}
Logger::debug("从 PacketContext 创建 ProcessContextBuilder");
return $builder;
}
/**
* 从现有的 ProcessContext 创建构造器
* 用于基于现有上下文创建修改后的新实例
*/
public static function from(ProcessContext $context): self
{
$builder = new self();
// 复制值对象
$builder->endoscope = $context->getEndoscope();
$builder->reader = $context->getReader();
$builder->operator = $context->getOperator();
$builder->storage = $context->getStorage();
$builder->morningWash = $context->getMorningWash();
$builder->voice = $context->getVoice();
$builder->result = $context->getResult();
$builder->processStatus = $context->getProcessStatus();
$builder->reminder = $context->getReminder();
// 复制原始数据
$builder->packetContext = $context->getPacketContext();
$builder->engineConfig = $context->getEngineConfig();
$builder->rawData = $context->getRawData();
// 复制标记
$builder->isOperatorCard = $context->isOperatorCard();
$builder->stepDurations = $context->getStepDurations();
return $builder;
}
// ==================== 数据加载方法 ====================
/**
* 加载内镜或操作员信息
*/
private function loadEndoscopeOrOperatorInfo(string $cardNo): void
{
if (empty($cardNo)) {
return;
}
// 优先查询内镜信息
$endoscope = EndoscopeRepository::new()->findByCardNo($cardNo);
if ($endoscope !== null) {
$this->endoscope = new EndoscopeInfo(
id: (string)$endoscope->endoscope_id,
name: (string)$endoscope->endoscope_name,
cardNo: $cardNo,
type: (string)$endoscope->endoscope_type
);
return;
}
// 内镜无记录则查询人员卡信息
try {
$user = UserRepository::new()->findByRfid($cardNo);
if ($user !== null) {
$this->isOperatorCard = true;
$this->operator = new OperatorInfo(
id: (string)$user->user_id,
name: (string)$user->user_name,
rfid: (string)$user->user_rfid
);
}
} catch (Exception $e) {
Logger::error("[ProcessContextBuilder] 查询人员卡信息出错: {}", [$e->getMessage()]);
}
}
/**
* 加载读卡器信息
*/
private function loadReaderInfo(string $readerNo): void
{
if (empty($readerNo)) {
return;
}
$readerInfo = ReaderRepository::new()->findReaderInfo($readerNo);
if ($readerInfo !== null) {
$this->reader = new ReaderInfo(
no: $readerNo,
type: $readerInfo['readerType'],
id: $readerInfo['readerId']
);
}
}
/**
* 加载内镜操作记录相关信息
*/
private function loadEndoscopeActionInfo(): void
{
$actionsRepo = EctActionsRepository::new();
// 查询最后一条操作记录
$lastAction = $actionsRepo->findLastAction($this->endoscope->id, [0, 7, 8]);
if (empty($lastAction)) {
return;
}
$lastStepStartTime = $lastAction->op_starttime;
$duration = time() - strtotime($lastStepStartTime);
$this->handleLastAction($lastAction, $actionsRepo, $duration);
// 查询今日洗消记录数(晨洗判断)
$todayWashRecords = $actionsRepo->countTodayActions($this->endoscope->id, $this->morningStartTime, [0, 7, 8]);
Logger::debug("{} 内镜今日洗消记录数: {}", [$this->endoscope->name,$todayWashRecords]);
$this->morningWash = new MorningWashStatus(
needMorningWash: $this->morningWash->needMorningWash,
morningWashed: $this->morningWash->morningWashed,
startTime: $this->morningStartTime,
todayWashRecords: $todayWashRecords
);
}
/**
* 加载内镜存储状态
*/
private function loadStorageStatus(): void
{
$actionsRepo = EctActionsRepository::new();
// 查询最后一次存储操作记录
$lastStorageAction = $actionsRepo->findLastStorageAction($this->endoscope->id);
if ($lastStorageAction !== null) {
$isInStorage = ($lastStorageAction['process_name'] === '内镜放入');
$inTime = $isInStorage ? $lastStorageAction['op_starttime'] : null;
$this->storage = new StorageStatus(
isInStorage: $isInStorage,
lastAction: $lastStorageAction['process_name'],
inTime: $inTime
);
}
// 查询最后一次存储入库时间(义乌模式晨洗判断)
$storageTime = $actionsRepo->findLastStorageTime($this->endoscope->id);
if ($storageTime !== null) {
$this->storage = new StorageStatus(
isInStorage: $this->storage->isInStorage,
lastAction: $this->storage->lastAction,
inTime: $storageTime
);
}
}
/**
* 处理最后一条操作记录的核心逻辑
*/
private function handleLastAction(EctActions $lastAction, EctActionsRepository $actionsRepo, int $duration): void
{
// 设置基础操作信息
$currentStep = (string)$lastAction->process_name;
$actionStartTime = date('Y-m-d H:i:s');
$processType = '';
$batchNo = '';
// 处理批次号逻辑
if ($currentStep === '结束') {
$batchNo = $this->generateBatchNo();
} else {
$batchNo = (string)$lastAction->op_batchno;
$processType = (string)$lastAction->action_type_name;
}
$this->processStatus = new ProcessStatus(
currentStep: $currentStep,
processType: $processType,
batchNo: $batchNo,
actionStartTime: $actionStartTime,
duration: $duration,
previousAction: $lastAction
);
// 加载批次操作员信息
if ($currentStep !== '结束' && !empty($batchNo)) {
$this->loadBatchOperatorInfo($actionsRepo, $batchNo);
}
}
/**
* 加载批次对应的操作员信息
*/
private function loadBatchOperatorInfo(EctActionsRepository $actionsRepo, string $batchNo): void
{
$operator = $actionsRepo->findOperatorByBatchNo($batchNo);
if ($operator !== null) {
$this->operator = new OperatorInfo(
id: $operator['id'],
name: $operator['name'],
rfid: $operator['rfid']
);
}
}
/**
* 生成批次号
* @return string 批次号值
*/
private function generateBatchNo(): string
{
return BatchNo::generate($this->config->machineId)->value;
}
// ==================== 值对象设置方法 ====================
public function withEndoscope(EndoscopeInfo $endoscope): self
{
$this->endoscope = $endoscope;
return $this;
}
public function withReader(ReaderInfo $reader): self
{
$this->reader = $reader;
return $this;
}
public function withOperator(OperatorInfo $operator): self
{
$this->operator = $operator;
return $this;
}
public function withStorage(StorageStatus $storage): self
{
$this->storage = $storage;
return $this;
}
public function withMorningWash(MorningWashStatus $morningWash): self
{
$this->morningWash = $morningWash;
return $this;
}
public function withVoice(VoiceState $voice): self
{
$this->voice = $voice;
return $this;
}
public function withResult(ExecutionResult $result): self
{
$this->result = $result;
return $this;
}
public function withProcessStatus(ProcessStatus $processStatus): self
{
$this->processStatus = $processStatus;
return $this;
}
public function withReminder(ReminderStatus $reminder): self
{
$this->reminder = $reminder;
return $this;
}
// ==================== 流程状态便捷设置方法 ====================
public function withCurrentStep(string $currentStep): self
{
$this->processStatus = new ProcessStatus(
currentStep: $currentStep,
processType: $this->processStatus->processType,
batchNo: $this->processStatus->batchNo,
actionStartTime: $this->processStatus->actionStartTime,
duration: $this->processStatus->duration,
previousAction: $this->processStatus->previousAction
);
return $this;
}
public function withProcessType(string $processType): self
{
$this->processStatus = new ProcessStatus(
currentStep: $this->processStatus->currentStep,
processType: $processType,
batchNo: $this->processStatus->batchNo,
actionStartTime: $this->processStatus->actionStartTime,
duration: $this->processStatus->duration,
previousAction: $this->processStatus->previousAction
);
return $this;
}
public function withBatchNo(string $batchNo): self
{
$this->processStatus = new ProcessStatus(
currentStep: $this->processStatus->currentStep,
processType: $this->processStatus->processType,
batchNo: $batchNo,
actionStartTime: $this->processStatus->actionStartTime,
duration: $this->processStatus->duration,
previousAction: $this->processStatus->previousAction
);
return $this;
}
public function withDuration(?int $duration): self
{
$this->processStatus = new ProcessStatus(
currentStep: $this->processStatus->currentStep,
processType: $this->processStatus->processType,
batchNo: $this->processStatus->batchNo,
actionStartTime: $this->processStatus->actionStartTime,
duration: $duration,
previousAction: $this->processStatus->previousAction
);
return $this;
}
public function withPreviousAction(?EctActions $action): self
{
$this->processStatus = new ProcessStatus(
currentStep: $this->processStatus->currentStep,
processType: $this->processStatus->processType,
batchNo: $this->processStatus->batchNo,
actionStartTime: $this->processStatus->actionStartTime,
duration: $this->processStatus->duration,
previousAction: $action
);
return $this;
}
// ==================== 便捷设置方法(语义化) ====================
/**
* 设置成功状态
*/
public function success(): self
{
$this->result = new ExecutionResult(success: true);
return $this;
}
/**
* 设置错误状态和消息
*/
public function error(VoiceMessage $message): self
{
$this->voice = $this->voice->withError($message);
$this->result = $this->result->fail();
return $this;
}
/**
* 设置自定义错误
*/
public function customError(string $message): self
{
$this->voice = $this->voice->withMessage($message)->withError(VoiceMessage::CUSTOM);
$this->result = $this->result->fail();
return $this;
}
/**
* 设置语音消息
*/
public function voiceMessage(string|VoiceMessage $message): self
{
$this->voice = $this->voice->withMessage($message);
return $this;
}
/**
* 设置期望下一步
*/
public function expectedNextStep(VoiceMessage $expected): self
{
$this->voice = $this->voice->withExpectedNextStep($expected);
return $this;
}
/**
* 添加数据库操作
*/
public function dbOperation(DbOperationType $operation): self
{
$this->result = $this->result->addDbOperation($operation);
return $this;
}
/**
* 设置需要数据库操作
*/
public function needDatabaseOperation(bool $need = true): self
{
$this->result = new ExecutionResult(
success: $this->result->success,
needDatabaseOperation: $need,
dbOperations: $this->result->dbOperations,
needWebSocketNotify: $this->result->needWebSocketNotify,
skipNodeCount: $this->result->skipNodeCount
);
return $this;
}
/**
* 设置需要WebSocket通知
*/
public function needWebSocketNotify(bool $need = true): self
{
$this->result = $this->result->withWebSocketNotify($need);
return $this;
}
/**
* 设置跳过节点数
*/
public function skipNodeCount(int $count): self
{
$this->result = $this->result->withSkipNodeCount($count);
return $this;
}
// ==================== 提醒状态便捷设置方法 ====================
public function withNeedEnhanceWash(bool $need): self
{
$this->reminder = $this->reminder->withEnhanceWash($need);
return $this;
}
public function withNeedLeakTestRemind(bool $need): self
{
$this->reminder = $this->reminder->withLeakTestRemind($need);
return $this;
}
public function withNeedStorageRemind(bool $need): self
{
$this->reminder = $this->reminder->withStorageRemind($need);
return $this;
}
public function withLeakTestDone(bool $done, string $result = ''): self
{
$this->reminder = $this->reminder->withLeakTestDone($result);
return $this;
}
// ==================== 标记设置方法 ====================
public function withIsOperatorCard(bool $isOperatorCard): self
{
$this->isOperatorCard = $isOperatorCard;
return $this;
}
public function withStepDuration(string $stepCode, int $duration): self
{
$this->stepDurations[$stepCode] = $duration;
return $this;
}
// ==================== 构建方法 ====================
/**
* 构建不可变的 ProcessContext 实例
*/
public function build(): ProcessContext
{
Logger::debug("ProcessContextBuilder 构建 ProcessContext 完成");
return new ProcessContext(
endoscope: $this->endoscope,
reader: $this->reader,
operator: $this->operator,
storage: $this->storage,
morningWash: $this->morningWash,
voice: $this->voice,
result: $this->result,
processStatus: $this->processStatus,
reminder: $this->reminder,
packetContext: $this->packetContext,
engineConfig: $this->engineConfig,
rawData: $this->rawData,
isOperatorCard: $this->isOperatorCard,
stepDurations: $this->stepDurations,
);
}
}