(string)$v, $parts))); } private static function path(string $key): string { return self::dir() . '/data_' . $key . '.json'; } private static function lockPath(string $key): string { return self::dir() . '/data_' . $key . '.lock'; } /** * 读取缓存;不存在 / 过期 / 损坏都返回 null * @return array{at:int,result:array}|null */ public static function read(string $key, int $ttl): ?array { $f = self::path($key); if (!is_file($f)) { return null; } $raw = @file_get_contents($f); if ($raw === false || $raw === '') { return null; } $d = json_decode($raw, true); if (!is_array($d) || !isset($d['at']) || !is_array($d['result'])) { return null; } if ($ttl > 0 && (time() - (int)$d['at']) > $ttl) { return null; } return ['at' => (int)$d['at'], 'result' => $d['result']]; } public static function write(string $key, array $result): bool { if (!is_dir(self::dir())) { @mkdir(self::dir(), 0775, true); } $tmp = self::path($key) . '.tmp'; $ok = @file_put_contents($tmp, json_encode( ['at' => time(), 'result' => $result], JSON_UNESCAPED_UNICODE ), LOCK_EX); if ($ok === false) { return false; } return @rename($tmp, self::path($key)); } /** * 获取构建锁(防止并发惊群:同一 key 只有一个请求真正去拉卷王)。 * * @return resource|null 拿到锁返回句柄(需配合 releaseLock 释放);拿不到返回 null */ public static function acquireLock(string $key) { if (!is_dir(self::dir())) { @mkdir(self::dir(), 0775, true); } $fh = @fopen(self::lockPath($key), 'c'); if ($fh === false) { return null; } if (!flock($fh, LOCK_EX | LOCK_NB)) { fclose($fh); return null; // 已有其它请求在构建 } return $fh; } public static function releaseLock($fh): void { if (is_resource($fh)) { @flock($fh, LOCK_UN); @fclose($fh); } } /** 清空所有数据缓存(名单变更 / 手动清缓存时使用) */ public static function clear(): int { $n = 0; foreach (glob(self::dir() . '/data_*.json') ?: [] as $f) { if (@unlink($f)) { $n++; } } return $n; } }