feat: 实现TCP Server
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\flow\strategies;
|
||||
|
||||
use app\flow\ProcessContext;
|
||||
use app\flow\nodes\ProcessNodeInterface;
|
||||
|
||||
/**
|
||||
* 策略抽象基类
|
||||
*/
|
||||
abstract class AbstractStrategy implements ProcessStrategyInterface
|
||||
{
|
||||
/**
|
||||
* 策略配置
|
||||
*/
|
||||
protected array $config = [];
|
||||
|
||||
/**
|
||||
* 策略执行阶段
|
||||
*/
|
||||
protected string $phase = 'before';
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行策略
|
||||
*/
|
||||
public function execute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext
|
||||
{
|
||||
if (!$this->isApplicable($context, $node)) {
|
||||
return $context;
|
||||
}
|
||||
return $this->doExecute($context, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体执行逻辑,由子类实现
|
||||
*/
|
||||
abstract protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext;
|
||||
|
||||
/**
|
||||
* 获取策略执行阶段
|
||||
*/
|
||||
public function getPhase(): string
|
||||
{
|
||||
return $this->phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行阶段
|
||||
*/
|
||||
public function setPhase(string $phase): self
|
||||
{
|
||||
$this->phase = $phase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断策略是否适用
|
||||
* 默认总是适用
|
||||
*/
|
||||
public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
protected function getConfig(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->config[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace app\flow\strategies;
|
||||
|
||||
use app\flow\config\MorningMode;
|
||||
use app\flow\config\MorningWashConfig;
|
||||
use app\flow\ProcessContext;
|
||||
use app\flow\nodes\ProcessNodeInterface;
|
||||
use app\utils\Logger;
|
||||
|
||||
/**
|
||||
* 晨洗判断策略
|
||||
* 根据配置判断是否需要晨洗
|
||||
*/
|
||||
class MorningWashStrategy extends AbstractStrategy
|
||||
{
|
||||
protected string $phase = 'before';
|
||||
|
||||
protected MorningWashConfig $morningWashConfig;
|
||||
|
||||
public function __construct(MorningWashConfig $config)
|
||||
{
|
||||
parent::__construct([]);
|
||||
$this->morningWashConfig = $config;
|
||||
Logger::debug("MorningWashStrategy 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行晨洗判断
|
||||
*/
|
||||
protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext
|
||||
{
|
||||
$isEnd = $context->currentStep == '结束';
|
||||
$needMorning = $this->checkNeedMorningWash($context) && $isEnd;
|
||||
$context->needMorningWash = $needMorning;
|
||||
|
||||
// 如果不需要晨洗,标记为已完成
|
||||
if (!$needMorning) {
|
||||
$context->morningWashed = true;
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要晨洗
|
||||
*/
|
||||
protected function checkNeedMorningWash(ProcessContext $context): bool
|
||||
{
|
||||
return match ($this->morningWashConfig->mode) {
|
||||
MorningMode::None => false,
|
||||
MorningMode::All => true,
|
||||
MorningMode::StorageTime => $this->checkByStorageTime($context),
|
||||
MorningMode::DailyFirst => $this->checkByDailyFirst($context),
|
||||
MorningMode::SpecificTypes => $this->checkBySpecificTypes($context),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据存储时间判断(义乌模式)
|
||||
* 普通镜柜超过阈值小时需要晨洗,无菌镜柜免晨消
|
||||
*/
|
||||
protected function checkByStorageTime(ProcessContext $context): bool
|
||||
{
|
||||
$storageInTime = $context->storageInTime;
|
||||
$lastActionType = $context->previousAction->action_type_name;
|
||||
$lastProcessName = $context->previousAction->process_name;
|
||||
|
||||
// 如果最后一次操作是存储且已取出
|
||||
if ($lastActionType === '存储' && $lastProcessName === '内镜取出') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果最后一次操作是存储且未取出,检查存储时间
|
||||
if ($lastActionType === '存储' && $lastProcessName === '内镜放入' && $storageInTime) {
|
||||
$storageHours = (time() - strtotime($storageInTime)) / 3600;
|
||||
return $storageHours > $this->morningWashConfig->storageThreshold;
|
||||
}
|
||||
|
||||
// 检查今天是否已有洗消记录
|
||||
return $this->hasWashRecordToday($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据每天第一次判断(忠县模式)
|
||||
*/
|
||||
protected function checkByDailyFirst(ProcessContext $context): bool
|
||||
{
|
||||
return $this->hasWashRecordToday($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查今天是否已有洗消记录
|
||||
*/
|
||||
protected function hasWashRecordToday(ProcessContext $context): bool
|
||||
{
|
||||
return $context->todayWashRecords === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据特定类型判断
|
||||
*/
|
||||
protected function checkBySpecificTypes(ProcessContext $context): bool
|
||||
{
|
||||
$specificTypes = $this->morningWashConfig->getExpand('specific_types', []);
|
||||
return in_array($context->endoscopeType, $specificTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return '晨洗判断策略(' . $this->morningWashConfig->mode->name . ')';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace app\flow\strategies;
|
||||
|
||||
use app\flow\ProcessContext;
|
||||
use app\flow\nodes\ProcessNodeInterface;
|
||||
|
||||
/**
|
||||
* 流程策略接口
|
||||
* 策略模式的核心接口
|
||||
*/
|
||||
interface ProcessStrategyInterface
|
||||
{
|
||||
/**
|
||||
* 执行策略
|
||||
* @param ProcessContext $context 流程上下文
|
||||
* @param ProcessNodeInterface $node 当前节点
|
||||
* @return ProcessContext 处理后的上下文
|
||||
*/
|
||||
public function execute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext;
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* 获取策略执行阶段
|
||||
* @return string 'before' | 'after'
|
||||
*/
|
||||
public function getPhase(): string;
|
||||
|
||||
/**
|
||||
* 判断策略是否适用
|
||||
* @param ProcessContext $context
|
||||
* @param ProcessNodeInterface $node
|
||||
* @return bool
|
||||
*/
|
||||
public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace app\flow\strategies;
|
||||
|
||||
use app\config\Config;
|
||||
use app\flow\config\TimeValidationConfig;
|
||||
use app\flow\ProcessContext;
|
||||
use app\flow\nodes\ProcessNodeInterface;
|
||||
use app\flow\VoiceMessage;
|
||||
use app\repository\ProcessDurationRepository;
|
||||
use app\utils\Logger;
|
||||
|
||||
/**
|
||||
* 时间验证策略
|
||||
* 验证步骤时间是否满足要求
|
||||
*/
|
||||
class TimeValidationStrategy extends AbstractStrategy
|
||||
{
|
||||
protected string $phase = 'before';
|
||||
|
||||
protected TimeValidationConfig $timeValidationConfig;
|
||||
|
||||
/**
|
||||
* 已从数据库加载的时长缓存:[processType => [stepName => seconds]]
|
||||
*/
|
||||
private array $dbDurationCache = [];
|
||||
|
||||
public function __construct(TimeValidationConfig $config)
|
||||
{
|
||||
parent::__construct([]);
|
||||
$this->timeValidationConfig = $config;
|
||||
Logger::debug("TimeValidationStrategy 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库获取步骤时长(按流程类型精确匹配)
|
||||
* 优先级:数据库记录 > 构造时配置 > 内置 fallback
|
||||
*/
|
||||
protected function getDurationFromDb(string $stepCode, string $processType): int
|
||||
{
|
||||
if (empty($processType)) {
|
||||
return $this->timeValidationConfig->getDuration($stepCode, $processType);
|
||||
}
|
||||
|
||||
// 整批加载并缓存,减少查询次数
|
||||
if (!isset($this->dbDurationCache[$processType])) {
|
||||
$this->dbDurationCache[$processType] = ProcessDurationRepository::new()
|
||||
->getDurationsByProcessType($processType);
|
||||
}
|
||||
|
||||
$dbValue = $this->dbDurationCache[$processType][$stepCode] ?? null;
|
||||
if ($dbValue !== null) {
|
||||
return $dbValue;
|
||||
}
|
||||
|
||||
// fallback:返回配置中的默认值
|
||||
return $this->timeValidationConfig->getDuration($stepCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行时间验证
|
||||
*/
|
||||
protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext
|
||||
{
|
||||
$stepCode = $node->getCode();
|
||||
$currentStep = $context->currentStep;
|
||||
$processType = $context->processType;
|
||||
|
||||
Logger::debug("开始执行时间验证策略,步骤:{$stepCode},流程类型:{$processType}");
|
||||
$configDuration = $this->timeValidationConfig->getDuration($stepCode,$processType);
|
||||
|
||||
Logger::debug("步骤:{$stepCode},流程类型:{$processType},配置时长:{$configDuration}s");
|
||||
if ($configDuration > 0) {
|
||||
// 配置中有明确时长,直接使用
|
||||
$requiredDuration = $configDuration;
|
||||
} else {
|
||||
Logger::debug("步骤:{$stepCode},流程类型:{$processType},无配置时长,从数据库查询");
|
||||
// 从数据库按流程类型精确查询
|
||||
$requiredDuration = $this->getDurationFromDb($stepCode, $context->processType);
|
||||
if ($requiredDuration > 0) {
|
||||
$context->setStepDuration($stepCode, $requiredDuration);
|
||||
} else {
|
||||
// 最后使用上下文已缓存值
|
||||
$requiredDuration = $context->getStepDuration($stepCode);
|
||||
}
|
||||
}
|
||||
|
||||
// 无需时间验证,直接放行
|
||||
if ($requiredDuration <= 0) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
// 获取上次该步骤的操作时间
|
||||
$duration = $context->duration;
|
||||
|
||||
// 没有上次记录(第一次操作),允许通过
|
||||
if (empty($duration)) {
|
||||
Logger::debug("步骤:{$stepCode},流程类型:{$processType},无上次记录,允许通过");
|
||||
return $context;
|
||||
}
|
||||
|
||||
if ($duration < $requiredDuration) {
|
||||
Logger::debug("[{$stepCode}] 时间不足,剩余: {}s", [$requiredDuration - $duration]);
|
||||
if (Config::getInstance()->blockMode){
|
||||
$voice = VoiceMessage::NOT_ENOUGH_TIME->value;
|
||||
$voice = str_replace('{step}', $stepCode, $voice);
|
||||
$voice = str_replace('{time}', $requiredDuration - $duration, $voice);
|
||||
$context->setCustomError($voice);
|
||||
}
|
||||
return $context;
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断策略是否适用
|
||||
* 只有在 timeValidationConfig 中登记的步骤才参与时间验证
|
||||
*/
|
||||
public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool
|
||||
{
|
||||
if ($context->currentStep != $context->previousAction->process_name) return false;
|
||||
if (!$this->timeValidationConfig->hasStep($node->getCode(), $context->processType)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return '时间验证策略';
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动设置步骤时长(覆盖 fallback,不影响数据库缓存)
|
||||
*/
|
||||
public function setStepDuration(string $stepCode, int $seconds): self
|
||||
{
|
||||
$this->timeValidationConfig->setDuration($stepCode, $seconds);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace app\flow\strategies;
|
||||
|
||||
use app\flow\config\VoiceTemplatesConfig;
|
||||
use app\flow\exception\IllegalUsageException;
|
||||
use app\flow\ProcessContext;
|
||||
use app\flow\nodes\ProcessNodeInterface;
|
||||
use app\utils\Logger;
|
||||
|
||||
/**
|
||||
* 语音生成策略
|
||||
* 根据配置生成语音播报内容
|
||||
*/
|
||||
class VoiceGenerationStrategy extends AbstractStrategy
|
||||
{
|
||||
protected string $phase = 'after';
|
||||
|
||||
protected VoiceTemplatesConfig $voiceTemplatesConfig;
|
||||
|
||||
public function __construct(VoiceTemplatesConfig $config)
|
||||
{
|
||||
parent::__construct([]);
|
||||
$this->voiceTemplatesConfig = $config;
|
||||
Logger::debug("VoiceGenerationStrategy 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行语音生成
|
||||
*/
|
||||
protected function doExecute(ProcessContext $context, ProcessNodeInterface $node): ProcessContext
|
||||
{
|
||||
// 如果存在流程中自定义语音,就直接输出
|
||||
if (!empty($context->voiceMessage)) {
|
||||
Logger::warn(
|
||||
"流程中存在自定义语音或存在多次设置语音,语音应该在 VoiceGenerationStrategy 策略中配置,否则不能拦截与自定义配置",
|
||||
new IllegalUsageException("In the existing process, there is custom voice, which should be configured in the VoiceGenerationStrategy strategy; otherwise, it cannot be intercepted and customized")
|
||||
);
|
||||
$context->setVoice($context->voiceMessage);
|
||||
return $context;
|
||||
}
|
||||
|
||||
// 如果已经有错误,生成错误语音
|
||||
// 否则生成正常流程语音
|
||||
$voice = !$context->success ? $this->generateErrorVoice($context) : $this->generateNormalVoice($context, $node);
|
||||
|
||||
|
||||
// 应用语音模板
|
||||
foreach ($context->voiceTemplateParams as $key => $val) {
|
||||
$replaceVal = match (true) {
|
||||
is_array($val) => implode(',', $val),
|
||||
is_bool($val) => $val ? 'true' : 'false',
|
||||
is_null($val) => '',
|
||||
default => (string) $val
|
||||
};
|
||||
$voice = str_replace('{' . $key . '}', $replaceVal, $voice);
|
||||
}
|
||||
Logger::debug("应用语音模板后的内容: {$voice}");
|
||||
$context->setVoice($voice);
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成正常流程语音
|
||||
*/
|
||||
protected function generateNormalVoice(ProcessContext $context, ProcessNodeInterface $node): string
|
||||
{
|
||||
$stepCode = $node->getCode();
|
||||
$stepName = $node->getName();
|
||||
$processType = $context->processType;
|
||||
|
||||
// 根据流程类型选择模板
|
||||
$templateKey = $this->getTemplateKey($processType, $stepCode, $context);
|
||||
$templates = $this->voiceTemplatesConfig->getTemplates($templateKey);
|
||||
if (empty($templates)) {
|
||||
$templates = $this->voiceTemplatesConfig->normalWash;
|
||||
}
|
||||
|
||||
// 优先使用自定义步骤语音(直接存储在对应模板key中)
|
||||
$voice = $templates[$stepCode] ?? $stepCode . '完成';
|
||||
|
||||
// 添加提醒信息
|
||||
$remind = $this->getRemindMessage($context);
|
||||
if ($remind) {
|
||||
$voice .= $remind;
|
||||
}
|
||||
|
||||
return $voice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误语音
|
||||
*/
|
||||
protected function generateErrorVoice(ProcessContext $context): string
|
||||
{
|
||||
$errorMessage = $context->errorMessage;
|
||||
$errorTemplates = $this->voiceTemplatesConfig->voiceMessage;
|
||||
|
||||
$errorMsg = $errorTemplates[$errorMessage->name] ?? $errorMessage->value;
|
||||
if (empty($errorMsg)) {
|
||||
$errorMsg = $context->voiceMessage;
|
||||
Logger::debug("错误信息配置未命中,使用自定义语音: {$errorMsg}");
|
||||
} else {
|
||||
Logger::debug("错误信息配置命中: {$errorMsg}");
|
||||
}
|
||||
|
||||
if (empty($errorMsg)) {
|
||||
Logger::error("配置文件与枚举信息中未找匹配到错误信息");
|
||||
}
|
||||
|
||||
return $errorMsg ?: '刷卡错误';
|
||||
}
|
||||
|
||||
/**
|
||||
* 流程类型到模板键的映射
|
||||
*/
|
||||
protected const array PROCESS_TYPE_MAP = [
|
||||
'机洗' => 'machine_wash',
|
||||
'机洗(晨洗)' => 'machine_wash',
|
||||
'机洗(加强)' => 'machine_wash',
|
||||
'测漏' => 'leak_test',
|
||||
'存储' => 'storage',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取模板键
|
||||
*/
|
||||
protected function getTemplateKey(string $processType, string $stepCode, ProcessContext $context): string
|
||||
{
|
||||
// 晨洗流程中的清洗/机洗步骤
|
||||
if ($context->needMorningWash) {
|
||||
return 'morning_wash';
|
||||
}
|
||||
|
||||
// 从映射表中获取,默认返回 normal_wash
|
||||
return self::PROCESS_TYPE_MAP[$processType] ?? 'normal_wash';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提醒信息
|
||||
*/
|
||||
protected function getRemindMessage(ProcessContext $context): string
|
||||
{
|
||||
$reminds = [];
|
||||
|
||||
if ($context->needLeakTestRemind) {
|
||||
$reminds[] = ',清洗开始前,请测漏';
|
||||
}
|
||||
|
||||
if ($context->needStorageRemind) {
|
||||
$reminds[] = ',未登记取出';
|
||||
}
|
||||
|
||||
return implode('', $reminds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断策略是否适用
|
||||
*/
|
||||
public function isApplicable(ProcessContext $context, ProcessNodeInterface $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return '语音生成策略';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user