request->param('id/d', 0); $link = $id > 0 ? LinksModel::find($id) : null; $url = $link ? (string)$link->url : ''; if (!$url) { return $this->serveDefault(); } $root = app()->getRootPath() . 'public/static/haonav/snapshots/'; if (! is_dir($root)) { @mkdir($root, 0755, true); } $key = md5($url); $file = $root . $key . '.jpg'; if (is_file($file) && (time() - filemtime($file)) < 7 * 86400) { return $this->serveFile($file); } // 失败/生成中负缓存 1 小时,避免每次 hover 都打远程 $failFile = $root . $key . '.fail'; if (is_file($failFile) && (time() - filemtime($failFile)) < 3600) { return is_file($file) ? $this->serveFile($file) : $this->serveDefault(); } $img = $this->fetchSnapshot($url); if ($img) { @file_put_contents($file, $img); return $this->serveFile($file); } @touch($failFile); return is_file($file) ? $this->serveFile($file) : $this->serveDefault(); } /** * 抓取 mshots 截图;生成中(返回 loading gif)或失败返回 false */ protected function fetchSnapshot(string $url) { if (! class_exists(\GuzzleHttp\Client::class)) { return false; } $api = 'https://s0.wp.com/mshots/v1/' . urlencode($url) . '?w=480'; try { $client = new \GuzzleHttp\Client(['timeout' => 8, 'verify' => false]); $resp = $client->get($api, ['headers' => ['Member-Agent' => 'Mozilla/5.0']]); $body = (string)$resp->getBody(); $ct = strtolower($resp->getHeaderLine('Content-Type')); // mshots 首次请求会 307 到 loading gif,表示截图排队生成中,不缓存 if (stripos($ct, 'gif') !== false || strlen($body) < 2048) { return false; } return $body; } catch (\Throwable $e) { return false; } } }