feat: favicon 边缘缓存 + CDN 整站刷新兜底 + 评论孤儿修复归档

- blog-admin tools.ts: favicon 响应加 s-maxage/CDN-Cache-Control,
  让 CF 边缘可缓存,边缘命中即不进 Worker,KV 读取量大幅下降
- scripts/refresh_cdn.js: 裸域名自动补尾斜杠(多吉云 rtype=path 是目录刷新,
  https://usj.cc 只刷首页,https://usj.cc/ 才是全站),并打印刷新清单
- 新增文档:评论孤儿修复方案、函数版证书管家方案、诊断报告
This commit is contained in:
zqlit committed 2026-10-06 12:46:05 +08:00
1 parent d273330e10
commit c2f890a0f7
5 files changed
+790 -28

No files matched your search

+29 -25
View File
@@ -75,6 +75,28 @@ async function discoverFaviconUrl(siteUrl: string): Promise<string | null> {
}
}
/**
* favicon 图片响应的统一缓存头。
*
* ★ 为什么要显式加 s-maxage / CDN-Cache-Control:
* `Cache-Control: max-age=86400` 只让**浏览器**缓存,Cloudflare 边缘默认
* **不缓存 Worker 的响应**(实测响应里没有 cf-cache-status)。于是每个访客、
* 每次清缓存、每换设备,都会真正进 Worker → 第一条语句就是 KV.get → KV 读量被放大。
*
* 加上 s-maxage(共享缓存)后,CF 边缘可缓存 24h:边缘命中就完全不进 Worker,
* KV 读取 ≈ 0,同时首包更快。浏览器侧仍保留 max-age。
* 只对**成功拿到图片**的响应使用;失败/兜底响应不缓存(避免把错误固化一天)。
*/
function imageCacheHeaders(contentType: string): Record<string, string> {
return {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400, s-maxage=86400',
'CDN-Cache-Control': 'max-age=86400',
'Cloudflare-CDN-Cache-Control': 'max-age=86400',
'Access-Control-Allow-Origin': '*',
};
}
export async function favicon(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const siteUrl = url.searchParams.get('url');
@@ -87,14 +109,7 @@ export async function favicon(request: Request, env: Env): Promise<Response> {
if (cached) {
const [meta, b64] = cached.split('|');
const body = Uint8Array.from(atob(b64), (ch) => ch.charCodeAt(0));
return new Response(body, {
status: 200,
headers: {
'Content-Type': meta,
'Cache-Control': 'public, max-age=86400',
'Access-Control-Allow-Origin': '*',
},
});
return new Response(body, { status: 200, headers: imageCacheHeaders(meta) });
}
// 失败短缓存:抓取失败的站点 1 小时内不再反复打外站
@@ -150,14 +165,7 @@ export async function favicon(request: Request, env: Env): Promise<Response> {
}
await env.RSS_KV.put(bodyKey, `${contentType}|${btoa(bin)}`, { expirationTtl: CACHE_TTL });
}
return new Response(buf, {
status: 200,
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400',
'Access-Control-Allow-Origin': '*',
},
});
return new Response(buf, { status: 200, headers: imageCacheHeaders(contentType) });
}
} catch {
/* 落到第三方兜底 */
@@ -182,14 +190,7 @@ export async function favicon(request: Request, env: Env): Promise<Response> {
bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + chunk)) as unknown as number[]);
}
await env.RSS_KV.put(bodyKey, `${contentType}|${btoa(bin)}`, { expirationTtl: CACHE_TTL });
return new Response(buf, {
status: 200,
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400',
'Access-Control-Allow-Origin': '*',
},
});
return new Response(buf, { status: 200, headers: imageCacheHeaders(contentType) });
}
} catch {
/* 彻底失败 */
@@ -212,8 +213,11 @@ function letterSvg(siteUrl: string): Response {
return new Response(svg, {
status: 200,
headers: {
// 兜底图也缓存(否则每个访客都要重跑一遍抓取失败流程 = 3 次 KV 读 + 1 次写)
'Content-Type': 'image/svg+xml',
'Cache-Control': 'public, max-age=86400',
'Cache-Control': 'public, max-age=86400, s-maxage=86400',
'CDN-Cache-Control': 'max-age=86400',
'Cloudflare-CDN-Cache-Control': 'max-age=86400',
'Access-Control-Allow-Origin': '*',
},
});