>/www/wwwroot/你的域名/data/getcard_poll_cron.log 2>&1 */ $cfg = require __DIR__ . '/../config.php'; $isCli = PHP_SAPI === 'cli'; $cronKey = isset($cfg['poll_cron_key']) ? (string)$cfg['poll_cron_key'] : ''; if (!$isCli) { if ($cronKey === '' || (($_GET['key'] ?? '') !== $cronKey)) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); echo 'Forbidden'; exit; } header('Content-Type: text/plain; charset=utf-8'); } $dataDir = __DIR__ . '/../data'; if (!is_dir($dataDir)) { if (!@mkdir($dataDir, 0755, true) && !is_dir($dataDir)) { sendcard_poll_out("Cannot create data directory: {$dataDir}\n"); exit(1); } } $statePath = isset($cfg['poll_state_json']) && $cfg['poll_state_json'] !== '' ? (string)$cfg['poll_state_json'] : $dataDir . DIRECTORY_SEPARATOR . 'getcard_poll_state.json'; $logPath = isset($cfg['poll_log_txt']) && $cfg['poll_log_txt'] !== '' ? (string)$cfg['poll_log_txt'] : $dataDir . DIRECTORY_SEPARATOR . 'getcard_poll_log.txt'; $externalBase = trim((string)($cfg['external_base_url'] ?? '')); if ($externalBase === '') { sendcard_poll_out("Config missing: external_base_url\n"); exit(1); } try { $externalUrl = sendcard_poll_build_external_url($cfg); } catch (InvalidArgumentException $e) { sendcard_poll_out('Config invalid: ' . $e->getMessage() . "\n"); exit(1); } $now = time(); $fh = fopen($statePath, 'c+'); if ($fh === false) { sendcard_poll_out("Cannot open state file: {$statePath}\n"); exit(1); } if (!flock($fh, LOCK_EX)) { fclose($fh); sendcard_poll_out("Cannot lock state file\n"); exit(1); } $raw = stream_get_contents($fh); $state = is_string($raw) && $raw !== '' ? json_decode($raw, true) : null; if (!is_array($state)) { $state = []; } $nextRunAt = isset($state['next_run_at']) ? (int)$state['next_run_at'] : 0; if ($now < $nextRunAt) { flock($fh, LOCK_UN); fclose($fh); $wait = $nextRunAt - $now; sendcard_poll_out("Skip: next run in {$wait}s (at " . date('Y-m-d H:i:s', $nextRunAt) . ")\n"); exit(0); } require_once __DIR__ . '/../lib/external.php'; $linePrefix = '[' . date('Y-m-d H:i:s') . '] '; $logBlock = ''; try { $resp = sendcard_fetch_credentials($externalUrl); $httpStatus = $resp['http_status']; $json = $resp['json']; $token = isset($json['data']['token']) ? (string)$json['data']['token'] : null; $logBlock = $token !== null && $token !== '' ? $token . "\n" : $linePrefix . "http={$httpStatus} token missing: " . json_encode($json, JSON_UNESCAPED_UNICODE) . "\n"; } catch (Throwable $e) { $logBlock = $linePrefix . 'ERROR ' . $e->getMessage() . "\n"; } // $intervalSec = max(4 * 60, 5 * 60 + random_int(-3 * 60, 3 * 60)); $intervalSec = random_int(540, 840); $newNext = $now + $intervalSec; $state['next_run_at'] = $newNext; $state['last_run_at'] = $now; $state['last_interval_sec'] = $intervalSec; rewind($fh); ftruncate($fh, 0); fwrite($fh, json_encode($state, JSON_UNESCAPED_UNICODE)); fflush($fh); flock($fh, LOCK_UN); fclose($fh); if ($logBlock !== '') { file_put_contents($logPath, $logBlock, FILE_APPEND | LOCK_EX); } sendcard_poll_out( $logBlock . "Scheduled next run in {$intervalSec}s (at " . date('Y-m-d H:i:s', $newNext) . ")\n" ); exit(0); function sendcard_poll_out(string $msg): void { echo $msg; } /** * 轮询请求 URL:使用 config['poll'] 下的 device_code、device_code_md5 作为查询参数值(与 getcard 顶层 device_* 分离)。 * * @param array $cfg */ function sendcard_poll_build_external_url(array $cfg): string { $base = trim((string)($cfg['external_base_url'] ?? '')); if ($base === '') { throw new InvalidArgumentException('external_base_url'); } $poll = $cfg['poll'] ?? null; if (!is_array($poll)) { throw new InvalidArgumentException("config['poll'] 须为数组且含 device_code、device_code_md5"); } $dc = trim((string)($poll['device_code'] ?? '')); $dm = trim((string)($poll['device_code_md5'] ?? '')); if ($dc === '' || $dm === '') { throw new InvalidArgumentException("config['poll']['device_code'] 与 ['device_code_md5'] 勿留空"); } return $base . '?device_code=' . rawurlencode($dc) . '&device_code_md5=' . rawurlencode($dm); }