feat(sites): 站点请求失败留样(site_failure_sample,每站最近 5 份)

- 迁移 0077 新表 site_failure_sample:类型(parse_error / challenge)、请求类型、脱敏 URL、状态码、
  关键响应头、错误文案、命中规则、脱敏页面开头(≤ 64 KB);样本几十 KB,不放进每条请求都读的 site.config
- 适配器 onFailureSample 回调接到 runtime.siteAdapter,异步写入并删到最近 5 份,写失败只记日志
- GET /sites/{id}/failure-samples(不带正文)与 /failure-samples/{sampleId}(带正文)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 15:41:00 +08:00
1 parent 1779e1a0f2
commit 0088c1c9e7
10 files changed
+355

No files matched your search

@@ -26,6 +26,7 @@ import {
} from "../../shared-cache.ts";
import type { ServerContext } from "../types.ts";
import { CloudflareGate, withClearance } from "./service/cloudflare.ts";
import { failureSampleSink } from "./service/failure-sample.ts";
import { readBrowserUserAgent, resolveUserAgent, type UserAgentDefaults } from "./service/user-agent.ts";
// 模块运行时依赖:站点适配器与限流器(每站一个实例,令牌在 PG 跨进程共享)、RSS 抓取器、TMDB 客户端、时钟。
@@ -333,6 +334,8 @@ export class SourcesRuntime {
// 每条请求都经 siteFetch:代理 + 按账号现场取 UA(BR-4)
fetch: this.siteFetch({ id: site.id, proxy }, defaults, () => this.siteUserAgent(site.id)),
...(baseUrl ? { baseUrl } : {}),
// parse_error / challenge 的脱敏页面样本存进 site_failure_sample(每站最近 5 份)
onFailureSample: failureSampleSink(this.ctx, site.id),
});
this.adapters.set(site.id, { sig, limiter, adapter });
return adapter;