$this->flowUseCustomProcess; } /** * 自定义流程配置键名(默认使用 'standard' 注意:此次为空,并非使用自定义配置文件里面的 'standard') */ public string $flowProcessConfigKey { get => $this->flowProcessConfigKey; } /** * @var int 普通日志轮转时间默认 14 天 */ public int $logRotationTimeByDay { get => $this->logRotationTimeByDay; } /** * @var int 错误日志轮转时间默认 30 天 */ public int $errorLogRotationTimeByDay { get => $this->errorLogRotationTimeByDay; } public DatabaseConfig $database { get => $this->database; } public array $customProcess { get => $this->customProcess; } /** * 存储单读卡器模式 * true=单读卡器模式(一个读卡器交替入库/出库) * false=双读卡器模式(分别使用"内镜放入"和"内镜取出"读卡器) */ public bool $storageSingleReader { get => $this->storageSingleReader; } /** * 机器ID,用于分布式环境,确保唯一 */ public string $machineId { get => $this->machineId; } public bool $dbDebug { get => $this->dbDebug; } /** * 方式 类:方法 * 方式 类 * 方式 :方法 * 允许使用正则 * 作为通配符 * @var array 日志过滤器 */ public array $logFilter { get => $this->logFilter; } public int $logLevel { get => $this->logLevel; } /** * 阻断模式 */ public bool $blockMode { get => $this->blockMode; } /** * @param bool $value * @return void * @deprecated 禁止使用,改方法仅仅用于 test 方法 */ public function setBlockMode(bool $value): void { $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); $isContainTests = false; foreach ($stack as $item) if (str_contains($item['file'] ?? '', 'tests')) { $isContainTests = true; } if (!$isContainTests) throw new \RuntimeException('禁止修改阻断模式'); $this->blockMode = $value; } /** * 人员刷卡记录缓存记录多久。然后是 0 表示不计算缓存时间,只要被使用了就删除 */ public int $openCardRecordCacheTime = 0 { get => $this->openCardRecordCacheTime; } /** * 人员刷卡记录记录哪些读卡器的 * 设置为空则表示所有读卡器 */ public array $openCardRecordReaders = [] { get => $this->openCardRecordReaders; } /** * 是否开启虚拟清洗机解析 */ public bool $enableVirtualCleanerParser { get => $this->enableVirtualCleanerParser; } private function __construct() { $this->detectCircularDependency(); $this->database = new DatabaseConfig(); // TODO 加载自定义流程配置 // 加载根目录的 $this->customProcess = require __DIR__ . '/custom_process_config.php'; $this->machineId = self::getStringEnv("MACHINE_ID", "0"); if (empty($this->machineId) || $this->machineId == '0') { $this->machineId = $this->getMachineIdByMac(); } $this->dbDebug = self::getBoolEnv('DB_DEBUG'); $this->flowUseCustomProcess = self::getBoolEnv('FLOW_USE_CUSTOM_PROCESS', true); $this->flowProcessConfigKey = self::getStringEnv('FLOW_PROCESS_CONFIG_KEY', ''); $this->logRotationTimeByDay = self::getIntEnv('LOG_ROTATION_TIME_BY_DAY', 14); $this->errorLogRotationTimeByDay = self::getIntEnv('ERROR_LOG_ROTATION_TIME_BY_DAY', 30); $this->logFilter = self::getStringArrayEnv('LOG_FILTER', []); $this->logLevel = match (strtoupper(self::getStringEnv('LOG_LEVEL', 'DEBUG'))) { 'INFO' => \Monolog\Logger::INFO, 'WARNING' => \Monolog\Logger::WARNING, 'ERROR' => \Monolog\Logger::ERROR, 'ALERT' => \Monolog\Logger::ALERT, 'EMERGENCY' => \Monolog\Logger::EMERGENCY, 'CRITICAL' => \Monolog\Logger::CRITICAL, 'NOTICE' => \Monolog\Logger::NOTICE, default => \Monolog\Logger::DEBUG }; $this->blockMode = self::getBoolEnv('BLOCK_MODE', true); $this->openCardRecordCacheTime = self::getIntEnv('OPEN_CARD_RECORD_CACHE_TIME', 60); $this->openCardRecordReaders = self::getStringArrayEnv('OPEN_CARD_RECORD_READERS', []); $this->storageSingleReader = self::getBoolEnv('STORAGE_SINGLE_READER', false); $this->enableVirtualCleanerParser = self::getBoolEnv('ENABLE_VIRTUAL_CLEANER_PARSER', false); } private function __clone() { } private function detectCircularDependency(): void { // 获取调用栈(深度10层,足够覆盖依赖链) $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); $logRelatedPaths = [ 'support/Log.php', // Log类文件 'support\Log.php', // Windows路径 'config/log.php', // 日志配置文件 'monolog' // 日志组件 ]; // 遍历调用栈,检查是否包含日志相关文件 foreach ($trace as $step) { if (isset($step['file'])) { $file = strtolower($step['file']); foreach ($logRelatedPaths as $path) { if (str_contains($file, strtolower($path))) { throw new \RuntimeException( "循环依赖检测:Config类初始化时检测到日志组件({$step['file']})被引入\n" . "请移除Config类中对日志的依赖,或调整日志配置加载逻辑。" ); } } } // 检查是否直接调用了 Log 类 if (isset($step['class']) && str_contains(strtolower($step['class']), 'support\log')) { throw new \RuntimeException( "循环依赖检测:Config类被 support\Log 类直接调用,导致循环依赖!" ); } } // 检查当前文件是否主动引入了 Log 类(防止硬编码引入) $includedFiles = get_included_files(); foreach ($includedFiles as $file) { if (str_contains(strtolower($file), 'support/log')) { throw new \RuntimeException( "循环依赖检测:Config类加载时,support\Log 已被提前引入({$file}),禁止循环依赖!" ); } } } /** * 获取服务器MAC地址并散列成2位数字(00-99)作为机器ID * 兼容Linux/Windows系统,失败时降级使用IP散列 * * @return string 2位机器ID(00-99) */ public function getMachineIdByMac(): string { try { // 步骤1:根据系统类型执行命令获取MAC地址 $mac = ''; if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { // Windows系统 $output = shell_exec('ipconfig /all'); if (preg_match('/物理地址[.:\s]+([0-9A-F]{2}[-:][0-9A-F]{2}[-:][0-9A-F]{2}[-:][0-9A-F]{2}[-:][0-9A-F]{2}[-:][0-9A-F]{2})/i', $output, $matches)) { $mac = str_replace(['-', ':'], '', strtolower($matches[1])); } } else { // Linux/Mac系统 $output = shell_exec('cat /sys/class/net/*/address 2>/dev/null || ifconfig'); if (preg_match('/([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})/i', $output, $matches)) { // 过滤虚拟网卡(docker/lo等),取第一个有效物理网卡MAC $mac = str_replace(':', '', strtolower($matches[1])); // 排除回环地址、docker虚拟网卡等无效MAC $invalidPrefixes = ['00:00:00', '02:42:ac', '12:34:56']; $isInvalid = false; foreach ($invalidPrefixes as $prefix) { if (str_starts_with($matches[1], $prefix)) { $isInvalid = true; break; } } if ($isInvalid) { $mac = ''; } } } // 步骤2:若MAC获取失败,降级使用服务器IP if (empty($mac)) { // 获取外网/内网IP(优先内网) $ip = $_SERVER['SERVER_ADDR'] ?? gethostbyname(gethostname()); if (empty($ip) || $ip === '127.0.0.1') { $ip = '0.0.0.0'; // 兜底 } $mac = $ip; // 用IP替代MAC做散列 } $hash = md5($mac); // 生成32位哈希值 $hashInt = hexdec(substr($hash, 0, 4)); $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $base = 62; // 62进制基数 $char1 = $chars[$hashInt % $base]; $char2 = $chars[(int)($hashInt / $base) % $base]; $machineId = $char2 . $char1; // 步骤4:补零为2位(如5→05,99→99) return str_pad($machineId, 2, '0', STR_PAD_LEFT); } catch (\Exception $e) { // 红色输出 echo "\033[31m获取机器ID失败:{$e->getMessage()}\033[0m\n"; return '01'; // 默认机器ID } } public static function getBoolEnv($name, $default = false) { $value = getenv($name); return empty($value) ? $default : filter_var($value, FILTER_VALIDATE_BOOLEAN); } public static function getIntEnv($name, $default = 0) { $value = getenv($name); return empty($value) ? $default : (int)$value; } public static function getStringEnv($name, $default = null) { $value = getenv($name); return empty($value) ? $default : $value; } private static ?Config $instance = null; public static function getInstance(): Config { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } private static function getStringArrayEnv(string $string, array $array): array { $value = getenv($string); return empty($value) ? $array : array_map('trim', explode(';', $value)); } }