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
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace app\flow\config;
use app\utils\Logger;
/**
* 流程配置抽象基类
* 所有配置类的公共基础,统一提供 Logger 内聚与 toArray 约定
*/
abstract class AbstractConfig
{
/**
* 转换为数组(用于序列化/调试)
*/
abstract public function toArray(): array;
/**
* 写 debug 日志(使用全局静态 Logger)
*/
protected function log(string $message, array $params = []): void
{
Logger::debug($message, $params);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace app\flow\config;
enum MorningMode
{
case None;
case All;
case StorageTime;
case DailyFirst;
case SpecificTypes;
/**
* 从 snake_case 字符串创建(兼容旧数组配置)
* 例如 'none' => None, 'storage_time' => StorageTime, 'daily_first' => DailyFirst
*/
public static function from_name(string $name): ?self
{
return match (strtolower($name)) {
'none' => self::None,
'all' => self::All,
'storage_time' => self::StorageTime,
'daily_first' => self::DailyFirst,
'specific_types' => self::SpecificTypes,
default => null,
};
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace app\flow\config;
/**
* 晨洗配置类
*/
class MorningWashConfig extends AbstractConfig
{
public MorningMode $mode {
get => $this->mode;
set => $this->mode = $value;
}
public int $storageThreshold {
get => $this->storageThreshold;
set => $this->storageThreshold = $value;
}
public string $morningStartTime {
get => $this->morningStartTime;
set => $this->morningStartTime = $value;
}
/**
* 扩展字段:存储除已知字段(mode / storage_threshold / morning_start_time)之外的原始字段
* 以及业务侧自定义扩展字段(如 specific_types 等)
*/
public array $expand {
get => $this->expand;
}
public function __construct(
MorningMode $mode = MorningMode::DailyFirst,
int $storageThreshold = 4,
string $morningStartTime = '00:00:00',
array $expand = []
) {
$this->mode = $mode;
$this->storageThreshold = $storageThreshold;
$this->morningStartTime = $morningStartTime;
$this->expand = $expand;
}
/**
* 从数组创建
* 已知字段直接映射到具体属性,其余字段(含原始字段)存入 expand
*/
public static function create(array $config = []): self
{
// mode 支持字符串(兼容旧配置文件)
$mode = $config['mode'] ?? MorningMode::DailyFirst;
if (is_string($mode)) {
$mode = MorningMode::from_name($mode) ?? MorningMode::DailyFirst;
}
$known = ['mode', 'storage_threshold', 'morning_start_time'];
$expand = array_diff_key($config, array_flip($known));
// 把原始 config 整体也保留在 expand['_raw'] 里,便于下游无损访问
$expand['_raw'] = $config;
$instance = new self(
$mode,
$config['storage_threshold'] ?? 4,
$config['morning_start_time'] ?? '00:00:00',
$expand
);
$instance->log("加载[morning_wash]晨洗配置,来源: {}", [empty($config) ? "默认配置" : "自定义配置"]);
return $instance;
}
/**
* 获取扩展字段值(如 specific_types
*/
public function getExpand(string $key, mixed $default = null): mixed
{
return $this->expand[$key] ?? $default;
}
public function toArray(): array
{
return array_merge([
'mode' => $this->mode->name,
'storage_threshold' => $this->storageThreshold,
'morning_start_time' => $this->morningStartTime,
], array_diff_key($this->expand, ['_raw' => null]));
}
}
+289
View File
@@ -0,0 +1,289 @@
<?php
namespace app\flow\config;
/**
* 流程配置类
* 支持动态配置流程步骤、策略和语音
*/
class ProcessConfig extends AbstractConfig
{
/**
* 步骤集合配置
*/
protected StepsConfig $stepsConfig;
/**
* 晨洗配置
*/
protected MorningWashConfig $morningWashConfig;
/**
* 时间验证配置
*/
protected TimeValidationConfig $timeValidationConfig;
/**
* 语音模板配置
*/
protected VoiceTemplatesConfig $voiceTemplatesConfig;
/**
* 构造函数
*
* @param array $config 原始配置数组(向下兼容)
*/
public function __construct(array $config = [])
{
$this->log("ProcessConfig 初始化中...");
$this->stepsConfig = StepsConfig::create($config['steps'] ?? [], $config['override_steps'] ?? true);
$this->morningWashConfig = MorningWashConfig::create($config['morning_wash'] ?? []);
$this->timeValidationConfig = TimeValidationConfig::create($config['time_validation'] ?? []);
$this->voiceTemplatesConfig = VoiceTemplatesConfig::create($config['voice_templates'] ?? []);
// node_status 覆盖 steps 中的 enabled
foreach (($config['node_status'] ?? []) as $code => $enabled) {
$this->stepsConfig->setEnabled($code, (bool)$enabled);
}
$this->log("ProcessConfig 初始化完成");
}
// ---- 步骤配置 ----
public function getStepsConfig(): StepsConfig
{
return $this->stepsConfig;
}
/**
* 获取所有步骤(StepConfig[])—— 兼容 ProcessEngine 调用
*/
public function getSteps(): array
{
return $this->stepsConfig->all();
}
/**
* 获取启用的步骤
*/
public function getEnabledSteps(): array
{
return $this->stepsConfig->enabled();
}
/**
* 获取步骤配置
*/
public function getStep(string $code): ?StepConfig
{
return $this->stepsConfig->find($code);
}
/**
* 添加步骤
*/
public function addStep(string $code, string $class, bool $enabled = true, array $expand = []): self
{
$this->stepsConfig->add($code, $class, $enabled, $expand);
return $this;
}
/**
* 移除步骤
*/
public function removeStep(string $code): self
{
$this->stepsConfig->remove($code);
return $this;
}
/**
* 设置节点启用状态
*/
public function setNodeEnabled(string $code, bool $enabled): self
{
$this->stepsConfig->setEnabled($code, $enabled);
return $this;
}
/**
* 检查节点是否启用
*/
public function isNodeEnabled(string $code): bool
{
return $this->stepsConfig->find($code)?->enabled ?? true;
}
/**
* 跳过某个步骤(禁用节点)
*/
public function skipStep(string $code): self
{
return $this->setNodeEnabled($code, false);
}
/**
* 启用某个步骤
*/
public function enableStep(string $code): self
{
return $this->setNodeEnabled($code, true);
}
// ---- 晨洗配置 ----
public function getMorningWashConfig(): MorningWashConfig
{
return $this->morningWashConfig;
}
public function setMorningWashConfig(MorningWashConfig $config): self
{
$this->morningWashConfig = $config;
return $this;
}
/**
* 快捷设置晨洗模式(支持字符串或枚举)
*/
public function setMorningWashMode(string|MorningMode $mode): self
{
if (is_string($mode)) {
$mode = MorningMode::from_name($mode) ?? MorningMode::DailyFirst;
}
$this->morningWashConfig->mode = $mode;
return $this;
}
// ---- 时间验证配置 ----
public function getTimeValidationConfig(): TimeValidationConfig
{
return $this->timeValidationConfig;
}
public function setTimeValidationConfig(TimeValidationConfig $config): self
{
$this->timeValidationConfig = $config;
return $this;
}
/**
* 设置步骤时长(秒)
*/
public function setStepDuration(string $stepCode, int $seconds): self
{
$this->timeValidationConfig->setDuration($stepCode, $seconds);
return $this;
}
// ---- 语音模板配置 ----
public function getVoiceTemplatesConfig(): VoiceTemplatesConfig
{
return $this->voiceTemplatesConfig;
}
public function setVoiceTemplatesConfig(VoiceTemplatesConfig $config): self
{
$this->voiceTemplatesConfig = $config;
return $this;
}
/**
* 设置指定步骤的自定义语音
*/
public function setStepVoice(string $templateKey, string $stepCode, string $voice): self
{
$this->voiceTemplatesConfig->setStepVoice($templateKey, $stepCode, $voice);
return $this;
}
// ---- 序列化 ----
public function toArray(): array
{
return [
'steps' => $this->stepsConfig->toArray(),
'morning_wash' => $this->morningWashConfig->toArray(),
'time_validation' => $this->timeValidationConfig->toArray(),
'voice_templates' => $this->voiceTemplatesConfig->toArray(),
];
}
public function saveToFile(string $filePath): bool
{
$content = "<?php\nreturn " . var_export($this->toArray(), true) . ";\n";
return file_put_contents($filePath, $content) !== false;
}
// ---- 静态工厂 ----
public static function fromArray(array $config): self
{
return new self($config);
}
public static function fromFile(string $filePath): self
{
if (!file_exists($filePath)) {
return new self();
}
$config = require $filePath;
return new self($config);
}
public static function createStandard(): self
{
return new self();
}
public static function createNoMorningWash(): self
{
$config = new self();
$config->setMorningWashMode(MorningMode::None);
$config->skipStep('晨洗');
return $config;
}
public static function createSimple(): self
{
$config = new self();
$config->setMorningWashMode(MorningMode::None);
$config->skipStep('漂洗');
$config->skipStep('消毒');
$config->skipStep('终末漂洗');
$config->skipStep('干燥');
return $config;
}
public static function createMachineWash(): self
{
$config = new self();
$config->setMorningWashMode(MorningMode::None);
$config->skipStep('漂洗');
$config->skipStep('消毒');
return $config;
}
public static function createNoDry(): self
{
$config = new self();
$config->skipStep('干燥');
return $config;
}
public static function createDryOnly(): self
{
$config = new self();
$config->setMorningWashMode(MorningMode::None);
$config->skipStep('晨洗');
$config->skipStep('清洗');
$config->skipStep('漂洗');
$config->skipStep('消毒');
$config->skipStep('终末漂洗');
return $config;
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace app\flow\config;
/**
* 流程步骤配置类
* 描述责任链中的单个节点
*/
class StepConfig extends AbstractConfig
{
/**
* 步骤标识码(如 '清洗'、'漂洗'
*/
public string $code {
get => $this->code;
}
/**
* 节点类名(不含命名空间)
*/
public string $class {
get => $this->class;
}
/**
* 是否启用
*/
public bool $enabled {
get => $this->enabled;
set => $this->enabled = $value;
}
/**
* 扩展字段(用于原始/自定义字段透传)
*/
public array $expand {
get => $this->expand;
}
/**
* @var array 需要上一个节点
*/
public array $required = [];
public function __construct(
string $code,
string $class,
bool $enabled = true,
array $required = [],
array $expand = []
)
{
$this->code = $code;
$this->class = $class;
$this->enabled = $enabled;
$this->required = $required;
$this->expand = $expand;
}
public function toArray(): array
{
return array_merge([
'code' => $this->code,
'class' => $this->class,
'enabled' => $this->enabled,
'required' => $this->required,
], $this->expand);
}
}
+163
View File
@@ -0,0 +1,163 @@
<?php
namespace app\flow\config;
/**
* 步骤集合配置类
* 持有完整的流程步骤列表,并封装 override_steps 合并逻辑
*/
class StepsConfig extends AbstractConfig
{
/**
* 步骤列表(StepConfig[]
*/
public array $steps {
get => $this->steps;
}
/**
* @param array $rawSteps 来自配置文件的原始步骤数组(每项为 ['code'=>..., 'class'=>..., 'enabled'=>...]
* @param bool $override true=完全替换默认步骤;false=追加到默认步骤之后
*/
public function __construct(array $rawSteps = [], bool $override = true)
{
$parsed = array_map(fn(array $s) => self::createStep($s), $rawSteps);
// 无自定义步骤时始终使用默认列表,不受 override 影响
if (empty($parsed)) {
$this->steps = self::defaultSteps();
} elseif ($override) {
$this->steps = $parsed;
} else {
$this->steps = array_merge(self::defaultSteps(), $parsed);
}
}
/**
* 从原始配置数组创建
* 读取 $config['steps'] 与 $config['override_steps']
*/
public static function create(array $config = [], bool $override = true): self
{
$instance = new self($config ?? [], $override);
$instance->log("加载[steps]步骤配置,来源: {}", [empty($config) ? "默认配置" : "自定义配置"]);
return $instance;
}
// ---- 默认步骤 ----
/**
* 标准手工洗流程默认步骤列表
*
* @return StepConfig[]
*/
public static function defaultSteps(): array
{
return [
new StepConfig('晨洗', 'MorningWashNode'),
new StepConfig('重复刷卡', 'DuplicateCheckNode'),
new StepConfig('清洗', 'WashNode'),
new StepConfig('漂洗', 'RinseNode'),
new StepConfig('消毒', 'DisinfectNode'),
new StepConfig('终末漂洗', 'FinalRinseNode'),
new StepConfig('干燥', 'DryNode'),
new StepConfig('结束', 'EndNode'),
new StepConfig('机洗', 'MachineWashNode'),
new StepConfig('存储', 'StorageNode'),
new StepConfig('内镜放入', 'StorageInNode'),
new StepConfig('内镜取出', 'StorageOutNode'),
new StepConfig('Close', 'CloseNode'),
];
}
// ---- 步骤查询 ----
/**
* 获取所有步骤
*
* @return StepConfig[]
*/
public function all(): array
{
return $this->steps;
}
/**
* 获取启用的步骤
*
* @return StepConfig[]
*/
public function enabled(): array
{
return array_values(array_filter($this->steps, fn(StepConfig $s) => $s->enabled));
}
/**
* 按 code 查找步骤
*/
public function find(string $code): ?StepConfig
{
foreach ($this->steps as $step) {
if ($step->code === $code) {
return $step;
}
}
return null;
}
// ---- 步骤修改 ----
/**
* 追加一个步骤
*/
public function add(string $code, string $class, bool $enabled = true, array $expand = []): self
{
$this->steps[] = new StepConfig($code, $class, $enabled, $expand);
return $this;
}
/**
* 移除步骤
*/
public function remove(string $code): self
{
$steps = array_values(array_filter($this->steps, fn(StepConfig $s) => $s->code !== $code));
$this->steps = $steps;
return $this;
}
/**
* 设置节点启用状态
*/
public function setEnabled(string $code, bool $enabled): self
{
foreach ($this->steps as $step) {
if ($step->code === $code) {
$step->enabled = $enabled;
}
}
return $this;
}
public function toArray(): array
{
return array_map(fn(StepConfig $s) => $s->toArray(), $this->steps);
}
// ---- 内部工厂 ----
/**
* 从数组创建单个步骤(兼容旧格式 ['code'=>..., 'class'=>..., 'enabled'=>...]
*/
private static function createStep(array $step): StepConfig
{
$known = ['code', 'class', 'enabled'];
$expand = array_diff_key($step, array_flip($known));
return new StepConfig(
$step['code'],
$step['class'],
$step['enabled'] ?? true,
$expand
);
}
}
+124
View File
@@ -0,0 +1,124 @@
<?php
namespace app\flow\config;
use app\repository\ProcessDurationRepository;
use app\utils\Logger;
/**
* 时间验证配置类
* 管理各流程步骤的最小时长要求(秒)
*/
class TimeValidationConfig extends AbstractConfig
{
/**
* 默认步骤时长
* 值 <= 0 表示该步骤不参与时间验证
*/
public array $durations = [
'手工洗' => [
'清洗' => 120,
'漂洗' => 60,
'消毒' => 300,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
'机洗' => [
'清洗' => 120,
'漂洗' => 60,
'消毒' => 300,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
'手工洗(加强)' => [
'清洗' => 60,
'漂洗' => 120,
'消毒' => 420,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
'机洗(加强)' => [
'清洗' => 60,
'漂洗' => 120,
'消毒' => 420,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
'手工洗(晨洗)' => [
'清洗' => 120,
'漂洗' => 60,
'消毒' => 300,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
'机洗(晨洗)' => [
'清洗' => 120,
'漂洗' => 60,
'消毒' => 300,
'终末漂洗' => 120,
'干燥' => 30,
'机洗' => 360,
],
];
public function __construct(array $durations = [])
{
if (empty($durations)) {
$this->durations = ProcessDurationRepository::new()->getProcessDurations();
Logger::debug("加载[time_validation]时间验证配置,来源: 数据库");
} else {
$this->durations = array_merge($this->durations, $durations);
Logger::debug("加载[time_validation]时间验证配置,来源: 自定义参数");
}
}
/**
* 获取指定步骤的时长(不存在则返回 0)
*/
public function getDuration(string $stepCode, string $processType = '手工洗'): int
{
$process = $this->durations[$processType];
if (!isset($process)) return 0;
return $process[$stepCode] ?? 0;
}
/**
* 设置指定步骤的时长
*/
public function setDuration(string $stepCode, int $seconds, string $processType = '手工洗'): self
{
$this->durations[$processType][$stepCode] = $seconds;
return $this;
}
/**
* 判断步骤是否参与时间验证
*/
public function hasStep(string $stepCode, string $processType = '手工洗'): bool
{
return array_key_exists($stepCode, $this->durations[$processType] ?? []);
}
/**
* 从数组创建(兼容旧配置格式 ['durations' => [...]]
*/
public static function create(array $config = []): self
{
$durations = $config['durations'] ?? $config;
if (!is_array($durations)) {
$durations = [];
}
$instance = new self($durations);
return $instance;
}
public function toArray(): array
{
return ['durations' => $this->durations];
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
namespace app\flow\config;
use app\flow\VoiceMessage;
/**
* 语音模板配置类
* 管理所有流程步骤的语音播报文本
*/
class VoiceTemplatesConfig extends AbstractConfig
{
/**
* 晨洗流程语音
*/
public array $morningWash {
get => $this->config['morning_wash'];
}
/**
* 标准手工洗流程语音
*/
public array $normalWash {
get => $this->config['normal_wash'];
}
/**
* 机洗流程语音
*/
public array $machineWash {
get => $this->config['machine_wash'];
}
/**
* 测漏流程语音
*/
public array $leakTest {
get => $this->config['leak_test'];
}
/**
* 存储流程语音
*/
public array $storage {
get => $this->config['storage'];
}
/**
* 错误/提示语音(VoiceMessage name => 文本)
*/
public array $voiceMessage {
get => $this->config['voice_message'];
}
protected array $config = [
'morning_wash' => [
'消毒' => '手工晨洗 流程开始',
'机洗' => '机洗晨洗 开始',
],
'normal_wash' => [
'start' => '清洗流程 开始',
'清洗' => '清洗',
'漂洗' => '漂洗',
'消毒' => '消毒',
'终末漂洗' => '终末漂洗',
'干燥' => '干燥',
'结束' => '流程结束',
],
'machine_wash' => [
'start' => '机洗流程 开始',
'机洗' => '机洗完成',
],
'leak_test' => [
'测漏正常' => '测漏正常',
'测漏异常' => '内镜测漏异常,请检查',
],
'storage' => [
'内镜放入' => '内镜放入',
'内镜取出' => '内镜取出',
],
'voice_message' => [
'wrong_step' => '刷错,请刷{expected}',
VoiceMessage::DUPLICATE_SWIPING->name => '刷错,重复刷卡',
'not_completed' => '刷错,清洗完成才能入柜',
],
];
public function __construct(array $config = [])
{
$this->config = array_merge($this->config, $config);
}
/**
* 按模板 key 获取整组模板
*/
public function getTemplates(string $key): array
{
return match ($key) {
'morning_wash' => $this->morningWash,
'normal_wash' => $this->normalWash,
'machine_wash' => $this->machineWash,
'leak_test' => $this->leakTest,
'storage' => $this->storage,
'voice_message' => $this->voiceMessage,
default => $this->expand[$key] ?? [],
};
}
/**
* 设置指定步骤的自定义语音
* @deprecated
*/
public function setStepVoice(string $stepCode, string $voice): self
{
$this->config['custom'][$stepCode] = $voice;
return $this;
}
/**
* 从数组创建
*
* @param array $config = [
* 'morning_wash' => ['start' => '手工晨洗 流程开始', 'machine_start' => '机洗晨洗 开始'],
* 'normal_wash' => ['start' => '清洗流程 开始', '清洗' => '清洗', '漂洗' => '漂洗', ...],
* 'machine_wash' => ['start' => '机洗流程 开始', '机洗' => '机洗完成'],
* 'leak_test' => ['测漏正常' => '测漏正常', '测漏异常' => '内镜测漏异常,请检查'],
* 'storage' => ['内镜放入' => '内镜放入', '内镜取出' => '内镜取出'],
* 'voice_message' => ['wrong_step' => '刷错,请刷{expected}', 'not_completed' => '...'],
* ]
*/
public static function create(array $config = []): self
{
$instance = new self($config);
$instance->log("加载[voice_templates]语音模板配置,来源: {}", [empty($config) ? "默认配置" : "自定义配置"]);
return $instance;
}
public function toArray(): array
{
return [
'morning_wash' => $this->morningWash,
'normal_wash' => $this->normalWash,
'machine_wash' => $this->machineWash,
'leak_test' => $this->leakTest,
'storage' => $this->storage,
'voice_message' => $this->voiceMessage,
];
}
}