Files
zimoyin defe163190 test
2026-03-18 13:27:48 +08:00

314 lines
11 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\config;
class Config
{
/**
* 是否使用自定义流程配置(从Config加载)
*/
public bool $flowUseCustomProcess {
get => $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;
}
/**
* TODO 代码内部进行数据库记录行列转换
*/
public bool $enableInternalRowColumnConvert = true {
get => $this->enableInternalRowColumnConvert;
}
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位机器ID00-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));
}
}