fix(search): 站点搜索失败、超时、被拦截的结果不再写缓存

之前失败结果也缓存 30 秒,只在一轮全部站点失败时才撤销;某站一次失败后,
后续几次搜索都复用同一个失败。现在只缓存成功结果,失败 / 超时 / 跳过下次直接重试。

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:33:58 +08:00
1 parent 4fa001750b
commit 6e71df2769
2 files changed
+19 -15

No files matched your search

+2 -8
View File
@@ -79,13 +79,12 @@ export class ReleaseFanout {
/**
* 在给定站点上搜一组查询词:每站每词一次请求(同站多个词的结果按站点资源 ID 去重)。
* 缓存按(站点, 查询词):失败 / 超时也缓存,但一轮里全部站点都失败时不缓存(下次直接重试)。
* 缓存按(站点, 查询词):只缓存成功的结果;失败 / 超时 / 验证拦截 / 跳过不写缓存,下次搜索直接重试。
*/
async search(terms: string[], sites: SiteRow[], opts: { refresh?: boolean } = {}): Promise<FanoutHit> {
const words = [...new Map(terms.map((t) => [cacheKey(t), t.trim()])).entries()].filter(([k]) => k);
let cached = true;
let coalesced = false;
const fresh: string[] = [];
const perSite = await mapLimit(sites, Math.max(1, this.opts.concurrency), async (site) => {
const parts: SiteResult[] = [];
for (const [k, term] of words) {
@@ -105,16 +104,11 @@ export class ReleaseFanout {
const p = this.searchSite(this.rt(), site, term).finally(() => this.inflight.delete(key));
this.inflight.set(key, p);
const r = await p;
this.cache.set(key, { expires: this.opts.now() + this.opts.ttlMs, result: r });
fresh.push(key);
if (r.coverage.status === "ok") this.cache.set(key, { expires: this.opts.now() + this.opts.ttlMs, result: r });
parts.push(r);
}
return mergeSite(site, parts);
});
// 本轮新查的站点全部失败:撤掉这些缓存
const freshResults = fresh.map((k) => this.cache.get(k)?.result).filter((r): r is SiteResult => !!r);
if (freshResults.length && freshResults.every((r) => r.coverage.status !== "ok"))
for (const k of fresh) this.cache.delete(k);
this.prune();
const fetched = perSite.map((r) => r.fetchedAt);
return {