From 200f0881a5d3cc10885efb6efa02979166a05b12 Mon Sep 17 00:00:00 2001 From: cat_shark <1716967236@qq.com> Date: Sat, 4 Jul 2026 22:34:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(lpt-ai)=EF=BC=9A=E6=96=B0=E5=A2=9E=20/fetc?= =?UTF-8?q?h-title=20=E7=BD=91=E9=A1=B5=E6=A0=87=E9=A2=98=E6=8A=93?= =?UTF-8?q?=E5=8F=96=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /fetch-title { url } → { url, title } - 服务端 fetch 目标页面,正则提取 标签 - 10 秒超时,抓取失败时返回 title: null - 请求日志记录到 logStore --- src/routes/ai.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 5ca68b9..bbef803 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -5,6 +5,7 @@ import type { FastifyInstance } from "fastify"; import { isAvailable, loadConfig } from "../llm/client.js"; import { createTask, getTask, type TaskType } from "../task-queue.js"; +import { logStore } from "../admin/store.js"; export async function aiRoutes(app: FastifyInstance): Promise<void> { const config = loadConfig(); @@ -39,4 +40,48 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> { } return task; }); + + /** 抓取网页标题 */ + app.post<{ Body: { url: string } }>("/fetch-title", async (request, reply) => { + const { url } = request.body ?? {}; + const t0 = Date.now(); + + if (!url || typeof url !== "string" || (!url.startsWith("http://") && !url.startsWith("https://"))) { + return reply.status(400).send({ error: "无效的 URL" }); + } + + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 10_000); + const response = await fetch(url, { signal: controller.signal }); + clearTimeout(timer); + + const html = await response.text(); + const match = html.match(/<title[^>]*>([^<]+)<\/title>/i); + const title = match ? match[1].trim() : null; + + logStore.add({ + timestamp: new Date().toISOString(), + endpoint: "/fetch-title", + model: "", + durationMs: Date.now() - t0, + status: 200, + requestBody: { url }, + responseBody: { url, title }, + }); + return { url, title }; + } catch (err) { + logStore.add({ + timestamp: new Date().toISOString(), + endpoint: "/fetch-title", + model: "", + durationMs: Date.now() - t0, + status: 502, + requestBody: { url }, + responseBody: { url, title: null }, + error: err instanceof Error ? err.message : String(err), + }); + return { url, title: null }; + } + }); }