feat: 实现TCP Server

This commit is contained in:
zimoyin
2026-03-02 21:59:43 +08:00
parent 043306819b
commit a79dfae57d
144 changed files with 15785 additions and 140 deletions
+284
View File
@@ -0,0 +1,284 @@
<?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();
}
}