285 lines
9.4 KiB
PHP
285 lines
9.4 KiB
PHP
<?php
|
||
|
||
namespace app\flow;
|
||
|
||
use app\flow\config\ProcessConfig;
|
||
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 = ProcessContext::fromPacketContext($packetContext,["engineConfig"=>$this->engine->getConfig()]);
|
||
|
||
Logger::debug("[{}] card: {} reader({}): {} {}: {} step: {}", [
|
||
$context->isOperatorCard ? '人员卡' : '内镜卡',
|
||
$context->cardNo,
|
||
empty($context->readerNo) ? '(未绑定)' : $context->readerNo,
|
||
$context->readerNo,
|
||
$context->isOperatorCard ? 'user' : 'endoscope',
|
||
$context->isOperatorCard ? $context->operatorName : ($context->endoscopeName ?: $context->endoscopeId ?: '(未绑定)'),
|
||
$context->currentStep ?: '(新流程)',
|
||
]);
|
||
|
||
// 读卡器未绑定
|
||
if (empty($context->readerId)) {
|
||
Logger::error('读卡器未绑定,放弃处理 card={}', [$context->cardNo]);
|
||
$context->setError(VoiceMessage::READER_NOT_BOUND);
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 如果是人员卡,记录操作员信息后直接返回,不走流程链
|
||
if ($context->isOperatorCard) {
|
||
$mgr = OperatorSessionManager::getInstance();
|
||
$mgr->setOperator(
|
||
$context->readerId,
|
||
$context->operatorId,
|
||
$context->operatorName,
|
||
$context->operatorRfid
|
||
);
|
||
$context->setError(VoiceMessage::PLEASE_SWIPE_ENDOSCOPE);
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 如果内镜未绑定,返回错误
|
||
if (empty($context->endoscopeId)) {
|
||
Logger::error('内镜未绑定,放弃处理 card={}', [$context->cardNo]);
|
||
$context->setError(VoiceMessage::CARD_NOT_BOUND);
|
||
$this->sendVoice($context);
|
||
return $context;
|
||
}
|
||
|
||
// 内镜卡:从 OperatorSessionManager 补充操作员信息
|
||
$mgr = OperatorSessionManager::getInstance();
|
||
if ($mgr->hasOperator($context->readerId) || $context->hasOperator()) {
|
||
if ($mgr->hasOperator($context->readerId)) {
|
||
$op = $mgr->getOperator($context->readerId, $context->readerType);
|
||
$context->operatorId = $op['id'];
|
||
$context->operatorName = $op['name'];
|
||
$context->operatorRfid = $op['rfid'];
|
||
}
|
||
} else {
|
||
// 未刷人员卡
|
||
Logger::warn('未刷人员卡 reader={} card={}', [
|
||
$context->readerId,
|
||
$context->cardNo,
|
||
]);
|
||
// 处理这个错误
|
||
$context->setError(VoiceMessage::PLEASE_SWIPE_OPERATOR);
|
||
$this->handleResult($context);
|
||
return $context;
|
||
}
|
||
|
||
// 执行流程
|
||
$result = $this->engine->execute($context);
|
||
|
||
// 处理结果
|
||
if ($isExecuteHandleResult) $this->handleResult($result);
|
||
|
||
// 判断人员是否没有
|
||
if (empty($result->operatorId)) {
|
||
Logger::error('[FlowProcessor] 结果集中人员不存在');
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 处理流程执行结果
|
||
*/
|
||
protected function handleResult(ProcessContext $context): void
|
||
{
|
||
if ($context->success) {
|
||
$repo = EctActionsRepository::new();
|
||
|
||
// 更新上一次的操作结束时间
|
||
$this->updateLastOperationEndTime($context, $repo);
|
||
|
||
// 数据库操作
|
||
if ($context->needDatabaseOperation) {
|
||
$this->saveToDatabase($context, $repo);
|
||
}
|
||
|
||
// 发送语音播报
|
||
Logger::debug('[FlowProcessor] 播报语音 voice={}', [$context->getFullVoice()]);
|
||
$this->sendVoice($context);
|
||
|
||
// WebSocket通知
|
||
if ($context->needWebSocketNotify) {
|
||
Logger::debug('[FlowProcessor] 发送 WebSocket 通知 endoscope={} step={}', [
|
||
$context->endoscopeName,
|
||
$context->currentStep,
|
||
]);
|
||
$this->sendWebSocketNotification($context);
|
||
}
|
||
} else {
|
||
// 执行失败,播报错误信息
|
||
Logger::warn('流程失败,播报错误 voice={} error={}', [
|
||
$context->getFullVoice(),
|
||
$context->errorMessage,
|
||
]);
|
||
$this->sendVoice($context);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 处理旧批次的结束时间更新
|
||
*
|
||
* @param ProcessContext $context 上下文
|
||
* @param EctActionsRepository $actionsRepo 操作记录仓储
|
||
*/
|
||
protected function updateLastOperationEndTime(ProcessContext $context, EctActionsRepository $actionsRepo): void
|
||
{
|
||
$lastAction = $context->previousAction;
|
||
$oldActionStartTime = $lastAction->op_starttime;
|
||
|
||
// 仅当旧批次未结束且流程类型不一致时更新
|
||
if ($lastAction->op_endtime === null && $lastAction->process_name != $context->readerType) {
|
||
$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->operatorId);
|
||
|
||
Logger::debug('[FlowProcessor] 写库 op={} step={} batch={}', [
|
||
$context->dbOperation,
|
||
$context->currentStep,
|
||
$context->batchNo,
|
||
]);
|
||
|
||
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();
|
||
}
|
||
}
|