perf(comments): 评论加载提速 + 骨架屏 + 友链弹窗 pjax 修复 + toast 统一 Message.js

优化:
- initArtalk 加节点级幂等守卫(mount.__atkInited,失败回滚),
  消除 artalk.html/mypjax 多入口导致的 4× comments + 4× pv 重复请求
- fetch 包装层对无 body 的 GET/HEAD 去掉多余 content-type,
  消掉 Artalk 对 GET /comments 的 CORS 预检(按完整 URL 缓存,
  每篇文章都重付一次 RTT)。实测评论列表 +1150~1565ms → +626ms

修复:
- 评论列表骨架屏改注入 .atk-list-body(原骨架在 #Comments 内,
  被 Artalk.init 清空),list-loaded/故障/12s 兜底移除
- 友链申请弹窗抽成 modules/friendlink.js 进 page-only bundle
  (原内联脚本在 #pjax-container 外,pjax 后 flApplyOpenModal 未定义)
- toast 统一走本地化 Message.js/Qmsg 适配层(保留 window.Toast/showToast 旧 API)

文档:
- README / 架构总览 更新;新增 评论加载优化方案.md
This commit is contained in:
zqlit committed 2026-10-04 15:00:52 +08:00
1 parent b60ddc5bf5
commit e3e3cce1a4
14 files changed
+1018 -486

No files matched your search

+122 -130
View File
@@ -1,173 +1,165 @@
# 优世界博客 · 架构总览与复杂度审计
# 优世界博客 · 架构总览
> 梳理时间:2026-10-04
> 目的:把「复杂在哪、为什么复杂、哪些是自己加的、哪些能砍」讲清楚,供架构决策。
> 方法:以代码/配置为证据,不用推测。凡属推测的地方都标了「待确认」。
> 更新时间:2026-10-04(CNB 迁移完成当日)
> 状态:**迁移已落地并跑通**(四线路全绿,单次发布约 3.5 分钟)
> 本文档只描述**现状**;迁移前的复杂度审计与选型过程见文末「相关文档」。
---
## 0. 一句话结论
## 0. 一句话
**这不是「一个博客」,而是四个独立系统,绑在一条 7 环节、跨境的、需要自己运维的发布链路上。**
前三个系统都很轻、也很健康;**复杂度几乎全部来自第四个 —— 为了支撑前三个而自建的基础设施**(Gitea + act_runner + 中转机),以及为了绕过跨境链路而打的一层层补丁。
四个子系统,一条 **3 步**的托管发布链路。构建与发布已从「自建 Gitea + act_runner + 广州中转机」迁到
**腾讯云 CNB(cnb.cool)** —— **需要自己运维的机器从 3 台降到 0 台**。
---
## 1. 全景:哪里有什么
## 1. 全景
### 1.1 四个子系统
| # | 子系统 | 技术栈 | 跑在哪 | 健康度 |
| # | 子系统 | 技术栈 | 跑在哪 | 状态 |
|---|---|---|---|---|
| **1** | **内容系统** | Hugo 0.128.2 + Ying 主题,138 篇 md,产物 424MB / 3267 文件 | 产物分发到 3 个 CDN | 主体,正常 |
| **2** | **评论系统** | `blog-admin/` = artalk-cf(Artalk v2 兼容服务端)+ RSS 机器人 | Cloudflare Workers + D1 + KV → `api.200181.xyz` | ✅ 已上线,**零服务器零运维**,架构最干净的一块 |
| **3** | **写作系统** | `write-server/`(Next.js 16 + Docker,`post.usj.cc`)<br>`write/`(本地 Windows 前端,带 vbs/bat 自启动) | 独立 Docker 主机(**待确认**)/本机 | ⚠️ **两套前端功能重叠** |
| **4** | **基础设施** | Gitea 28.0.0 + act_runner + 1Panel 中转机 + 各类备份脚本 | 境外 VPS + 腾讯云广州 | ⚠️ 正在迁移,**复杂度的主要来源** |
| **1** | **内容系统** | Hugo 0.128.2 extended + Ying 主题,138 篇 md;产物约 3200 文件 / 424MB | 产物分发到 3 个 CDN | 正常 |
| **2** | **评论系统** | `blog-admin/` = artalk-cf(Artalk v2 兼容服务端)+ RSS 机器人 | Cloudflare Workers + D1 + KV → `api.200181.xyz` | ✅ 零服务器零运维 |
| **3** | **写作系统** | `write-server/`(Next.js 16,`post.usj.cc`)<br>`write/`(本地 Windows 前端) | 独立 Docker 主机/本机 | 可用(两套前端功能重叠) |
| **4** | **发布基础设施** | **CNB 流水线**(`.cnb.yml`)+ 又拍云 + 多吉云 + EdgeOne | 腾讯云 CNB(**托管**) | **新建,已跑通** |
### 1.2 机器与服务地图
### 1.2 服务与地址地图
| 标识 | 地址 / 位置 | 承担什么 |
| 角色 | 地址 / 位置 | 说明 |
|---|---|---|
| **Gitea 主机** | `23.254.236.47:3001`(境外) | Git 仓库 + **Gitea Actions / act_runner** + artifact 存储 |
| **国内中转机** | `119.29.215.187`(腾讯云广州) | 1Panel + OpenResty + mysql + alist + vaultwarden(7 个容器)+ `sync.sh` 每分钟轮询同步 |
| **EdgeOne Pages** | 项目 `hugo-blog`,`--area overseas` | **境外线路**的托管与 CDN(腾讯) |
| **又拍云** | 对象存储 | **国内源站** |
| **多吉云** | CDN,回源又拍云 | **国内加速** |
| 腾讯云 COS | `cos.ap-*.myqcloud.com` | 曾是「境外构建产物 → 国内中转机」的中转层,**正在砍** |
| Cloudflare | `api.200181.xyz` | 评论后端 + RSS 机器人 |
| 通知渠道 | Telegram / 飞书 / QQ 邮件 | 三套并行,内容 90% 重复 |
| **代码主仓** | `cnb.cool/zqlit/blog` | CNB,**私有**;构建由它触发 |
| **代码备份** | `github.com/zqlit/blog` | 仅作备份,**不参与构建** |
| **构建 + 发布** | CNB 流水线(腾讯云国内节点,4 核 8G) | 托管,0 元(免费额度内) |
| **密钥仓库** | `cnb.cool/zqlit/blog-secrets` | CNB「密钥仓库」类型,10 项变量经 `imports` 注入 |
| **境内源站** | 又拍云对象存储 | 由 CNB 国内节点**直传**(原为 `if: false` 的死代码) |
| **国内加速** | 多吉云 CDN | 回源又拍云 |
| **境外线路** | EdgeOne Pages(项目 `hugo-blog`,`--area overseas`) | 腾讯 EdgeOne 国际站 |
| **评论后端** | `api.200181.xyz` | Cloudflare Workers |
| **通知** | QQ 邮件(`imql@qq.com`) | 已收敛为**单一通道** |
> 关键点:**境外线路和国内线路是完全独立的两套。** 境外走 EdgeOne,国内走 多吉云 → 又拍云。任一篇文章上线,两条链路上的东西都得各自到位。
> 关键点:境内、境外仍是**两条独立线路**,但**由同一条流水线一次推完** ——
> 这是本次迁移最大的结构性改善。
---
## 2. 一次发文的完整旅程
## 2. 一次发布的完整旅程(3 步)
```
① 写作 write-server (web) 或 write/ (本地 Windows)
│
② 提交 git push ──► Gitea(自建,跨境) ← 你自己维护的机器 1
│
③ 构建 Gitea Actions / act_runner ← 你自己维护的 runner 2
│ ├─ hugo --minify --gc (要的)
│ ├─ 图片优化 (sharp, 482 张) (可砍,见 §3-C)
│ └─ 草稿隐藏处理
│
④ 传递 424MB 产物 → 打 tar.zst → Gitea artifact ← 踩过 3 个坑才通
│
┌────┴─────────────────────────────┐
▼ ▼
⑤ 境外线路 ⑥ 国内线路
EdgeOne Pages 广州中转机 sync.sh(每分钟轮询) ← 你自己维护的机器 3
(runner 直接 deploy) └─ 跨境下载 376MB
└─ upx sync → 又拍云
└─ upx purge
│
⑦ 多吉云 CDN 刷新 + TG / 飞书 / 邮件 三套通知
① 写作 write-server(网页) 或 write/(本地 Windows)
│
② git push git pushall = git push origin main ; git push gh main
│ ├─ origin → cnb.cool/zqlit/blog (主仓,触发构建)
│ └─ gh → github.com/zqlit/blog (备份,不构建)
▼
③ CNB 流水线(国内节点,约 3.5 分钟,7 个 stage 顺序执行)
├─ 1. Hugo 构建 草稿隐藏预处理 → hugo --minify --gc → 算构建哈希
├─ 2. 同步到又拍云 upx sync(境内直传,非阻断但如实上报)
├─ 3. 刷新又拍云 CDN purge 首页 + sitemap/rss/archives/posts 等固定入口
├─ 4. 刷新多吉云 CDN node scripts/refresh_cdn.js
├─ 5. 部署 EdgeOne edgeone pages deploy --area overseas
├─ 6. 上报部署状态 POST api.200181.xyz/api/deploy-status
└─ 7. 邮件通知 成功 → stages 末尾;失败 → failStages
```
**对照**:一个普通 Hugo 博客 + 托管 CI 的发布链路是 **3 步**(push → 构建 → 上线)。
你这里是 **7 步,中间有 3 个环节是你自己在运维的服务器/服务**。
这就是「复杂」最直观的度量。功能一点都不多,**多的是中继环节**。
**对照**:迁移前是 **7 步,中间有 3 个环节是你自己运维的服务器**。
---
## 3. 复杂度归因:三层,性质完全不同
## 3. 流水线细节(`.cnb.yml`,284 行)
### A 层 · 外部约束 —— 不是你的选择,但躲不掉
| 约束 | 连带出来的复杂度 |
|---|---|
| **GitHub 账号被标记** | 自建 Gitea → 连带 act_runner 自运维、artifact API 不合规、缓存服务跨网段不可达 |
| **跨境链路不稳** | 境外 runner 直传又拍云 v0 API 稳定 EOF → 必须有境内兜底 |
| **国内 / 境外访问质量不同** | 被迫维护两条独立线路(EdgeOne + 多吉云/又拍云) |
| **EdgeOne Pages 国际站必须 `--area overseas`** | 部署动作只能从境外发起 |
> A 层是**根因**。B、C 两层的大部分,都是 A 层长出来的。
### B 层 · 为绕开 A 打的补丁 —— 半被迫,可以简化但不能直接删
| 补丁 | 为什么存在 | 代价 |
| 触发 | 条件 | 说明 |
|---|---|---|
| 广州中转机 `sync.sh` 每分钟轮询 | 境外直传又拍云不通 | 多一台机器要维护;每分钟一次轮询;产物要跨境搬一次 |
| COS 中转层(正在砍) | 给「境外产物 → 国内源站」当缓冲区 | 存储 + 流量账单 |
| 一套 workflow 硬塞进 Gitea | 原本为 GitHub 写 | **8 处** `github.server_url == 'https://github.com'` 分支判断 |
| artifact 单文件 tar 化 | v4 不支持 / v3 传目录 finalize 500 | 额外的打包/解包步骤 |
| 备份脚本三套(aliyun / gitea / cleanup) | 自建基础设施的必然成本 | — |
| push | 推送到 `main` | 主链路 |
| crontab | `0 9 * * *`(Asia/Shanghai) | 每天 09:00,与 push **共用同一组 stage**(YAML 锚点) |
> ⚠️ **一个需要你注意的点**(推测,建议实测):砍掉 COS 之后,中转机变成**直接跨境从境外 Gitea 拉 376MB**。跨境传输这一步并没有消失,只是从「境外 → 境内 COS(一次跨境上传)」变成「境内 ← 境外 Gitea(一次跨境下载)」。**除非实测新链路更快更稳,否则「砍 COS」省的是账单,不是复杂度。** 这条直接关系到你在做的改造是不是真的划算。
### C 层 · 自己加的 —— 可以直接砍,收益最大
| # | 冗余 | 证据 | 能砍吗 |
|---|---|---|---|
| 1 | **图片优化塞进 CI** | `Optimize Images` + `Commit Optimized Images` + `Push Image Optimizations` 三步 + `npm ci` 装 sharp(~50MB)。扫 482 张图,其中 **15 张 2021–2023 的老图永远处理不掉**,每轮重扫重试 | ✅ **最该砍**。图片在写作时本地处理,CI 就永远不需要 sharp |
| 2 | **CI 反向 commit 回仓库** | `Commit Optimized Images` 把结果 commit,`Push Image Optimizations` 再 rebase push | ✅ 砍掉图片优化后,这两步一起消失 |
| 3 | **三套通知** | `Send Telegram` ×2 + `Send Feishu` ×2 + `Send email` ×1 = **5 个 step,约占 deploy.yml 的 1/3(~400 行)**,同一份内容写 3 遍 | ✅ 留一套(TG 或飞书),其余删 |
| 4 | **永久跳过的死代码** | `Upload to UpYun` 标了 `if: false`,但 **~100 行代码仍在**(upx 下载、登录重试、sync 重试) | ✅ 要么删,要么挪进中转机 |
| 5 | **脚本重复** | `add_draft_to_hidden` 有 `.ps1`(96行) 和 `.py`(110行) 两份,README 写的是 ps1、CI 用的是 py;`deploy_*.sh` 有 4 个共 225 行 | ✅ 各留一份 |
| 6 | **两套写作前端** | `write/`(本地 Windows)与 `write-server/`(网页)功能重叠 | ⚠️ 看你的使用习惯,可合并 |
---
## 4. 量化:复杂度有多少
| 指标 | 值 |
| 机制 | 实现 |
|---|---|
| `deploy.yml` 总行数 | **1169 行** |
| job 数 | 7 个(含 1 个手动触发的网络探测 job) |
| 单个 job 的 step 数(finalize) | 12 个 |
| 通知类 step | 5 个(TG×2 / 飞书×2 / 邮件×1),约 400 行 |
| `github.server_url` 兼容分支 | 8 处 |
| `if: false` 死代码 | 约 100 行 |
| 需要自己运维的机器 / 服务 | **3 个**(Gitea 主机、act_runner、广州中转机) |
| 发布链路环节数 | **7 步**(理想 3 步) |
| 涉及的 CDN / 存储 | 4 个(EdgeOne / 又拍云 / 多吉云 / COS) |
| 构建产物体积 | 424MB / 3267 文件 |
| 真正发布所需的机器数(理想) | **1 台**(构建 + 推送)或 0 台(托管 CI) |
**一句话**:功能是一个静态博客 + 评论 + 写作后台,但支撑它的是 **7 个 job、4 个 CDN、3 台自维护机器、1169 行流水线**。
| 构建镜像 | `deploy/Dockerfile`,hugo 二进制**随仓库携带**(`bin/linux/hugo`,linux/amd64) |
| ⚠️ docker 上下文 | `by: [bin/linux/hugo]` —— CNB 的 docker build **只看得见 Dockerfile + by 列出的文件**,漏写会报 `not found` |
| 密钥注入 | `imports: https://cnb.cool/zqlit/blog-secrets/-/blob/main/secrets.yml` |
| stage 间传状态 | 落盘 `.ci_status`(原版靠 `needs.*.outputs`) |
| 并发控制 | `lock.cancel-in-progress`(对应原 `concurrency`) |
| 非阻断 | 又拍云三步 + 状态上报 + 邮件用 `allowFailure: true`,失败仍写状态如实上报 |
| 资源 | `cpus: 4`(4 核 8G) |
---
## 5. 四条可选的收敛方向
## 4. 迁移前后对比
| | 方向 A · 就地瘦身 | 方向 B · 构建源外包 | 方向 C · 构建下移境内 | 方向 D · 取消 CI |
|---|---|---|---|---|
| **做什么** | 砍 C 层:图片优化移出 CI、合并通知、删死代码、合并脚本 | 引入 gitlab.com(GitHub 已被封)或用 CF Pages 构建 | Gitea + runner 迁到广州机,构建发生在境内 | 写作后台直接构建 + 部署,Gitea 退化为纯代码托管 |
| **产物落地** | 不变 | gitlab.com 共享 runner | 境内直接推又拍云 ✓ | 写作后台直接推又拍云 + EdgeOne |
| **跨境传输** | 不变 | 视方案 | **只剩 EdgeOne 一步** | 同上 |
| **新增依赖** | 无 | gitlab.com 账号 + 跨境 push | 无 | 无 |
| **主要风险** | 只是变短,环节数不变 | **GitLab 免费版仅 400 分钟/月**(你月需约 400–900 分钟,踩线) | 广州机**内存 1.9G / 已跑 7 个容器**,hugo 构建未必跑得动 | 写作后台所在机器算力是否够;构建与发布耦合 |
| **代价** | 最低,纯收益 | 月额度可能超 | 需要瘦身后实测内存 | 需要改造写作后台 |
| **建议** | ✅ **先做,是所有方案的前置** | 备选 | 与 D 二选一,实测决定 | 与你「一键直达」的偏好最契合 |
| 维度 | 迁移前(Gitea 自建) | 迁移后(CNB) |
|---|---|---|
| 发布链路环节 | **7 步** | **3 步** |
| 自维护机器 / 服务 | **3 台**(Gitea 主机 + act_runner + 广州中转机) | **0**(构建托管) |
| 流水线定义 | GitHub Actions **1169 行** | `.cnb.yml` **284 行** |
| 产物传递 | 打 `tar.zst` → artifact → 中转机跨境下载 | 同一 pipeline **共享工作目录**,无需传递 |
| 又拍云直传 | `if: false`(境外 runner 必挂,**从未跑通**) | **已跑通**(国内节点直传) |
| COS 中转层 | 需要(存储 + 流量账单) | **已砍** |
| `github.server_url` 兼容分支 | **8 处** | **0** |
| 通知 | 3 套(TG / 飞书 / 邮件),约 400 行 | **1 套**(邮件) |
| 图片优化 + 反向 commit | CI 内每轮白跑(15 张老图处理不掉) | **未迁**(如需保留,照搬 `scripts/optimize_images.js`) |
| 构建环境 | 境外 VPS | 腾讯云**国内节点** 4 核 8G |
| 费用 | VPS 月租 | **0 元**(100GiB 仓库 + 160 核时/月) |
| 单次发布耗时 | — | **约 3.5 分钟** |
### 推荐路径
1. **先做方向 A**(无风险、纯收益、且是 B/C/D 的前置)。做完后 `deploy.yml` 预计能从 1169 行降到 600 行以内,构建里只剩 `hugo build`。
2. **再实测两件事**:① 广州机跑一次 `hugo` 的峰值内存与耗时;② 国内机跑一次 `hugo` 的耗时(对比 CI)。
3. **依据实测二选一**:
- 广州机跑得动 → **方向 C**(Gitea 迁境内,跨境只剩 EdgeOne)
- 写作后台机器跑得动 → **方向 D**(连 CI 都取消,最符合「一键直达」)
- 都跑不动 → **方向 B**(但要接受 GitLab 400 分钟/月的紧箍咒)
> 原链路里那些补丁(COS 中转 / `sync.sh` 每分钟轮询 / artifact 打包 / 兼容分支)
> **唯一根因是「runner 在境外」**。CNB 节点在国内,这一整层随之消失。
---
## 6. 需要你决定的三件事
## 5. 运维要点
1. **那 15 张 2021–2023 的老图能清吗?** 能清的话,我本地转 WebP + 修引用,然后 `Optimize Images` 这一步整个从 CI 删掉 —— 这是砍掉 C 层最大块的钥匙。
2. **构建最终放在哪一侧?** 境外(现状)/境内(方向 C/D)/外包(方向 B)。这决定后面所有改造的走向。
3. **通知留哪一套?** TG / 飞书 / 邮件 —— 留一个,其余删。另外邮件通知那份的长文本(约 50 行模板)可以整个去掉。
### 5.1 推送
```bash
git pushall # 别名 = git push origin main; git push gh main
```
用 `;` 而非 `&&` —— **一段失败不影响另一段**(实测:串联 `pushurl` 时第一个失败会中止后续)。
### 5.2 远端
| remote | 地址 | 角色 |
|---|---|---|
| `origin` | `https://cnb.cool/zqlit/blog.git` | **主仓**(fetch + push) |
| `gh` | `git@github.com:zqlit/blog.git` | **备份**(push) |
| `gitea` | 自建 `23.254.236.47:3001` | 过渡期只读参考,稳定后手工删 |
### 5.3 凭据
- 令牌存**仓库外**:`~/.workbuddy/secrets/cnb-token`
- 本仓 `credential.helper` 指向一个自定义脚本;`.git/config` **无明文**
- ⚠️ 本机全局 helper 是 **GCM**(会弹窗、对第三方 HTTPS 远端还会挂死);wincred 对 cnb.cool 有过「幽灵记录」删不掉 → 故用自定义 helper 绕开
### 5.4 密钥
- 修改密钥 → 编辑 CNB 密钥仓库的 `secrets.yml`(**网页编辑,禁 clone**)
- 本地 `cnb-secrets.yml` 只是粘贴草稿(已 gitignore,**不入库**)
- ⚠️ CNB 的 `imports` 是**一层映射**(key 就是变量名本身);又拍云服务名在 CNB 里必须叫
**`UPYUN_SERVICE`**(旧 Gitea 里叫 `UPYUN_BUCKET`,照抄会「变量未定义」)
---
## 附:本次梳理的证据出处
## 6. 遗留待办
- `.github/workflows/deploy.yml`(1169 行,全文通读)
- `blog-admin/README.md`、`write-server/README.md`、`write/`(模块定位)
- `hugo.toml`(`baseURL = https://usj.cc`)
- `gitea-backup/README.md`、`docker-compose.yml`(迁移目标与坑)
- `scripts/` 目录清单与行数统计
- 线上实测:Gitea `/api/v1/version` = `28.0.0`;广州机 `docker pull gitea/gitea:28.0.0-rootless` 可拉、`1.28.0-rootless` 不存在
| # | 项 | 说明 |
|---|---|---|
| 1 | `gitea` remote | 只读保留,稳定几天后 `git remote remove gitea` |
| 2 | GitHub 侧旧 workflow | `deploy.yml` / `cleanup.yml` / `aliyun-backup.yml` 已无用途,可删 |
| 3 | 令牌 scope | 缺 `repo-cnb-history:r`(读构建日志);补上后 agent 可自行排错 |
| 4 | Gitea 主机 | 发布链路已不依赖;是否退役取决于其它用途 |
| 5 | 广州中转机 | 同上(`sync.sh` 轮询已无用;机上另有 1Panel / vaultwarden 等服务) |
| 6 | `README.md` | 仍写着旧 GitHub Actions 流程,待更新 |
---
## 附:相关文档
- `CNB构建落地方案.md` — 迁移实施方案与实测数据
- `代码源与构建平台选型.md` — 平台对比(CNB / Gitee / GitLab / EdgeOne / 阿里云 ESA)
- `砍COS改造步骤.md` — COS 下线记录
- `EdgeOne双区域方案评估.md`、`Gitee方案评估.md`、`阿里云ESA评估.md`、`精简方案-只留CF和Hugo.md` — 决策期评估
**证据出处(现行)**:`.cnb.yml`(284 行)、`deploy/Dockerfile`、`bin/linux/hugo`、
`scripts/send_mail.js`、`scripts/refresh_cdn.js`、`scripts/setup-cnb-remotes.sh`。
已退役参考:`.github/workflows/deploy.yml`(1169 行)。