merge: feat/y-meta-cache

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 19:51:50 +08:00
commit 6d37a337c9
51 files changed
+2407 -200

No files matched your search

+23
View File
@@ -82,6 +82,27 @@ export function tmdbImageUrl(path: string | null | undefined, size: TmdbImageSiz
export const CacheRegionName = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "缓存区名不合法");
export const CacheRegionParam = z.object({ region: CacheRegionName });
/**
* 命中统计口径(docs/runbook/cache.md):各进程在内存里计数,每分钟把增量加进 cache_stat(按天 × region),
* 接口返回最近 7 天各进程之和 + 当前进程还没写入的部分(别的进程最近不到一分钟的计数不在内)。
* hitRate = (l1Hits + l2Hits + staleHits) / (l1Hits + l2Hits + staleHits + misses);没有读取时为 null。
*/
export const CacheHitStats = z.object({
/** 进程内存(一级)命中 */
l1Hits: NonNegInt,
/** PG(二级)命中 */
l2Hits: NonNegInt,
/** 过期在宽限期内、先返回旧值的次数 */
staleHits: NonNegInt,
misses: NonNegInt,
/** 回源失败(网络、被拦、5xx) */
failures: NonNegInt,
/** 后台刷新次数 */
refreshes: NonNegInt,
hitRate: z.number().min(0).max(1).nullable(),
});
export type CacheHitStats = z.infer<typeof CacheHitStats>;
export const CacheRegionStat = z.object({
region: z.string(),
/** 未过期条目数 */
@@ -92,6 +113,8 @@ export const CacheRegionStat = z.object({
bytes: NonNegInt,
/** 最近一次写入 */
lastWriteAt: Timestamp.nullable(),
/** 命中统计(统一元数据缓存的 region 才有):最近 7 天,见 CacheHitStats */
hits: CacheHitStats.optional(),
});
export type CacheRegionStat = z.infer<typeof CacheRegionStat>;
+2
View File
@@ -220,6 +220,8 @@ export const EntityMetadataField = z.enum([
"cast",
"crew",
"externalIds",
// 统一元数据缓存一轮:TMDB 标志(刮削写 clearlogo.png)
"logoPath",
]);
export type EntityMetadataField = z.infer<typeof EntityMetadataField>;
+1 -1
View File
@@ -9,7 +9,7 @@ import { Id, NonNegInt, Timestamp } from "./common.ts";
export const ScrapeFileResult = z.object({
path: z.string(),
kind: z.enum(["nfo", "image"]),
/** movie_nfo / tvshow_nfo / season_nfo / episode_nfo / poster / fanart / season_poster / episode_thumb */
/** movie_nfo / tvshow_nfo / season_nfo / episode_nfo / poster / fanart / clearlogo / season_poster / episode_thumb */
role: z.string(),
action: z.enum(["written", "skipped", "failed"]),
/** 跳过 / 失败原因码(reasons.ts:scrape_exists / scrape_write_failed / scrape_image_failed …) */
+18 -2
View File
@@ -578,8 +578,19 @@ export const StorageCacheTtl = z.object({
metaSearchDays: Days,
/** 豆瓣 / bgm 榜单、热门、每日放送等列表请求(小时) */
metaListHours: z.number().int().min(1).max(720),
/** 豆瓣 / bgm 空结果(小时,对照 MP empty_ttl) */
/** 豆瓣 / bgm 列表空结果(小时,对照 MP empty_ttl);搜索空结果改按 metaMissDays */
metaEmptyHours: z.number().int().min(1).max(720),
// ── 统一元数据缓存(2026-10-06,docs/runbook/cache.md 策略表);旧库缺这些字段时按缺省补 ──
/** 豆瓣 / bgm 条目详情(douban.subject / bangumi.subject)与发现页条目 → TMDB 匹配(discover.match) */
metaDetailDays: Days,
/** 豆瓣 / bgm 查无结果:搜索空结果、条目不存在(meta.subject_miss) */
metaMissDays: Days,
/** TMDB 榜单 / 热门 / 趋势 / 按条件筛选(小时) */
tmdbListHours: z.number().int().min(1).max(720),
/** TMDB 列表空结果(小时) */
tmdbListEmptyHours: z.number().int().min(1).max(720),
/** TMDB 人物 / 图片列表(/person/*、/images) */
imageDays: Days,
});
export type StorageCacheTtl = z.infer<typeof StorageCacheTtl>;
@@ -608,9 +619,14 @@ export const DEFAULT_STORAGE_SETTINGS: StorageSettings = {
tmdbMissDays: 1,
siteDetailDays: 7,
webSearchDays: 1,
metaSearchDays: 1,
metaSearchDays: 7,
metaListHours: 6,
metaEmptyHours: 1,
metaDetailDays: 30,
metaMissDays: 1,
tmdbListHours: 6,
tmdbListEmptyHours: 1,
imageDays: 90,
},
};