提高电子书源探测稳定性

This commit is contained in:
mtvpls
2026-04-29 16:28:45 +08:00
parent 4b8bf68179
commit fe32a9afe4
+16 -8
View File
@@ -50,6 +50,8 @@ interface ParsedFeed {
const DEFAULT_TIMEOUT_MS = Number(process.env.OPDS_TIMEOUT_MS || 20000); const DEFAULT_TIMEOUT_MS = Number(process.env.OPDS_TIMEOUT_MS || 20000);
const feedCache = new Map<string, { expiresAt: number; data: ParsedFeed }>(); const feedCache = new Map<string, { expiresAt: number; data: ParsedFeed }>();
const sourceCapabilityCache = new Map<string, { expiresAt: number; data: BookSourceCapabilities }>(); const sourceCapabilityCache = new Map<string, { expiresAt: number; data: BookSourceCapabilities }>();
const SOURCE_CAPABILITY_SUCCESS_TTL_MS = 6 * 60 * 60 * 1000;
const SOURCE_CAPABILITY_FAILURE_TTL_MS = 30 * 1000;
function asArray<T>(value: T | T[] | undefined | null): T[] { function asArray<T>(value: T | T[] | undefined | null): T[] {
if (!value) return []; if (!value) return [];
@@ -291,8 +293,8 @@ async function getSourceById(sourceId: string): Promise<BookSource> {
async function detectCapabilities(source: BookSource): Promise<BookSourceCapabilities> { async function detectCapabilities(source: BookSource): Promise<BookSourceCapabilities> {
const cached = sourceCapabilityCache.get(source.id); const cached = sourceCapabilityCache.get(source.id);
const { cacheTTL } = await resolveOPDSConfig(); const now = Date.now();
if (cached && cached.expiresAt > Date.now()) return cached.data; if (cached && cached.expiresAt > now) return cached.data;
try { try {
const feed = await getFeed(source); const feed = await getFeed(source);
@@ -311,23 +313,29 @@ async function detectCapabilities(source: BookSource): Promise<BookSourceCapabil
searchMode: searchLink ? 'opds' : source.searchTemplate ? 'template' : 'disabled', searchMode: searchLink ? 'opds' : source.searchTemplate ? 'template' : 'disabled',
catalogMode: navigationCount > 0 ? 'navigation' : entryCount > 0 ? 'flat' : 'disabled', catalogMode: navigationCount > 0 ? 'navigation' : entryCount > 0 ? 'flat' : 'disabled',
acquisitionTypes, acquisitionTypes,
lastCheckedAt: Date.now(), lastCheckedAt: now,
}; };
sourceCapabilityCache.set(source.id, { data, expiresAt: Date.now() + cacheTTL }); sourceCapabilityCache.set(source.id, { data, expiresAt: now + SOURCE_CAPABILITY_SUCCESS_TTL_MS });
return data; return data;
} catch (error) { } catch (error) {
const data: BookSourceCapabilities = { const failureData: BookSourceCapabilities = {
searchSupported: !!source.searchTemplate, searchSupported: !!source.searchTemplate,
catalogSupported: false, catalogSupported: false,
searchMode: source.searchTemplate ? 'template' : 'disabled', searchMode: source.searchTemplate ? 'template' : 'disabled',
catalogMode: 'disabled', catalogMode: 'disabled',
acquisitionTypes: [], acquisitionTypes: [],
lastCheckedAt: Date.now(), lastCheckedAt: now,
lastError: (error as Error).message, lastError: (error as Error).message,
}; };
sourceCapabilityCache.set(source.id, { data, expiresAt: Date.now() + cacheTTL / 2 });
return data; if (cached?.data && !cached.data.lastError) {
sourceCapabilityCache.set(source.id, { data: cached.data, expiresAt: now + SOURCE_CAPABILITY_FAILURE_TTL_MS });
return cached.data;
}
sourceCapabilityCache.set(source.id, { data: failureData, expiresAt: now + SOURCE_CAPABILITY_FAILURE_TTL_MS });
return failureData;
} }
} }