Files
tcpserver-flow/app/flow/FlowProcessor.php
T
zimoyin 177c3ae9b2 refactor(context): 重命名上下文构建器及更新相关方法命名
- 将 ProcessContext 中的 builder() 重命名为 createModifyBuilder() 并添加调用栈日志
- ProcessContextBuilder 中所有 with* 方法统一改为 set* 命名风格
- Flow 各节点及流程处理器中调用 builder() 替换为 createModifyBuilder()
- 虚拟测试环境相关 ContextBuilder 中方法同步改名保持一致
- 优化流程节点中上下文修改代码调用,提升代码规范性
- 添加Config中加载自定义流程配置的占位注释及代码
- 无功能改动,纯代码风格及命名规范调整
2026-03-13 18:51:02 +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->createModifyBuilder()->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->createModifyBuilder()->error(VoiceMessage::PLEASE_SWIPE_ENDOSCOPE)->build();
$this->sendVoice($context);
return $context;
}
// 如果内镜未绑定,返回错误
if ($context->getEndoscope()->isEmpty()) {
Logger::error('内镜未绑定,放弃处理 card={}', [$context->getEndoscope()->cardNo]);
$context = $context->createModifyBuilder()->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->createModifyBuilder()->setOperator(new \app\flow\context\bean\OperatorInfo(
id: $op['id'],
name: $op['name'],
rfid: $op['rfid']
))->build();
}
} else {
// 未刷人员卡
Logger::warn('未刷人员卡 reader={} card={}', [
$context->getReader()->id,
$context->getEndoscope()->cardNo,
]);
// 处理这个错误
$context = $context->createModifyBuilder()->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();
}
}