feat(lpt-ai):新增 /fetch-title 网页标题抓取接口
POST /fetch-title { url } → { url, title }
- 服务端 fetch 目标页面,正则提取 <title> 标签
- 10 秒超时,抓取失败时返回 title: null
- 请求日志记录到 logStore
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import { isAvailable, loadConfig } from "../llm/client.js";
|
import { isAvailable, loadConfig } from "../llm/client.js";
|
||||||
import { createTask, getTask, type TaskType } from "../task-queue.js";
|
import { createTask, getTask, type TaskType } from "../task-queue.js";
|
||||||
|
import { logStore } from "../admin/store.js";
|
||||||
|
|
||||||
export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
@@ -39,4 +40,48 @@ export async function aiRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
}
|
}
|
||||||
return task;
|
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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user