feat(cache): 通用缓存按区统计与清空接口

GET /cache/regions 列出 kv_cache 各区的未过期条数、已过期条数、占用字节、最近写入;
POST /cache/regions/{region}/clear 清空一个区(admin)。contracts 加区名中文映射
cacheRegionLabel,web 加对应 hooks。

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 18:01:19 +08:00
1 parent ad3bf8926b
commit b7460d5d0a
5 files changed
+190 -6

No files matched your search

+15
View File
@@ -95,6 +95,21 @@ export const platformApi = {
summary: "清空图片代理缓存",
response: cache.CacheClearResult,
}),
regions: ep({
method: "get",
path: "/cache/regions",
scope: "admin",
summary: "通用缓存(kv_cache)按区统计:条目数、已过期数、占用字节、最近写入",
response: cache.CacheRegionList,
}),
regionClear: ep({
method: "post",
path: "/cache/regions/{region}/clear",
scope: "admin",
summary: "清空通用缓存的一个区",
params: cache.CacheRegionParam,
response: cache.CacheClearResult,
}),
},
// ── 网页搜索(WEB-1,docs/runbook/websearch.md) ──
+56
View File
@@ -75,3 +75,59 @@ export function tmdbImageUrl(path: string | null | undefined, size: TmdbImageSiz
if (!/^[A-Za-z0-9_-]+\.(jpg|jpeg|png|webp)$/.test(p)) return null;
return `/api/v1/img/tmdb/${size}/${p}`;
}
// ── 通用缓存(kv_cache)按 region 管理(设置 → 缓存) ──
/** region 名:小写字母、数字、`.`、`_`、`-` */
export const CacheRegionName = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "缓存区名不合法");
export const CacheRegionParam = z.object({ region: CacheRegionName });
export const CacheRegionStat = z.object({
region: z.string(),
/** 未过期条目数 */
entries: NonNegInt,
/** 已过期、等清理任务删除的条目数 */
expired: NonNegInt,
/** 键与值占用字节(pg_column_size 之和,含已过期) */
bytes: NonNegInt,
/** 最近一次写入 */
lastWriteAt: Timestamp.nullable(),
});
export type CacheRegionStat = z.infer<typeof CacheRegionStat>;
export const CacheRegionList = z.object({ items: z.array(CacheRegionStat) });
export type CacheRegionList = z.infer<typeof CacheRegionList>;
/** region → 界面名;精确匹配优先,其次按前缀(`bangumi.*`);都不中返回 null(界面原样显示 region) */
const CACHE_REGION_LABELS: Record<string, string> = {
"tmdb.request": "TMDB 请求",
"tmdb.recognize": "TMDB 识别",
"douban.request": "豆瓣请求",
"douban.subject": "豆瓣条目",
"bangumi.request": "Bangumi 请求",
"bangumi.subject": "Bangumi 条目",
"meta.subject_miss": "豆瓣 / Bangumi 查无结果",
"discover.list": "发现列表",
"site.detail": "站点详情页",
"identity.detail_ids": "识别抓详情页计数",
enrich: "元数据补全",
"enrich.robots": "元数据补全 robots.txt",
websearch: "网页搜索",
"websearch.allow": "可读取的网页",
images: "图片",
};
const CACHE_REGION_PREFIX_LABELS: [string, string][] = [
["tmdb.", "TMDB"],
["douban.", "豆瓣"],
["bangumi.", "Bangumi"],
["discover.", "发现"],
["site.", "站点"],
["enrich.", "元数据补全"],
["websearch.", "网页搜索"],
];
export function cacheRegionLabel(region: string): string | null {
const exact = CACHE_REGION_LABELS[region];
if (exact) return exact;
const hit = CACHE_REGION_PREFIX_LABELS.find(([p]) => region.startsWith(p));
return hit ? `${hit[1]}(${region.slice(hit[0].length)})` : null;
}