3471deb3f1
- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext - 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示 - 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法 - 增加日志记录细节,便于调试策略执行中断时的错误信息 - 优化代码注释,提升代码可读性和维护性
387 lines
12 KiB
PHP
387 lines
12 KiB
PHP
<?php
|
|
|
|
namespace tests\flow;
|
|
|
|
use app\flow\config\ProcessConfig;
|
|
use app\flow\context\ProcessContext;
|
|
use app\flow\ProcessEngine;
|
|
use app\flow\vo\EndoscopeInfo;
|
|
use app\flow\vo\OperatorInfo;
|
|
use app\flow\vo\ReaderInfo;
|
|
use app\utils\Logger;
|
|
|
|
/**
|
|
* 虚拟流程处理器
|
|
*
|
|
* 用于测试环境的刷卡流程模拟器。
|
|
* 不依赖真实的 FlowProcessor,不连接数据库,不发送语音。
|
|
* 支持完整的刷卡流程模拟和验证。
|
|
*/
|
|
class VirtualityFlowProcessor
|
|
{
|
|
private ProcessEngine $engine;
|
|
private array $environment;
|
|
private string $envPath;
|
|
|
|
/** @var array 当前操作员会话缓存 (readerId => operatorInfo) */
|
|
private array $operatorSessions = [];
|
|
|
|
/** @var ProcessContext[] 执行历史 */
|
|
private array $history = [];
|
|
|
|
/** @var array 当前内镜的流程状态 (endoscopeId => ProcessContext) */
|
|
private array $endoscopeStates = [];
|
|
|
|
/**
|
|
* 构造函数
|
|
*
|
|
* @param ProcessConfig|null $config 流程配置
|
|
* @param string|null $envPath 环境配置文件路径
|
|
*/
|
|
public function __construct(?ProcessConfig $config = null, ?string $envPath = null)
|
|
{
|
|
$this->envPath = $envPath ?? (__DIR__ . '/../resources/default_environment.json');
|
|
$this->loadEnvironment();
|
|
$this->engine = ProcessEngine::create($config ?? ProcessConfig::createStandard());
|
|
}
|
|
|
|
/**
|
|
* 加载环境配置
|
|
*/
|
|
private function loadEnvironment(): void
|
|
{
|
|
if (!file_exists($this->envPath)) {
|
|
throw new \RuntimeException("环境配置文件不存在: {$this->envPath}");
|
|
}
|
|
|
|
$content = file_get_contents($this->envPath);
|
|
$this->environment = json_decode($content, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new \RuntimeException("环境配置文件解析失败: " . json_last_error_msg());
|
|
}
|
|
}
|
|
|
|
// ==================== 静态工厂方法 ====================
|
|
|
|
/**
|
|
* 创建标准流程处理器
|
|
*/
|
|
public static function create(?ProcessConfig $config = null, ?string $envPath = null): self
|
|
{
|
|
return new self($config, $envPath);
|
|
}
|
|
|
|
/**
|
|
* 创建标准手工洗流程处理器
|
|
*/
|
|
public static function createStandard(?string $envPath = null): self
|
|
{
|
|
return new self(ProcessConfig::createStandard(), $envPath);
|
|
}
|
|
|
|
/**
|
|
* 创建无晨洗流程处理器
|
|
*/
|
|
public static function createNoMorningWash(?string $envPath = null): self
|
|
{
|
|
return new self(ProcessConfig::createNoMorningWash(), $envPath);
|
|
}
|
|
|
|
/**
|
|
* 创建机洗流程处理器
|
|
*/
|
|
public static function createMachineWash(?string $envPath = null): self
|
|
{
|
|
return new self(ProcessConfig::createMachineWash(), $envPath);
|
|
}
|
|
|
|
// ==================== 刷卡模拟方法 ====================
|
|
|
|
/**
|
|
* 模拟刷人员卡
|
|
*
|
|
* @param string $operatorName 操作员名称(环境配置中定义)
|
|
* @param string $readerType 读卡器类型
|
|
* @return ProcessContext
|
|
*/
|
|
public function swipeOperatorCard(string $operatorName, string $readerType = '清洗'): ProcessContext
|
|
{
|
|
$operatorData = $this->environment['operators'][$operatorName]
|
|
?? throw new \InvalidArgumentException("未找到操作员: {$operatorName}");
|
|
$readerData = $this->environment['readers'][$readerType]
|
|
?? throw new \InvalidArgumentException("未找到读卡器: {$readerType}");
|
|
|
|
// 保存操作员会话
|
|
$this->operatorSessions[$readerData['id']] = $operatorData;
|
|
|
|
// 构建上下文
|
|
$context = VirtualContextBuilder::create($this->envPath)
|
|
->operator($operatorName)
|
|
->reader($readerType)
|
|
->asOperatorCard()
|
|
->withEngineConfig($this->engine->getConfig())
|
|
->build();
|
|
|
|
// 人员卡不走流程链,直接返回
|
|
$result = $context->builder()
|
|
->voiceMessage('请刷内镜卡')
|
|
->build();
|
|
|
|
$this->history[] = $result;
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 模拟刷内镜卡
|
|
*
|
|
* @param string $endoscopeName 内镜名称(环境配置中定义)
|
|
* @param string $readerType 读卡器类型
|
|
* @return ProcessContext
|
|
*/
|
|
public function swipeEndoscopeCard(string $endoscopeName, string $readerType): ProcessContext
|
|
{
|
|
$readerData = $this->environment['readers'][$readerType]
|
|
?? throw new \InvalidArgumentException("未找到读卡器: {$readerType}");
|
|
$endoscopeData = $this->environment['endoscopes'][$endoscopeName]
|
|
?? throw new \InvalidArgumentException("未找到内镜: {$endoscopeName}");
|
|
|
|
// 获取内镜当前状态
|
|
$currentState = $this->endoscopeStates[$endoscopeData['id']] ?? null;
|
|
|
|
// 构建上下文
|
|
$builder = VirtualContextBuilder::create($this->envPath)
|
|
->endoscope($endoscopeName)
|
|
->reader($readerType)
|
|
->withEngineConfig($this->engine->getConfig());
|
|
|
|
// 如果有操作员会话,添加操作员信息
|
|
if (isset($this->operatorSessions[$readerData['id']])) {
|
|
$opData = $this->operatorSessions[$readerData['id']];
|
|
$builder->customOperator($opData['id'], $opData['name'], $opData['rfid']);
|
|
}
|
|
|
|
// 继承之前的流程状态
|
|
if ($currentState !== null) {
|
|
$builder->currentStep($currentState->getCurrentStep())
|
|
->batchNo($currentState->getBatchNo())
|
|
->processType($currentState->getProcessType());
|
|
} else {
|
|
// 新流程
|
|
$builder->newProcess();
|
|
}
|
|
|
|
$context = $builder->build();
|
|
|
|
// 未刷人员卡检查
|
|
if (!$context->hasOperator() && !isset($this->operatorSessions[$readerData['id']])) {
|
|
$result = $context->builder()
|
|
->error(\app\flow\enum\VoiceMessage::PLEASE_SWIPE_OPERATOR)
|
|
->build();
|
|
$this->history[] = $result;
|
|
return $result;
|
|
}
|
|
|
|
// 执行流程
|
|
$result = $this->engine->execute($context);
|
|
|
|
// 保存状态
|
|
$this->endoscopeStates[$endoscopeData['id']] = $result;
|
|
$this->history[] = $result;
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 快捷方法:完整刷卡(先刷人员卡再刷内镜卡)
|
|
*
|
|
* @param string $operatorName 操作员名称
|
|
* @param string $endoscopeName 内镜名称
|
|
* @param string $readerType 读卡器类型
|
|
* @return ProcessContext 返回内镜卡刷卡结果
|
|
*/
|
|
public function swipe(string $operatorName, string $endoscopeName, string $readerType): ProcessContext
|
|
{
|
|
Logger::info("测试刷卡:{$operatorName}, {$endoscopeName}, {$readerType}");
|
|
// 刷人员卡
|
|
$result = $this->swipeOperatorCard($operatorName, $readerType);
|
|
// 当前语音
|
|
Logger::info("当前语音:{$result->getFullVoice()}");
|
|
|
|
// 刷内镜卡
|
|
Logger::info("测试刷卡:{$endoscopeName}, {$readerType}");
|
|
$result = $this->swipeEndoscopeCard($endoscopeName, $readerType);
|
|
Logger::info("当前流程:{}", [$result->getProcessType()]);
|
|
Logger::info("当前批次号:{}", [$result->getBatchNo()]);
|
|
Logger::info("当前步骤:{}", [$result->getCurrentStep()]);
|
|
Logger::info("上一个步骤类型:{}", [$result->getPreviousAction()->action_type??"null"]);
|
|
Logger::info("上一个步骤:{}", [$result->getPreviousAction()->process_name??"null"]);
|
|
Logger::info("当前语音:{$result->getFullVoice()}\n");
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 执行完整的手工洗流程
|
|
*
|
|
* @param string $operatorName 操作员名称
|
|
* @param string $endoscopeName 内镜名称
|
|
* @return array<string, ProcessContext> 每个步骤的执行结果
|
|
*/
|
|
public function executeFullManualWash(string $operatorName, string $endoscopeName): array
|
|
{
|
|
$steps = ['清洗', '漂洗', '消毒', '终末漂洗', '干燥'];
|
|
$results = [];
|
|
|
|
foreach ($steps as $step) {
|
|
$results[$step] = $this->swipe($operatorName, $endoscopeName, $step);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* 执行完整的机洗流程
|
|
*
|
|
* @param string $operatorName 操作员名称
|
|
* @param string $endoscopeName 内镜名称
|
|
* @return ProcessContext
|
|
*/
|
|
public function executeMachineWash(string $operatorName, string $endoscopeName): ProcessContext
|
|
{
|
|
return $this->swipe($operatorName, $endoscopeName, '机洗');
|
|
}
|
|
|
|
// ==================== 状态管理方法 ====================
|
|
|
|
/**
|
|
* 重置所有状态
|
|
*/
|
|
public function reset(): self
|
|
{
|
|
$this->operatorSessions = [];
|
|
$this->endoscopeStates = [];
|
|
$this->history = [];
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 清除指定内镜的状态
|
|
*/
|
|
public function clearEndoscopeState(string $endoscopeName): self
|
|
{
|
|
$endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null;
|
|
if ($endoscopeData !== null) {
|
|
unset($this->endoscopeStates[$endoscopeData['id']]);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 清除指定读卡器的操作员会话
|
|
*/
|
|
public function clearOperatorSession(string $readerType): self
|
|
{
|
|
$readerData = $this->environment['readers'][$readerType] ?? null;
|
|
if ($readerData !== null) {
|
|
unset($this->operatorSessions[$readerData['id']]);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 手动设置内镜状态
|
|
*/
|
|
public function setEndoscopeState(string $endoscopeName, ProcessContext $context): self
|
|
{
|
|
$endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null;
|
|
if ($endoscopeData !== null) {
|
|
$this->endoscopeStates[$endoscopeData['id']] = $context;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
// ==================== 查询方法 ====================
|
|
|
|
/**
|
|
* 获取执行历史
|
|
*/
|
|
public function getHistory(): array
|
|
{
|
|
return $this->history;
|
|
}
|
|
|
|
/**
|
|
* 获取最后一次执行结果
|
|
*/
|
|
public function getLastResult(): ?ProcessContext
|
|
{
|
|
return end($this->history) ?: null;
|
|
}
|
|
|
|
/**
|
|
* 获取内镜当前状态
|
|
*/
|
|
public function getEndoscopeState(string $endoscopeName): ?ProcessContext
|
|
{
|
|
$endoscopeData = $this->environment['endoscopes'][$endoscopeName] ?? null;
|
|
if ($endoscopeData === null) {
|
|
return null;
|
|
}
|
|
return $this->endoscopeStates[$endoscopeData['id']] ?? null;
|
|
}
|
|
|
|
/**
|
|
* 获取流程引擎
|
|
*/
|
|
public function getEngine(): ProcessEngine
|
|
{
|
|
return $this->engine;
|
|
}
|
|
|
|
/**
|
|
* 获取环境配置
|
|
*/
|
|
public function getEnvironment(): array
|
|
{
|
|
return $this->environment;
|
|
}
|
|
|
|
/**
|
|
* 创建上下文构建器
|
|
*/
|
|
public function createContextBuilder(): VirtualContextBuilder
|
|
{
|
|
return VirtualContextBuilder::create($this->envPath)
|
|
->withEngineConfig($this->engine->getConfig());
|
|
}
|
|
|
|
// ==================== 断言辅助方法 ====================
|
|
|
|
/**
|
|
* 获取语音内容(用于断言)
|
|
*/
|
|
public function getVoice(?ProcessContext $context = null): string
|
|
{
|
|
$ctx = $context ?? $this->getLastResult();
|
|
return $ctx?->getFullVoice() ?? '';
|
|
}
|
|
|
|
/**
|
|
* 检查是否成功
|
|
*/
|
|
public function isSuccess(?ProcessContext $context = null): bool
|
|
{
|
|
$ctx = $context ?? $this->getLastResult();
|
|
return $ctx?->isSuccess() ?? false;
|
|
}
|
|
|
|
/**
|
|
* 获取当前步骤
|
|
*/
|
|
public function getCurrentStep(?ProcessContext $context = null): string
|
|
{
|
|
$ctx = $context ?? $this->getLastResult();
|
|
return $ctx?->getCurrentStep() ?? '';
|
|
}
|
|
}
|