|null */ public static function fetchStats(): ?array { $url = SplitSyncConfigService::getNodeHost() . '/api/stats'; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); curl_setopt($ch, CURLOPT_TIMEOUT, 3); $response = curl_exec($ch); if (curl_errno($ch)) { curl_close($ch); return null; } $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { return null; } $decoded = json_decode((string) $response, true); return is_array($decoded) ? $decoded : null; } /** * 当前是否适合向 Node 提交新 Browser 任务 */ public static function canAcceptWork(): bool { $stats = self::fetchStats(); if ($stats === null) { return true; } if (!empty($stats['shuttingDown'])) { return false; } $queue = is_array($stats['queue'] ?? null) ? $stats['queue'] : []; $config = is_array($stats['config'] ?? null) ? $stats['config'] : []; $queued = (int) ($queue['queued'] ?? 0); $active = (int) ($queue['active'] ?? 0); $max = max(1, (int) ($config['maxConcurrentBrowsers'] ?? ($queue['max'] ?? 4))); $threshold = SplitSyncConfigService::getNodeQueueSkipThreshold(); if ($queued >= $threshold) { return false; } return $active < $max; } /** * @return array{queued:int,active:int,max:int} */ public static function getQueueSnapshot(): array { $stats = self::fetchStats(); if ($stats === null) { return ['queued' => 0, 'active' => 0, 'max' => 0]; } $queue = is_array($stats['queue'] ?? null) ? $stats['queue'] : []; $config = is_array($stats['config'] ?? null) ? $stats['config'] : []; return [ 'queued' => (int) ($queue['queued'] ?? 0), 'active' => (int) ($queue['active'] ?? 0), 'max' => max(1, (int) ($config['maxConcurrentBrowsers'] ?? ($queue['max'] ?? 4))), ]; } /** * Node 侧 Real Browser / Captcha 是否就绪(供同步失败日志可读性) * * @return array{realBrowserReady:bool,captchaConfigured:bool} */ public static function getAntiBotSnapshot(): array { $stats = self::fetchStats(); if ($stats === null) { return ['realBrowserReady' => false, 'captchaConfigured' => false]; } return [ 'realBrowserReady' => !empty($stats['realBrowserReady']), 'captchaConfigured' => !empty($stats['captchaConfigured']), ]; } }