Files
tcpserver-flow/app/flow/FlowProcessor.php
T
zimoyin 3471deb3f1 ai-refactor(flow): 调整抽象流程节点实现和依赖路径
- 修改 AbstractProcessNode 中 ProcessContext 的命名空间引用为 app\flow\context\ProcessContext
- 引入 app\flow\vo\CanHandleResult 用于节点处理结果表示
- 更新前置策略执行后对成功状态的判断,改为调用 isSuccess() 方法
- 增加日志记录细节,便于调试策略执行中断时的错误信息
- 优化代码注释,提升代码可读性和维护性
2026-03-11 02:40:45 +08:00

290 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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();
}
}