Files
blog/诊断报告-2026-10-06.md
T
zqlit c2f890a0f7 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/ 才是全站),并打印刷新清单
- 新增文档:评论孤儿修复方案、函数版证书管家方案、诊断报告
2026-10-06 12:46:05 +08:00

333 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 四问诊断报告(2026-10-06)
一次性回答四个问题:CDN 刷新、KV 配额、D1 评论对账、certimate 迁移。
---
## 一、多吉云刷新 & 已删文章还能访问
### 1.1 先更正我自己的话
我一开始说「多吉云那条已经是全量(`--all`)」——**这句话是错的**,实测证据如下:
```
cnb-secrets.yml:33 CDN_URL_LIST: "https://usj.cc"
```
`scripts/refresh_cdn.js` 只做一件确定性的事:把 `CDN_URL_LIST` 按逗号/换行拆开,
逐条调 `/cdn/refresh/add.json`(`rtype: "path"`)。**当前配置里这个变量只有首页一个 URL**。
结论:**多吉云确实不是全量刷新,只刷新了首页 `https://usj.cc`。**
### 1.2 那为什么删掉的文章还能访问?
不是多吉云的问题。实测这条 URL:
```
$ curl -sI https://usj.cc/202610021614.html
HTTP/1.1 200 OK
Server: marco/3.2 ← 又拍云特征头
X-Cache-Lookup: Hit From Upstream Cluster ← 命中缓存
Last-Modified: Sun, 04 Oct 2026 14:12:26 GMT ← 旧副本时间
Cache-Control: max-age=691200 ← 8 天 TTL
Age: 131684 ← 已缓存约 1.5 天
```
判别实验(关键):
| URL | 状态 | 说明 |
|---|---|---|
| `/202610021614.html`(已删除) | **200** | 缓存里还有旧副本 |
| `/zzz-not-exist-111.html`(从未存在) | **302 → /404.html** | 源站没这个文件 |
两者行为不同 ⇒ 说明**源站确实已经没有这个文件了**(本地 `content/` 和 `public/` 均已确认无此页),
现在返回 200 完全是**又拍云上的 8 天旧副本**。
### 1.3 根因:两条 CDN 线路都刷不到「已删除页」
`scripts/purge_list.js` 的逻辑是:遍历 **构建产物 `public/`**,把里面所有 `.html` 列出来提交刷新。
```js
// 全量时 walkHtml('public') —— 遍历构建产物
// 已删除的页面自然不在里面 → 永远不会被提交刷新
```
也就是说:**刷新清单只会包含「现在存在的页面」**。一个页面被删掉后,
它就从清单里消失了,于是 CDN 上的旧副本只能等 TTL 自然过期。
- 又拍云:HTML `max-age=691200` = **8 天**
- 多吉云:只刷首页,问题更明显
### 1.4 处置建议
| 方案 | 说明 | 成本 |
|---|---|---|
| **A. 什么都不做** | 等 8 天自然过期 | 0 |
| **B. 手动刷新一次**(推荐) | 把已知的已删 URL 提一次刷新,立刻生效 | 一次性 |
| C. 流水线加「删除清单」 | 构建时对比上一次产物,把消失的 `.html` 追加进 purge 清单 | 改脚本 |
对已删文章这种低频事件,**B 足够**。需要的话我可以立刻把已知的那几个已删 URL 提交到又拍云+多吉云。
---
## 二、KV 配额告警:需要调整吗?
### 2.1 告警内容
> 已使用 Workers KV 免费套餐每日限制的 **50%**,超限将返回 429;重置时间 2026-10-06 00:00 UTC。
> 免费额度:读 100,000/天、写 1,000/天、删 1,000/天、list 1,000/天。
### 2.2 谁在吃配额?(实测 4 类来源)
| 来源 | 代码位置 | 频率 | 影响 |
|---|---|---|---|
| **评论数缓存** | `comments.ts:180/206` | 每次评论列表请求,miss 时写(TTL 600s) | 读 + 写 |
| **评论数版本号** | `comments.ts:53/61` | 评论增删时写 | 写 |
| **friend-link favicon** | `routes/rss/tools.ts:86` | **每次访问友链/友圈页,每个友链一次** | **读(大户)** |
| 人机验证 / 通知节流 | `human.ts` / `admin-notify.ts` | 低 | 少量 |
**读数最大的就是 favicon 接口。** 友链共 54 条(可见 42 条),其中 15 条头像走 `/api/favicon`:
```js
// circles.html / linkify.js
img.src = RSS_API_BASE + '/api/favicon?url=' + encodeURIComponent(target);
```
每刷一次友圈/友链页 → 触发 N 次 `/api/favicon` → 每次至少 1 次 KV `get`。
favicon 本体有 24h 浏览器缓存,但**首访、清缓存、换设备都会重新打**。
### 2.3 建议:**暂时不用调整,但建议做一个小优化**
- **读 50% 是「接近」不是「超了」**,且每天 UTC 0 点清零。按当前站点的访问量,正常不会打穿。
- 即使打穿,后果是 `/api/favicon` 走兜底字母图、评论数退化为实时查库——**不是灾难性故障**。
- 免费版写限额 1,000/天,比读更容易被评论数缓存写穿。真正的风险点是**评论突然暴涨**或**有人刷评论**。
**可做的小优化(成本低、收益直接):**
1. favicon 接口响应已经带了 `Cache-Control: max-age=86400`,可以加一层 **Cloudflare Cache Rule**,
让 `/api/favicon*` 在边缘缓存 24h,这样 KV 读基本归零。
2. 若担心写限额,把 `cml:ver:` 的写入合并/延迟(比如 5 分钟内同页重复 bump 只写一次)。
**要不要升级到付费($5/月)?** 目前**不需要**。等到真收到「已超限」而不是「接近」的邮件再说。
---
## 三、D1 评论对账:老文章评论为什么没了
### 3.1 先说结论(这个和你想的不一样)
原库 **3979 条**评论 / 线上 **3423 条**。乍看差 556 条,但拆开看,**「缺失」的绝大部分根本不是评论**:
| 项 | 数量 |
|---|---|
| 线上缺失合计 | **711 条 / 63 页** |
| 其中 **点赞型记录**(内容含 `[LIKE]`) | **494 条(69%)** |
| → **真实评论型缺失** | **217 条** |
**关键发现:线上 `[LIKE]` 记录为 0 条。**
```
$ SELECT SUM(CASE WHEN content LIKE '%[LIKE]%' THEN 1 ELSE 0 END) FROM comments;
→ 0
```
说明当年数据导入时,**已经主动过滤掉了所有点赞型记录**——这是正确的设计(点赞不该占用评论表)。
所以拿原库和线上直接比条数,本身就会假性多出 494 条。
### 3.2 缺失的 217 条真实评论,按站点拆
| 站 | 缺失 | 其中点赞 | 真实 |
|---|---|---|---|
| 伍比贰 | 521 | 431 | **90** |
| 优世界 | 101 | 0 | **101** |
| 演示站 | 48 | 41 | 7 |
| 往后 | 41 | 22 | 19 |
**注意:「伍比贰」「演示站」「往后」是另外的站点,不属于 usj.cc。** 它们的数据本来就不该进本站。
### 3.3 「优世界」那 101 条对得上现站吗?
逐页核对,结果是 **一页都对不上**:
| page_key | 条数 | 现站情况 |
|---|---|---|
| `/yl` | 48 | 页面不存在(是 2022 年的旧友链页) |
| `/20241018-some-useful-win-tools` | 12 | 文章还在,但 slug 已改为 `20241019`,且**是草稿**(未发布) |
| `/27`、`/13`、`/m/17` 等 | 41 | 不存在(旧站的分页/短链) |
| `/20240921-clock-in` | 5 | 文章已删除 |
**根因不是「key 对不上」,而是这些文章本身已经不存在了。**
评论是挂在「已删除文章」上的,key 再怎么规范化也接不回来。
### 3.4 站上现存页面 vs 线上 —— 有没有真的错位?
**没有。** 用现站 284 个 slug 逐一匹配线上缺失的 63 个 key:
```
能对上现存页面: 1 页 / 4 条 (只有 /about.html)
对不上 : 62 页 / 707 条
```
**唯一一个疑似错位:`/about.html`(4 条)**——但深挖后发现**它也不需要修**:
```
id=14229 /about.html [伍比贰] 2026-02-07 👍 已点赞 Cool [LIKE]
id=14268 /about.html [伍比贰] 2026-02-12 👍 已点赞 Interesting [LIKE]
id=14269 /about.html [伍比贰] 2026-02-12 👍 已点赞 很棒的文章! [LIKE]
id=14276 /about.html [伍比贰] 2026-02-13 👍 已点赞 写得很好 [LIKE]
```
这 4 条全是**点赞型记录**,而且属于**「伍比贰」站**,本就不该进本站。
→ **结论:线上 D1 没有任何错位,完全不需要修复。**
### 3.5 线上「多出」的 7 页是什么?
| page_key | 条数 |
|---|---|
| `/20260630162329.html` | 25 |
| `/20260724101237.html` | 24 |
| `/20211001.html` | 14 |
| `/20261005113439.html` | 13 |
| `/20260629223659.html` | 10 |
| `/202610011226.html` | 2 |
| `/20261005213010.html` | 2 |
这些是**导出 db 之后新增的评论**(2026-06 之后),属于正常增长,不用动。
### 3.6 修复建议
**结论:线上 D1 不需要做任何修复。**
你的直觉「很多评论没跟页面 key 对应上、老文章没评论了」——
真实情况是:**那些评论对应的文章,本身已经不在了**(已删除 / 仍是草稿 / 属于别的站),
key 再怎么规范化也接不回来。线上数据是**干净的**。
**不建议做的**:
- ❌ 批量把「伍比贰/演示站/往后」的评论导进来 —— 那是别的站的数据(含大量点赞记录)
- ❌ 把 `/yl`、`/27` 等改成别的 key —— 没有正确的目标,改了反而污染
- ❌ 恢复 494 条点赞记录 —— 线上设计本就不存点赞(`[LIKE]` 记录当前为 0)
**如果你更看重「把老评论展示出来」**,唯一的正路是**重建这些页面**
(把已删文章从 git 历史恢复,或用 slug 重定向)。这个要单独评估,工作量比改 key 大得多。
**但请注意**:即使恢复了页面,能救回的也只是极小一部分——
「优世界」站真实缺失的 101 条里,48 条属于 `/yl`(2022 年旧友链页),
41 条属于 `/27` `/13` `/m/17` 等旧站分页短链,只有 12 条是正经文章评论。
### 3.7 那「评论少了」的体感从哪来?
很可能来自这两点(都正常):
1. **点赞不再算评论**:原库 494 条点赞在导入时被过滤,前端看到的评论数自然变少。
2. **老文章页面已删**:文章没了,评论区自然也没了。
如果确实想提升「评论数」,更实际的做法是**在新文章上做引导**,而不是抢救旧数据。
---
## 四、certimate 能否迁到 Cloudflare
### 4.1 你现在的架构
```
certimate(跑在某台服务器上)
├─ 申请:litessl CA,*.usj.cc + usj.cc,DNS-01,tencentcloud-dns
├─ 部署:dogecloud-cdn(多吉云 CDN)
└─ 部署:1panel website(那台服务器上的站点)
定时:10 12 * * *(每天 UTC 12:10)
失败邮件:177018615@qq.com
```
当前线上证书实测:
```
issuer = LiteSSL RSA CA 2025(TrustAsia)
subject = CN=usj.cc
SAN = usj.cc, *.usj.cc
有效期 = 2026-09-08 → 2026-12-07
```
### 4.2 certimate 支持 Cloudflare 吗?
**支持,而且支持两种角色:**
| 角色 | 支持 | 说明 |
|---|---|---|
| **DNS provider(申请用)** | ✅ | 填 Cloudflare API Token 即可,用于 DNS-01 验证 |
| **Deploy provider(部署用)** | ✅ | 可将证书部署到 Cloudflare |
(certimate 官方:60+ DNS 托管商、120+ 部署目标,Cloudflare 在列。)
### 4.3 你的真实目标:「把服务器干掉」
需要先厘清一件事:**usj.cc 现在到底谁在提供 HTTPS?**
从实测响应头看:
```
Server: marco/3.2 ← 又拍云
Via: T.206.M, V.403-zj-fud-202, ... ← 又拍云多级节点
```
**对外服务的是又拍云 CDN**,不是那台服务器。那台服务器承担的是:
- 跑 certimate(证书申请+分发)
- 1Panel 上的 write-server / editor-api 等自建服务
所以「干掉服务器」要分清两件事:
**① 只干掉「证书维护」这件事** → **完全可以,用 Cloudflare 托管 DNS + certimate 的 CF provider。**
路径:
1. 把 `usj.cc` 的 **DNS 托管迁到 Cloudflare**(当前在腾讯云 DNSPod)
2. certimate 里把「DNS 提供商」从 `tencentcloud-dns` 换成 `cloudflare`(填 CF API Token)
3. 证书申请照旧(litessl 或换 Let's Encrypt),DNS-01 走 CF API
4. 部署目标改为「Cloudflare」(如果把站点也迁到 CF)或保留又拍云/多吉云
**② 连「站点托管」也迁到 Cloudflare** → **可行但有取舍:**
| 优点 | 缺点 |
|---|---|
| CF 免费版自带 Universal SSL(免申请免续期) | 国内访问速度**不如又拍云/多吉云**(CF 免费版节点在境外) |
| 不用再管 90 天续期 | 免费版 Universal SSL 只覆盖 `example.com` + `*.example.com` |
| 有 CF Origin CA(15 年有效期)给回源用 | 想用 CF 自签源站证书需另配 |
### 4.4 ⚠️ 关键提醒:国内访问速度
你的读者主要在国内。**又拍云/多吉云是国内 CDN,Cloudflare 免费版是境外节点**——
如果为了省一台服务器而把整个站点切到 CF,**国内访问大概率变慢**(首包延迟从几十 ms 变成几百 ms)。
**所以我建议:**
- ✅ **可以做的**:把 `usj.cc` 的 DNS 托管迁到 Cloudflare(CF DNS 免费、管理方便、API 完善),
certimate 用它做 DNS-01,证书照旧签发后部署到**又拍云 + 多吉云**。这样证书维护不再依赖服务器上的 1Panel 部署环节。
- ⚠️ **建议保留又拍云/多吉云做 CDN**,不要为了「干掉服务器」把站点也搬到 CF。
- ❌ 如果那台服务器还跑着 write-server / editor-api,**那就不能关**——关之前先确认没有别的服务在跑。
### 4.5 你现在到底能不能关服务器?
需要先回答这个问题:**那台服务器上除了 certimate,还跑着什么?**
- 如果只有 certimate → 迁完就能关 ✅
- 如果还有 write-server / editor-api(按记忆应该有)→ **关不掉** ⚠️
建议你在关之前先列一下那台机器上的服务清单。需要的话我可以帮你连上去盘一遍。
---
## 附:本次用到的排查命令
```bash
# CDN 缓存判别
curl -sI https://usj.cc/202610021614.html | grep -iE "server|age|last-modified|x-cache"
# D1 查询(curl 走 IPv4,Node fetch 会 ENETUNREACH)
curl -s -X POST \
"https://api.cloudflare.com/client/v4/accounts/$ACC/d1/database/$DB/query" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"sql":"SELECT page_key, COUNT(*) FROM comments GROUP BY page_key"}'
# 线上是否有点赞记录
# → SELECT SUM(CASE WHEN content LIKE '%[LIKE]%' THEN 1 ELSE 0 END) FROM comments; → 0
```