3471deb3f1
- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext - 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示 - 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法 - 增加日志记录细节,便于调试策略执行中断时的错误信息 - 优化代码注释,提升代码可读性和维护性
290 lines
10 KiB
PHP
290 lines
10 KiB
PHP
<?php
|
||
|
||
namespace app\flow;
|
||
|
||
use app\flow\config\ProcessConfig;
|
||
use app\flow\context\ProcessContext;
|
||
use app\flow\context\ProcessContextBuilder;
|
||
use app\flow\enum\VoiceMessage;
|
||
use app\flow\OperatorSessionManager;
|
||
use app\net\PacketContext;
|
||
use app\repository\EctActionsRepository;
|
||
use app\repository\UserRepository;
|
||
use app\utils\Logger;
|
||
|
||
/**
|
||
* 流程处理器
|
||
* 整合 PacketContext、ProcessContext、ProcessEngine 的完整处理流程
|
||
*/
|
||
class FlowProcessor
|
||
{
|
||
/**
|
||
* 流程引擎
|
||
*/
|
||
public ProcessEngine $engine {
|
||
get {
|
||
return $this->engine;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构造函数
|
||
* @param ProcessEngine|ProcessConfig $value 流程引擎或流程配置
|
||
*/
|
||
public function __construct(ProcessEngine|ProcessConfig $value)
|
||
{
|
||
$config = $value instanceof ProcessConfig ? $value : new ProcessConfig();
|
||
$this->engine = $value instanceof ProcessEngine ? $value : ProcessEngine::create($config);
|
||
}
|
||
|
||
/**
|
||
* 处理刷卡请求
|
||
*
|
||
* @param PacketContext $packetContext 刷卡数据包上下文
|
||
* @param bool $isExecuteHandleResult 是否执行处理结果
|
||
* @return ProcessContext 处理结果上下文
|
||
*/
|
||
public function process(PacketContext $packetContext, bool $isExecuteHandleResult = true): ProcessContext
|
||
{
|
||
// 从 PacketContext 创建 ProcessContext
|
||
$context = ProcessContextBuilder::fromPacketContext($packetContext, ["engineConfig" => $this->engine->getConfig()])->build();
|
||
|
||
Logger::debug("[{}] card: {} reader({}): {} {}: {} step: {}", [
|
||
$context->isOperatorCard() ? '人员卡' : '内镜卡',
|
||
$context->isOperatorCard() ? $context->getOperator()->rfid : $context->getEndoscope()->cardNo,
|
||
empty($context->getReader()->no) ? '(未绑定)' : $context->getReader()->no,
|
||
$context->getReader()->no,
|
||
$context->isOperatorCard() ? 'user' : 'endoscope',
|
||
$context->isOperatorCard() ? $context->getOperator()->name : ($context->getEndoscope()->name ?: $context->getEndoscope()->id ?: '(未绑定)'),
|
||
$context->getCurrentStep() ?: '(新流程)',
|
||
]);
|
||
|
||
// 读卡器未绑定
|
||
if (empty($context->getReader()->id)) {
|
||
Logger::error('读卡器未绑定,放弃处理 card={}', [$context->isOperatorCard() ? $context->getOperator()->rfid : $context->getEndoscope()->cardNo]);
|
||
$context = $context->builder()->error(VoiceMessage::READER_NOT_BOUND)->build();
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 如果是人员卡,记录操作员信息后直接返回,不走流程链
|
||
if ($context->isOperatorCard()) {
|
||
$mgr = OperatorSessionManager::getInstance();
|
||
$mgr->setOperator(
|
||
$context->getReader()->id,
|
||
$context->getOperator()->id,
|
||
$context->getOperator()->name,
|
||
$context->getOperator()->rfid
|
||
);
|
||
$context = $context->builder()->error(VoiceMessage::PLEASE_SWIPE_ENDOSCOPE)->build();
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 如果内镜未绑定,返回错误
|
||
if ($context->getEndoscope()->isEmpty()) {
|
||
Logger::error('内镜未绑定,放弃处理 card={}', [$context->getEndoscope()->cardNo]);
|
||
$context = $context->builder()->error(VoiceMessage::CARD_NOT_BOUND)->build();
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 内镜卡:从 OperatorSessionManager 补充操作员信息
|
||
$mgr = OperatorSessionManager::getInstance();
|
||
if ($mgr->hasOperator($context->getReader()->id) || $context->hasOperator()) {
|
||
if ($mgr->hasOperator($context->getReader()->id)) {
|
||
$op = $mgr->getOperator($context->getReader()->id, $context->getReader()->type);
|
||
$context = $context->builder()->withOperator(new \app\flow\vo\OperatorInfo(
|
||
id: $op['id'],
|
||
name: $op['name'],
|
||
rfid: $op['rfid']
|
||
))->build();
|
||
}
|
||
} else {
|
||
// 未刷人员卡
|
||
Logger::warn('未刷人员卡 reader={} card={}', [
|
||
$context->getReader()->id,
|
||
$context->getEndoscope()->cardNo,
|
||
]);
|
||
// 处理这个错误
|
||
$context = $context->builder()->error(VoiceMessage::PLEASE_SWIPE_OPERATOR)->build();
|
||
$this->handleResult($context);
|
||
return $context;
|
||
}
|
||
|
||
// 执行流程
|
||
$result = $this->engine->execute($context);
|
||
|
||
// 处理结果
|
||
if ($isExecuteHandleResult) $this->handleResult($result);
|
||
|
||
// 判断人员是否没有
|
||
if (!$result->getOperator()->isValid()) {
|
||
Logger::error('[FlowProcessor] 结果集中人员不存在');
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 处理流程执行结果
|
||
*/
|
||
protected function handleResult(ProcessContext $context): void
|
||
{
|
||
if ($context->isSuccess()) {
|
||
$repo = EctActionsRepository::new();
|
||
|
||
// 更新上一次的操作结束时间
|
||
$this->updateLastOperationEndTime($context, $repo);
|
||
|
||
// 数据库操作
|
||
if ($context->isDatabaseOperationNeeded()) {
|
||
$this->saveToDatabase($context, $repo);
|
||
}
|
||
|
||
// 发送语音播报
|
||
Logger::debug('[FlowProcessor] 播报语音 voice={}', [$context->getFullVoice()]);
|
||
$this->sendVoice($context);
|
||
|
||
// WebSocket通知
|
||
if ($context->isWebSocketNotifyNeeded()) {
|
||
Logger::debug('[FlowProcessor] 发送 WebSocket 通知 endoscope={} step={}', [
|
||
$context->getEndoscope()->name,
|
||
$context->getCurrentStep(),
|
||
]);
|
||
$this->sendWebSocketNotification($context);
|
||
}
|
||
} else {
|
||
// 执行失败,播报错误信息
|
||
Logger::warn('流程失败,播报错误 voice={} error={}', [
|
||
$context->getFullVoice(),
|
||
$context->getVoice()->errorMessage,
|
||
]);
|
||
$this->sendVoice($context);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 处理旧批次的结束时间更新
|
||
*
|
||
* @param ProcessContext $context 上下文
|
||
* @param EctActionsRepository $actionsRepo 操作记录仓储
|
||
*/
|
||
protected function updateLastOperationEndTime(ProcessContext $context, EctActionsRepository $actionsRepo): void
|
||
{
|
||
$lastAction = $context->getPreviousAction();
|
||
$oldActionStartTime = $lastAction->op_starttime;
|
||
|
||
// 仅当旧批次未结束且流程类型不一致时更新
|
||
if ($lastAction->op_endtime === null && $lastAction->process_name != $context->getReader()->type) {
|
||
$oldBatchNo = $lastAction->op_batchno;
|
||
$oldActionEndTime = date('Y-m-d H:i:s');
|
||
$oldDuration = strtotime($oldActionEndTime) - strtotime($oldActionStartTime);
|
||
|
||
$actionsRepo->updateEndTime($oldBatchNo, $oldActionEndTime, $oldDuration);
|
||
Logger::debug(
|
||
"[更新] {$oldBatchNo} 批次中 {$lastAction->process_name} 流程的结束与耗时时间 {$oldActionEndTime} {$oldDuration} s"
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存到数据库
|
||
*/
|
||
protected function saveToDatabase(ProcessContext $context, EctActionsRepository $actionsRepo): void
|
||
{
|
||
$opuserType = $this->getOpusesType($context->getOperator()->id);
|
||
|
||
Logger::debug('[FlowProcessor] 写库 op={} step={} batch={}', [
|
||
$context->getDbOperations(),
|
||
$context->getCurrentStep(),
|
||
$context->getBatchNo(),
|
||
]);
|
||
|
||
try {
|
||
$actionsRepo->saveFromContext($context, $opuserType);
|
||
} catch (\Exception $e) {
|
||
Logger::error('[FlowProcessor] 保存数据到数据库失败 context={} error={}', [
|
||
json_encode($context, JSON_UNESCAPED_UNICODE),
|
||
$e->getMessage(),
|
||
]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送语音播报
|
||
*/
|
||
protected function sendVoice(string|ProcessContext $context): void
|
||
{
|
||
$voice = $context->getFullVoice();
|
||
if (is_string($context)) $voice = $context;
|
||
Logger::info("语音播报:$voice");
|
||
// 语音播报内容已准备就绪,外部服务按需接入:
|
||
// Windows: System.Speech / Linux: ekho / TTS服务 / 读卡器TCP指令
|
||
// 示例: VoiceService::speak($voice);
|
||
}
|
||
|
||
/**
|
||
* 发送WebSocket通知
|
||
*/
|
||
protected function sendWebSocketNotification(ProcessContext $context): void
|
||
{
|
||
// 通过 Webman Push 发送通知:
|
||
// $api = new Api(...);
|
||
// $api->trigger('wash-update', 'message', [
|
||
// 'endoscope_id' => $context->endoscopeId,
|
||
// 'endoscope_name' => $context->endoscopeName,
|
||
// 'current_step' => $context->currentStep,
|
||
// 'process_type' => $context->processType,
|
||
// 'voice' => $context->voiceMessage,
|
||
// ]);
|
||
}
|
||
|
||
/**
|
||
* 获取操作员类型(从 User 表查询 role_id)
|
||
*/
|
||
protected function getOpusesType(string $operatorId): int
|
||
{
|
||
if (empty($operatorId)) {
|
||
return 1; // 默认洗消工
|
||
}
|
||
|
||
try {
|
||
$user = UserRepository::new()->getUser((int)$operatorId);
|
||
return $user->role_id ?? 1;
|
||
} catch (\Throwable $e) {
|
||
Logger::debug('[FlowProcessor] 获取操作员类型失败,使用默认值 operatorId={} error={}', [
|
||
$operatorId,
|
||
$e->getMessage(),
|
||
]);
|
||
return 1; // 默认洗消工
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新配置
|
||
*/
|
||
public function updateConfig(ProcessConfig $config): self
|
||
{
|
||
$this->engine->updateConfig($config);
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* 创建处理器(静态工厂)
|
||
*/
|
||
public static function create(?ProcessConfig $config = null): self
|
||
{
|
||
return new self($config);
|
||
}
|
||
|
||
/**
|
||
* 获取流程配置
|
||
*/
|
||
public function getConfig(): ProcessConfig
|
||
{
|
||
return $this->engine->getConfig();
|
||
}
|
||
}
|