知识库到 Hexo 自动发布详细设计
知识库到 Hexo 自动发布详细设计
说明:本文件是《知识库到自动发布系统详细设计》的对外分享副本,已做脱敏处理。
以下内容已替换为占位符:本地绝对路径(<知识库目录>/<博客源码目录>)、
代码托管组织与账号(<组织>/<GitHub 账号>)、站点域名、构建主机名。
文中出现的文章标题、目录名与统计数字均取自作者本人的知识库,仅用于说明设计取舍。
需要可执行版本请使用原始文档。
设计结论(v3):采用四仓库、单向数据流架构。
OneNote(cnb.cool,私有)——内容与发布策略的唯一权威;cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next,私有)——发布源码主仓与控制面,承载同步器、.cnb.yml与分类对账;github-hexo(github.com/<GitHub 账号>/hexo-butterfly-next,私有)——cnb-hexo的单向 Git 镜像,GitHub Actions 的唯一构建输入;github-pages(<用户名>.github.io,公开)——只保存已验证的静态产物并承担 Pages 发布。
推荐采用消费者拉取模型:cnb-hexo 通过定时或手动任务读取知识库规则与最新内容;无有效差异时终止,只有生成新的发布源码快照后才继续镜像、构建和部署。
**发布范围(本版关键约束):只有通用 frontmatter(8 字段)**的文档才是发布候选;wiki frontmatter(10 字段)的文档一律不发布。因此 0X0F_知识沙盒/ 下除 原始资料/ 外的 5 个 wiki 目录(实体信息 125、概念专题 66、计划方案 5、对比分析 3、查询存档 1)全部排除。
0. 修订记录
0.1 V4 变更(实现对齐,本次)
实施阶段(2026-10)依据实测数据与运行验证,对 V3 设计做了如下修正:
| 项 | V3 设计 | V4 实现 | 依据 |
|---|---|---|---|
[[双链]] 未命中 |
降级为纯文本 | 保留原文(可配 text) |
shell 的 [[ $a -lt 100 ]] 与双链语法相同,降级会破坏代码 |
| 本地图片落盘 | source/assets/images/ |
source/assets/images/_sync/ |
父目录同时放主题头像,按目录全量清理曾误删 Avatar_*.jpg |
abbrlink 来源 |
继承已有值 + 自行生成 | 只由 hexo-abbrlink 在构建期产生,同步器只回读 |
避免维护两套 crc32 算法 |
| 存量 83 篇短链 | 原样继承 | 不继承,全部按新文档处理 | 实测 80 篇可比对文章中 46 篇 URL 变化,已确认接受 |
| 提交粒度 | 单次原子提交 | 两笔提交(正文 / abbrlink 回填) | 构建失败时正文仍可上线,仓库保持自洽 |
| 分类对账 | 已定义 | 已实现;abbrlink 改按源路径继承 |
分类迁移后短链不得变化 |
详细实现见「附录 B.6 实施后实测」。
0.2 V3 变更(实现前的设计基线)
| # | 变更 | 说明 |
|---|---|---|
| 1 | 恢复四仓库架构 | 重新引入 cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next)作为发布源码主仓与控制面;github-hexo 降级为单向镜像与唯一构建入口 |
| 2 | 恢复 “ 构建输入镜像态 “ | 四个状态、镜像触发、镜像凭证、镜像失败隔离与回滚路径全部恢复 |
| 3 | 关闭 v2 待决策项 1 | 同步器运行位置确定为 CNB 侧 cnb-hexo。CNB 对 cnb.cool 知识库有原生访问,不需要再验证 GitHub Runner → cnb.cool 的连通性 |
| 4 | 路由规则格式调整 | pathDefaults / fileOverrides 由 “ 路径 → 分类 “ 改为**” 分类 → 路径列表 “**,同一分类的多条路径集中书写 |
| 5 | 新增镜像语义与本地开发迁移的待决项 | 见 18.12、18.13 |
| 6 | 目录结构重构 | A140_杂七杂八 拆为 A140_开发工具 + A150_博客搭建;A135_MySQL数据库/A136_Git/A137_Docker 由 A130_软件开发 上移并重编号为 A141/A142/A143;A138_开源项目 收回为 A135_开源项目 |
| 7 | 0X0A 侧目录级对齐 | 5 个分支 = 5 个博客分类,pathDefaults 简化为每分类一条;fileOverrides 仅保留 原始资料/网络文章 一处混合目录 |
| 8 | ddc 全量校准 | 104 篇 ddc 前缀与所在目录编号 100% 一致;补齐 6 处空值;确立 ddc = <目录编号>.<序号> 约定(见 5.2) |
0.3 V2 变更(相对 V1 的现状修正,全部保留)
| # | v1 的写法 | 现状事实 | v2/v3 的处理 |
|---|---|---|---|
| 1 | include: "docs/**/*.md" |
知识库没有 docs/ 目录 |
改为真实目录:0X0A_知识管理/**、0X0F_知识沙盒/原始资料/**、0X0D_三味书屋/** |
| 2 | 文章级 blog-category 字段 |
全库 0 处使用;SCHEMA 两套 frontmatter 都无此字段 | 取消该字段,路由规则改放 .blog-publish.yml,零 SCHEMA 改动 |
| 3 | categoryTargets: {devops: "source/devops"} |
真实路径是 source/_posts/<中文分类>/,分类由 abbrlink.auto_category(depth:1, over_write:true) 从目录名生成 |
改为 source/_posts/<分类>;分类清单改为真实 8 个 + manual 白名单 |
| 4 | 分类由 “Hexo 源码一级目录 “ 产生 | 实际是 source/_posts/ 下的一级目录 |
措辞修正 |
| 5 | 未提及 wiki / 通用 frontmatter 之分 | SCHEMA 明确两套;wiki 套不含 publish |
新增《发布范围与内容分级》,wiki 文档命中即整批失败 |
| 6 | 未提及 Obsidian 语法转换 | ==高亮== 399 处、[[wikilink]] 860 处、三种图片解析模式 |
新增《内容转换规范》整章 |
| 7 | Manifest 无 abbrlink |
存量 38 篇博客标题与知识库不同名,按知识库 title 重算会断 38 个 URL | Manifest 增加 abbrlink / created,并规定只继承不重算 |
| 8 | npm ci |
项目用 pnpm 10.11.0 + pnpm-workspace.yaml 的 patchedDependencies,依赖 patches/hexo@8.1.2.patch 让 created 优先 |
改为 pnpm install --frozen-lockfile,Node 24 |
| 9 | github-pages “ 保留静态产物历史用于回滚 “ | 现有 workflow 是 force_orphan: true + keep_files: false,单提交无历史 |
列为必决项(改 workflow 或删除该要求) |
| 10 | 隐私门禁为 “ 建议 “ | 0X0E_Custom/ 被 Git 跟踪,其中含明文账号口令 |
门禁升级为阻断式,0X0E_Custom/** 硬排除 |
| 12 | 未区分存量与新增 | 线上已有 83 篇人工迁移文章 | 新增《存量接管》整章 |
1. 文档目的与适用范围
1.1 目的
本文用于评审并指导实现一套从私有知识库向公开 Hexo 博客自动发布内容的系统。重点解决:
- 知识库文章分散在多级中文目录中,且只有通用 frontmatter 文档才允许公开;
- 同一目录中的文章可能属于不同博客分类(重组后 0X0A 侧已无混合目录,仅
0X0F_知识沙盒/原始资料/网络文章仍是混合目录); - 博客分类由文章在
source/_posts/中的一级目录名产生,不读 front-matter 的categories字段; - 知识库可高频自动提交,但博客不应每次提交都构建部署;
- 已存在 83 篇人工迁移并已上线的文章,URL 与发布日期不能因同步而改变;
- CNB 与 GitHub 分别承担源码管理、镜像、构建与 Pages 部署职责;
- 整个链路必须可追溯、可重跑、可回滚,任一阶段失败保留上一线上成功版本。
1.2 范围
四仓库职责、发布协议与内容分级、分类路由、Obsidian 语法转换、存量接管、同步算法与 Manifest、触发时机、分支策略、凭证权限、幂等与并发、失败隔离、回滚、可观测性、实施阶段与验收标准。
1.3 非目标
- 不规定 Hexo 主题与页面视觉设计(沿用 Butterfly + 现有玻璃皮肤层);
- 不设计通用 CMS 或在线编辑器;
- 第一阶段不支持一篇文章同时发布到多个渠道;
- 不允许从公开仓库反向覆盖知识库、
cnb-hexo或github-hexo; - 不允许人工直接编辑自动生成的同步文章或
github-pages静态产物。
2. 总体架构
数据流单向:OneNote → cnb-hexo → github-hexo → github-pages。任何下游仓库不得反向成为上游内容来源。
1 | ┌────────────────────────────────────────┐ |
2.1 四个状态
| 状态 | 权威载体 | 说明 |
|---|---|---|
| 创作态 | OneNote |
私有笔记、草稿、可发布文章、图片与发布策略 |
| 发布源码态 | cnb-hexo |
经过筛选、分类路由、语法转换与资源处理后,可直接交给 Hexo 构建的确定性源码快照 |
| 构建输入镜像态 | github-hexo |
cnb-hexo 的 Git 镜像,作为 GitHub Actions 的唯一构建输入 |
| 线上静态态 | github-pages |
只保存成功构建并验证后的公开静态产物 |
2.2 关键设计原则
- 单一事实源:原稿以知识库为准;发布源码以
cnb-hexo为准;构建输入以github-hexo指定提交为准;线上内容以github-pages当前发布版本为准。 - 语义与实现分离:知识库只表达 “ 是否发布、属于哪个逻辑分类 “;
cnb-hexo负责校验、映射、语法转换、分类对账与后续构建衔接。 - 完整状态生成:每次同步基于知识库最新完整状态生成期望发布树,不能只复制最近变更文件。
- 无变化不推进:同步结果无差异时不提交、不镜像、不构建、不部署。
- 全批次原子性:任一发布文章或资源校验失败,整批失败,不允许部分成功;已知问题文件通过显式
quarantine清单隔离,而不是放宽原子性(见 4.6)。 - 失败不污染下游:同步失败不修改
cnb-hexo;镜像失败不修改github-hexo;构建失败不修改github-pages。 - URL 稳定、内容以知识库为准:已上线的 83 篇文章,
abbrlink与created继承不变(URL 与发布日期稳定);但正文与文件名是知识库的镜像——知识库改则博客改,知识库删则博客下线。 - 自动目录不可手改:Manifest 登记的同步文件与
github-pages产物由流水线独占维护。
2.3 为何采用四仓库
| 取舍 | 收益 | 代价 |
|---|---|---|
| CNB 侧承载同步器 | CNB 对 cnb.cool 知识库有原生访问,不需要跨平台只读凭据,也没有 GitHub Runner → cnb.cool 的连通性风险 | 需要 .cnb.yml 与 CNB 侧 Node 运行环境 |
| 保留 GitHub 侧构建 | 沿用已在运行的 GitHub Actions 与 peaceiris/actions-gh-pages 部署链 |
增加一次镜像、一组凭据与一个监控点 |
| 源码双仓 | CNB 主仓 + GitHub 镜像,跨平台冗余,CI 与内容源解耦 | 必须保证镜像方向单一,避免双向编辑 |
| 静态产物独立公开仓库 | 产物与源码隔离,产物历史可审计、可直接回滚 | 需要跨仓写权限 |
3. 仓库职责与边界
3.1 OneNote:私有内容与发布策略仓库
- 保存:原始 Markdown、图片资源、私有笔记与草稿、发布策略文件
.blog-publish.yml。 - 现状:不含构建与部署类流水线;仅有
.cnb.yml一个 job,在 push 时执行只读的发布协议 Lint(RESOURCES/scripts/lint.js),不构建、不发布。 - 不负责:创建或维护 Hexo 物理目录、主题、Node 环境、构建命令、Pages 部署与下游仓库写入。
3.2 cnb-hexo:私有 Hexo 源码主仓与控制面
- 仓库:
https://cnb.cool/<组织>/hexo-butterfly-next。 - 保存:Hexo 站点配置、Butterfly 配置、玻璃皮肤层、
patches/、pnpm-workspace.yaml、83 篇存量文章、同步器实现(建议tools/sync/)、同步 Manifest、自动生成文章、.cnb.yml(定时同步与源码镜像任务)。 - 现状关键事实(详见附录 B):
- 站点
https://<用户名>.github.io;language: zh-CN;permalink: posts/:abbrlink/; - 包管理 pnpm 10.11.0(
packageManager锁定),构建侧 Node 24; abbrlink.auto_category = {enable: true, over_write: true, depth: 1}→source/_posts/的一级目录名即分类;patches/hexo@8.1.2.patch使created优先于date,由 pnpmpatchedDependencies自动应用;scripts/只能放合法 JS(Hexo 硬编码该目录并递归加载所有文件),非 JS 工具放tools/。
- 站点
- 引导方式:初始内容以现有
github-hexo的main为基线导入;此后cnb-hexo为唯一写入源。 - 负责:读取知识库协议、生成发布源码、执行语法转换、维护 Manifest、执行分类对账、把源码镜像到
github-hexo。 - 不负责:执行 GitHub Actions,或保存最终静态站点。
3.3 github-hexo:私有 GitHub 源码镜像
- 仓库:
github.com/<GitHub 账号>/hexo-butterfly-next。 - 是
cnb-hexo的单向 Git 镜像,也是 GitHub Actions 的构建入口。 - 原则上禁止直接提交;任何源码变更必须先进入
cnb-hexo。 - 通过
.github/workflows/pages.yaml执行唯一一次完整 Hexo 构建。 - 构建成功后把
public原子更新到github-pages;构建失败时不触碰github-pages。 .cnb.yml会随镜像一同出现在本仓库,但 GitHub 不读取该文件,无副作用。
运维注意:现有本地开发目录
<博客源码目录>的origin目前指向 GitHub。切换为四仓库模型后,本地开发必须改为推送cnb-hexo,否则直接推送到 GitHub 的提交会被镜像覆盖或造成分叉。见 18.13。
3.4 github-pages:公开静态页面仓库
- 仓库
<用户名>.github.io,公开;禁止放入源码、私密配置、Token、未公开文章与构建日志。 - 禁止人工编辑;变更只允许来自
github-hexo的部署工作流。 - GitHub Pages 从约定分支(
main)与根目录发布。 - 是否保留静态产物 Git 历史取决于 11.4 的决策。
3.5 职责矩阵
| 职责 | OneNote | cnb-hexo | github-hexo | github-pages |
|---|---|---|---|---|
| 维护文章原稿 | 负责 | 否 | 否 | 否 |
| 定义是否发布、逻辑分类 | 负责 | 执行与校验 | 否 | 否 |
| 校验并执行 Hexo 物理目录映射 | 否 | 负责 | 镜像 | 否 |
| 生成发布源码与语法转换 | 否 | 负责 | 否 | 否 |
| 源码单向镜像 | 否 | 负责 | 接收 | 否 |
| 执行 Hexo 构建 | 否 | 否 | 负责 | 否 |
| 保存并发布静态文件 | 否 | 否 | 部署 | 负责 |
4. 知识库发布协议
4.1 协议所有权
知识库团队拥有内容发布策略,Hexo 系统只消费并执行。双方通过稳定字段与显式版本解耦;禁止根据标题、正文关键词或任意深层目录名猜测发布意图。
4.2 发布范围与内容分级(核心约束)
知识库按位置使用两套 frontmatter(见 0X0F_知识沙盒/SCHEMA.md),发布权限与套别严格绑定:
| 套别 | 适用位置 | 字段数 | 可否发布 |
|---|---|---|---|
| 通用 frontmatter | 0X0A_知识管理/、0X0D_三味书屋/、0X0F_知识沙盒/原始资料/、控制文件 |
8 | 可以(需 publish: true) |
| wiki frontmatter | 0X0F_知识沙盒/ 下 实体信息/、概念专题/、对比分析/、查询存档/、计划方案/ |
11 | 一律不发布 |
判定规则(双重保险):
- 路径级:wiki 目录不在
include中,正常情况根本不会进入候选集; - 类型级:候选文件若含 wiki 专有字段(
type/owner/sources/confidence任一),视为分类错误,整批同步失败并提示 “wiki frontmatter 文档不可发布 “。
该约束由内容负责人明确指定:知识沙盒内的 wiki 页面仅作内部知识沉淀,不上博客。
4.3 文章级 Front Matter
本设计不新增任何 frontmatter 字段,只使用 SCHEMA 通用套别中已存在的 publish:
1 |
|
| 字段 | 类型 | 默认值 | 语义 |
|---|---|---|---|
publish |
boolean | false |
必须显式为 true 才进入发布候选集 |
**为什么取消 v1 的 blog-category 字段:**SCHEMA 声明自己是 frontmatter 的单一事实源,两套基线字段已固定并规定 “ 除 copyright、publish 外均必填 “。新增字段会破坏该契约,并需要为 300+ 篇文档补字段。分类路由本质属于 “ 发布策略 “,因此改放仓库级协议文件(见 4.4),零 schema 改动。
事实:当前全库没有任何一篇文章是
publish: true。标注发布范围是本项目的前置工作量,见 16.1。
4.4 仓库级协议文件
知识库根目录维护 .blog-publish.yml。现状:文件存在但为 0 字节,需按下述内容填充(示例为按真实目录写就的 v1 协议):
1 | # 结构摘要。可执行版本以仓库根 .blog-publish.yml 为准 |
A600_时光余热当前为空目录,无需规则。
格式约定:
| 小节 | 键 | 值 | 匹配语义 |
|---|---|---|---|
pathDefaults |
逻辑分类 | 目录前缀模式列表,必须以 /** 结尾 |
文件路径命中该前缀即视为该分类 |
fileOverrides |
逻辑分类 | 具体 .md 文件路径列表 |
精确匹配单个文件,优先级高于 pathDefaults |
唯一性约束(硬性):pathDefaults 与 fileOverrides 中出现的每条路径必须在全文件范围内唯一,即不得同时挂在两个分类下,也不得在两个小节重复出现。违反则整批同步失败——否则同一文件会有两个互相矛盾的期望分类。
4.5 解析优先级
- 文件必须命中
include(窄根是唯一的发布范围入口,故无需exclude); - 文件不得命中
quarantine; - 文件名以
_开头 → 视为草稿,不参与发布(见下); publish必须显式为true;- 候选文件若带 wiki 专有字段(
type/owner/sources/confidence),整批失败; - 分类解析:
- 在
fileOverrides中按精确文件路径查找 → 命中即采用该分类;同一文件命中多个分类(违反唯一性约束)则整批失败; - 否则在
pathDefaults中收集所有命中该文件路径的模式,取最长前缀者; - 最长前缀并列且分属不同分类 → 整批失败;
- 均未命中 → 整批失败。
- 在
- 解析出的分类必须已出现在
categoryTargets中,否则整批失败。
下划线草稿约定(已确定,硬规则):文件名以 _ 开头的文章一律不参与发布——不论 publish 取值如何,也不进入差异计算(既不新增,也不因此下线博客已有文章)。
- 现有 4 篇:
_软件测试面试记录.md、_软件测试面试题总结.md(A120_软件测试)、_Clash 电脑端使用教程.md、_Clash 手机端使用教程.md(A140_开发工具)。 - 需要发布时,先去掉
_前缀重命名,再标publish: true。 - 若出现「
_前缀 +publish: true」的矛盾标记,仍按草稿跳过,并在报告中产出告警。 - 该规则不能由
quarantine替代:后者是临时隔离;下划线是内容负责人表达 “ 未完成 “ 的语义标记。
4.6 已知问题文件的隔离
设计保留 “ 整批原子性 “,但现实已存在无法一次性修复的问题(见附录 B.4:8 篇网络文章的 54 处本地图片引用全部指向不存在的 images/ 目录)。若直接启用,0X0F/原始资料 这条线第一天即整批失败。
处理方式:
source.quarantine显式列出问题文件,它们不进入候选集,因此不参与校验,也不影响整批;- 每次同步必须报告 quarantine 命中数量,并在数量变化时告警;
- 问题修复后从清单移除。禁止用 “ 自动跳过失败文件 “ 替代该清单——那会静默丢内容。
4.7 协议版本
schemaVersion 是强制字段。cnb-hexo 只接受已声明支持的版本;未知版本必须在写入任何目标文件前失败。协议升级应提供兼容窗口;禁止静默忽略未知字段或把新语义按旧规则执行。
4.8 知识库提交时的前置 Lint
知识库普通提交不触发发布,但建议执行轻量 Lint:
- YAML 与 frontmatter 可解析;
publish类型为布尔; - 未混用两套 frontmatter(wiki 字段与通用字段不得同时出现);
pathDefaults/fileOverrides的每条路径全局唯一;pathDefaults的模式均以/**结尾,fileOverrides均为具体.md路径;- 两个小节引用的分类键均已在
categoryTargets中声明; categoryTargets的分类键与目标路径唯一且格式合法;quarantine/fileOverrides/pathDefaults中出现的路径必须在工作树中存在(防止改名或删除后配置静默失效);- 敏感信息扫描:明文口令、Token、私钥、内部域名(对应
0X0E_Custom/E100_慢煮生活/账户管理.md这类文件,必须直接命中阻断)。
该 Lint 仅帮助作者提前发现问题;cnb-hexo 消费时仍必须重新执行全部校验。
5. 分类与路径映射
5.1 真实分类清单
博客现有一级分类(source/_posts/ 下目录,共 8 个、83 篇):
| 分类 | 篇数 | 知识库来源 | 状态 |
|---|---|---|---|
| 编程语言 | 29 | A130_软件开发 |
受管 |
| 软件测试 | 23 | A120_软件测试、原始资料/网络文章(AI 测试类) |
受管 |
| 开发工具 | 13 | A140_开发工具 |
受管 |
| 人工智能 | 8 | A200_人工智能 |
受管 |
| 操作系统 | 5 | A110_操作系统 |
受管 |
| 博客搭建 | 3 | A150_博客搭建 |
受管 |
| 其他内容 | 1 | A400_学习工坊 |
manual(不受管) |
| 视频剪辑 | 1 | A300_创意工坊/A310_视频剪辑 |
受管(需确认是否发布) |
需新增的分类(不存在于当前博客,启用后由同步器创建目录,Hexo 自动生成分类页):读书笔记、金融投资。
5.2 逻辑分类到物理目录
1 | categoryTargets: |
- 分类由
source/_posts/下的一级目录名生成(auto_category.depth: 1、over_write: true),因此 front-matter 里的categories字段会被覆盖——同步器应主动写入正确的categories以免误导读者,但不得依赖它决定分类。 - 知识库不得直接填写
source/_posts/…等物理路径。
对账语义:知识库策略中的 categoryTargets 是权威期望值,cnb-hexo 中的实际目录是当前值。同步时由第 8 章算法比较并修正:缺失的分类目录补齐、路径不符的修正;不属于任何已声明分类的目录不主动删除(见 8.1)。
目录编号与 ddc 约定(知识库侧):0X0A_知识管理 采用三级编号——A1x0 为分支、A1x1 起为子目录;文章 ddc 统一取 <所在目录编号>.<序号>(如 A141_MySQL数据库 下为 A141.1),与所在目录编号保持一致。当前 A100_技术体系 的 5 个分支与博客分类一一对应:
| 知识库分支 | 篇数 | 博客分类 |
|---|---|---|
A110_操作系统 |
5 | 操作系统 |
A120_软件测试 |
36 | 软件测试 |
A130_软件开发(A132–A135,纯语言与开源项目) |
29 | 编程语言 |
A140_开发工具(A141_MySQL / A142_Git / A143_Docker + 本层 3 篇) |
16 | 开发工具 |
A150_博客搭建 |
3 | 博客搭建 |
该对齐由 2026-10-17 的目录重构达成:
A140_杂七杂八拆为A140_开发工具+A150_博客搭建;MySQL/Git/Docker三个子目录由A130_软件开发上移至A140_开发工具并重编号为A141/A142/A143;A138_开源项目收回为A135_开源项目。知识库内 104 篇ddc已按此约定全量校准(前缀与目录编号 100% 一致,无空值)。
5.3 文件命名与重名
- 目标文件名与知识库文件名完全一致,不做任何改写:不剥离
1.序号前缀、不追加语言前缀、不拼接父目录。
1 | 0X0A_知识管理/A100_技术体系/A130_软件开发/A133_Golang系列/Golang-04:函数详解.md |
- 分类目录仍为扁平结构(
source/_posts/的一级目录 = 分类),但文章名不扁平化。 - 若两个源文件映射到同一目标路径(同名冲突),整批失败并列出全部冲突源文件;不允许后写覆盖。
- 校验范围包含存量 83 篇:新文章与已存在博客文章同名(导致
abbrlink冲突)同样整批失败。 - 实测:
0X0A_知识管理、0X0D_三味书屋、0X0F_知识沙盒/原始资料三者之间当前不存在文件名冲突。 - 对存量的影响:博客侧有 38 篇在发布时改过文件名,与知识库不同。按本规则,同步后这些博客文件将被重命名为知识库的名字;
abbrlink按 7.4 规则继承不变,URL 不受影响(对照表见附录 A)。
5.4 图片与本地资源
知识库存在三种图片引用形态,同步器分别处理(实测数据见附录 B.3):
| 形态 | 示例 | 处理方式 |
|---|---|---|
| 远程 URL |  |
保持原链接,不下载、不重写。此类引用占 0X0A 的绝大多数(957 处) |
| 文件相对路径 |  |
以该 .md 所在目录为基准解析 → 复制到 source/assets/images/_sync/ → 改写为 /assets/images/_sync/<文件名> |
| 裸文件名(Obsidian 全局附件目录) |  |
按 .obsidian/app.json 的 attachmentFolderPath = RESOURCES/images/wiki-images 解析 → 同样复制到 source/assets/images/_sync/ 并改写 |
规则:
- 统一落盘目录为
source/assets/images/_sync/,不再按文章隔离子目录。_sync是同步器独占子目录:可安全地按 “ 是否仍被引用 “ 整体清理;父目录source/assets/images/下还放着主题头像等人工资源,绝不可当作受管目录整体清理(曾误删Avatar_*.jpg)。若配置指向父目录,清理跳过并告警。 - 只复制正文实际引用的资源:候选中未被任何发布文章引用的图片不进入发布树;缺失引用(引用了但文件不存在)仍整批失败(已知问题文件走
quarantine)。 - 同名冲突:文件名相同且内容一致 → 复用同一份;文件名相同但内容不同 → 整批失败并列出冲突来源,禁止静默覆盖。
- 远程 URL 不得改写(含不得改动查询串);被排除目录(如
RESOURCES/**)的图片引用按 5.5 处理,不复制。 - 资源缺失、越权引用被排除目录、目标路径逃逸时,整批失败。
5.5 附件与 Git LFS
- 该目录不在
include中,且引用行由content.stripReferencePrefixes剥离,因此不会出现在cnb-hexo/github-hexo/github-pages; - 同步器不得触发 LFS 拉取(避免私有附件进入构建环境);两个源码仓库均须确认不会把 LFS 指针文件当正文处理;
- 因此纯附件型文章(正文只有附件链接、无实际内容)发布后为空文,应在发布标注阶段直接排除,不设
publish: true。
5.6 自动管理边界
- 采用 Manifest 方案(而非
synced子目录),因为auto_category.depth: 1要求文章直接位于分类目录下; - Manifest 精确记录每个受管目标文件;
- 同步器只删除 “Manifest 上一版本登记、本次期望状态不再包含 “ 的文件,禁止对
source/或source/_posts/执行无边界删除; - 存量 83 篇全部纳入差异计算(见 7.2);Manifest 记录其目标路径与
abbrlink,以便执行重命名、内容覆盖与删除。
6. 内容转换规范
知识库是 Obsidian 库,含大量 Hexo 原生不支持的语法。这是最容易 “ 上线即翻车 “ 的部分,必须先于自动化实现。
6.1 Obsidian 语法转换表
| 语法 | 实测数量 | Hexo 直接构建的后果 | 转换规则 |
|---|---|---|---|
==高亮== |
0X0A 285 处、0X0F 114 处 |
—— | 不转换。实测这些 == 全是代码比较运算符([ $a == $b ]、currency == 'USD')与 ==> 箭头,无一为真高亮;开启转换会破坏代码,故该逻辑已删除 |
[[目标]] / [[目标|别名]] |
0X0F 855 处、0X0A 5 处 |
输出字面量 [[…]] |
建立「知识库路径 → 目标文章 permalink」索引(来源是 Manifest 的 abbrlink);命中则转 [别名](/posts/<abbrlink>/);未命中默认保留原文(wikilinkFallback: keep),可配 text 降级为纯文本 |
![[]] 嵌入 |
2 处,均在行内代码块中 | 无 | 无需处理;若出现真实嵌入则按 5.4 处理 |
> [!quote] callout |
0X0A 4 处、0X0D 1 处 |
显示为 [!quote] 字面量 |
转 > **标题** + 正文引用块;不依赖主题 tag 插件 |
<br /> 等行内 HTML |
大量 | 正常(marked 直通 HTML) | 原样保留 |
%%注释%% |
0X0A 0 处、0X0E 3 处 |
原样显示 | 整段删除 |
正文中的 #标签 |
少量 | 原样显示为文本 | 第一阶段不处理,仅报告 |
网络文章还有固定的头部引用块,需转换后保留(不要丢掉来源信息):
1 | > **公众号**: 听玛玛的话 |
6.2 Front Matter 输出白名单
博客现用字段(其仓库 AGENTS.md 明确 “ 不要写 summary、copyright、aliases、toc“):
1 |
|
映射规则:
| 输入(知识库通用 frontmatter) | 输出(博客 front-matter) | 说明 |
|---|---|---|
title |
title |
存量文章保持现有值(见 7.3) |
description |
description |
直接映射 |
tags |
tags |
tags: 为空(YAML null)时输出 tags: [] 并发 Lint 警告 |
ddc |
ddc |
直接映射 |
created |
created |
存量文章保持博客现值 |
updated |
updated |
保留;随知识库 bump |
publish |
— | 丢弃 |
copyright |
copyright: false |
源值为布尔(true/false,2026-10-18 起);仅在值为 false 时输出。主题判断 page.copyright !== false,这是按文章关闭版权声明的唯一途径;true 不输出(走主题默认版权声明) |
type / owner / sources / confidence |
— | wiki 字段,出现即整批失败 |
7. 存量接管
7.1 现状
| 项 | 数量 |
|---|---|
| 线上博客文章总数 | 83 |
| 与知识库文件名同名(可直接对上) | 45 |
| 文件名不同(发布时改过标题) | 38 |
38 篇的完整映射见附录 A。典型改名模式:
- 序号前缀被剥离:
Golang-01:如何快速运行 Golang 程序→如何快速运行 Golang 程序 - 系列编号被改写:
Pytest 测试框架三部曲(一)→Pytest 测试框架三部曲 - 上 - 序号与副标题被压缩:
AI 系列-4:计算机视觉-给机器装上眼睛→AI 系列:计算机视觉-给机器装上眼睛 - 错字/漏字:知识库
1.信息系统项目管理师教程-第4版→ 博客信息系项目管理师教程-第4版(缺” 统” 字)
7.2 三类文章
| 类别 | 数量 | 同步器行为 |
|---|---|---|
已接管(publish: true 且已登记映射) |
0(起步) | 进入 Manifest,完整状态生成 |
存量未接管(已在线上,但知识库未标 publish 或未映射) |
83 | 参与差异计算,以知识库为准(见下) |
新增(知识库 publish: true,博客无对应文件) |
— | 正常生成并新增 |
「以知识库为准」的具体含义:
- 博客文章在知识库有对应来源 → 正文以知识库为准覆盖;文件名改为知识库名(见 5.3);
abbrlink与created按 7.4 继承,URL 不变。 - 博客文章在知识库找不到对应来源 → 视为知识库已删除该内容,从博客中移除。判定依据
adoption-map.yml(知识库路径 → 博客文件)而非文件名猜测。 - 知识库侧为
publish: false、或落入quarantine→ 对应博客文章下线。
实测结论(2026-10-17):博客 83 篇全部能在知识库找到对应来源,0 篇缺失。因此启用「以知识库为准」当前不会删除任何线上文章,可安全切换。该结论必须随每次同步重新计算并写入报告;一旦出现 “ 博客有、知识库无 “ 的条目,须在报告中显式列出并告警。
7.3 接管流程(逐篇显式)
- 在
cnb-hexo/tools/sync/adoption-map.yml中登记:知识库源路径 → 博客现有文件(含分类); - 在知识库对应文章的 frontmatter 中置
publish: true; - 首次同步对已接管文章执行内容一致性检查:以博客现有文件为基线,比对转换后的正文是否一致;
- 一致的:写入 Manifest,纳入受管;
- 不一致的(如正文已被人工润色):标记为
manual-diverged,默认采用知识库版本,并在报告中列出差异,供人工决定是否把博客侧改动回补到知识库;
- 首批建议只接管 5 篇作为试点,验证
abbrlink、日期、图片、分类、渲染均无变化后再批量。
7.4 abbrlink 与 created 继承规则(URL 稳定性)
permalink: posts/:abbrlink/。abbrlink 的唯一来源是 hexo-abbrlink 插件:它挂在 before_post_render 过滤器上,在 hexo generate 阶段读 title 算 crc32 并写回源文件 front-matter。同步器运行在写文件之前,此时插件尚未运行,因此不自行计算 abbrlink,只做「继承」与「回读」。
- 规则 R1:目标文件不存在 → 由插件在构建期生成,同步器不预置值;
- 规则 R2:Manifest 中已有
abbrlink时,同步器写入目标文件;插件检测到该值存在即跳过重写(if (!abbrlink || config.force)),构建后工作区保持干净; - 规则 R3:
abbrlink的继承键是知识库源路径(身份稳定),目标路径仅作兜底。分类迁移改变目标路径时短链不得变化; - 规则 R4:
created直接取知识库值。存量 83 篇已随 2026-10 清空,不再存在「博客现值优先」的优先级问题; - 规则 R5:闭环分两笔提交。第一笔写正文(新文章无
abbrlink);构建后运行readback把短链收进 Manifest;第二笔用「规范格式 +abbrlink」重写并提交。此后插件不再重写,状态收敛。构建失败时第一笔仍有效(正文上线、短链缺失、双链保留原文)。
8. 同步算法与 Manifest
8.1 完整期望状态算法
- 锁定知识库
main的具体提交 SHA; - 读取并校验
.blog-publish.yml、schemaVersion、categoryTargets,以及pathDefaults/fileOverrides的路径唯一性与分类声明合法性;同时读取cnb-hexo当前source/_posts/目录结构与 Manifest; - 扫描
include,排除quarantine; - 解析全部候选文件的 frontmatter;
- 校验 frontmatter 套别(含 wiki 字段则整批失败);
- 筛选
publish: true; - 按 4.5 的规则解析逻辑分类(
fileOverrides精确命中优先,否则pathDefaults最长前缀); - 应用
categoryTargets映射目标目录、文件名与资源路径; - 执行第 6 章语法转换与资源复制;
- 目标路径冲突检测、图片可解析性校验(缺失即整批失败)、路径安全校验;
- 生成完整期望发布树与新 Manifest(
contentHash记录内容指纹); - 执行 8.2 分类对账:补齐声明目录、迁移受管文章、清理仅含受管内容的旧目录;
- 与
cnb-hexo当前状态比对,无差异则成功结束;有差异则增量落盘并提交推送(第一笔提交:正文 + 资源 + Manifest); - 在 CNB 运行一次
hexo generate(不部署),由hexo-abbrlink在before_post_render阶段把短链写回源文件; - 运行
readback把短链收进 Manifest; - 再次增量落盘并提交推送(第二笔提交:abbrlink 回填并规范化),随后强制镜像至
github-hexo。
8.2 分类对账算法
在文章分类解析与发布树生成之后、最终 diff 与提交之前执行。它只校准 “ 分类到物理目录 “ 的配置与受管目录。
- 读取知识库
categoryTargets(期望)与cnb-hexo实际source/_posts/目录(当前); - 比较键集合:期望有而仓库缺少的分类创建目录;
- 只增不删:未被
categoryTargets声明的既有分类目录不主动删除,仅补齐与修正已声明分类; - 同名键目标路径不一致时,以知识库为准迁移该分类下的受管文章;
- 期望已删除且仅含受管内容的旧目录删除;
- 如待删除目录含人工或未知非托管文件、目标路径越界、两个分类映射到同一路径,整批失败等待人工处理;
- 分类修正、目录创建/删除、文章迁移与 Manifest 更新纳入同一个提交。
与 v1 的差异:v1 的「Hexo 有而策略缺少的分类一律删除」在存在人工分类的现状下会误删,故改为只增不删(
source/_posts已在 2026-10-17 清空,后续由同步器按categoryTargets重建)。
8.3 Manifest 结构
1 | { |
Manifest 用于处理删除、下线、移动、重命名与改分类:
publish: true → false:删除旧目标;- 源文件删除:删除旧目标;
- 分类改变:删除旧分类文件、新增新分类文件(
abbrlink不变); categoryTargets对账结果:记录期望映射、修正前实际映射与操作摘要;- 源文件移动但目标不变:更新来源记录,不重复创建页面。
8.4 幂等要求
- 相同知识库提交、相同发布策略、相同
cnb-hexo基线重复执行,必须生成完全相同的发布树、对账结果与 Manifest;第二次执行不产生差异; - 时间戳不得写入会造成每次 diff 的源码文件;构建时间只写入构建产物元数据;
- 构建后工作区必须干净(对应 7.4 规则 R5)。
9. 触发模型与端到端流程
9.1 触发总表
| 触发点 | 执行位置 | 动作 |
|---|---|---|
| 知识库普通 push | 知识库 CNB | 仅执行发布协议 Lint;不发布 |
| 每日定时或人工手动 | cnb-hexo CNB |
拉取知识库最新 main,生成发布源码;有差异才提交 |
cnb-hexo main 更新 |
cnb-hexo CNB |
将源码单向镜像到 github-hexo main |
github-hexo main 更新 |
GitHub Actions | pnpm install --frozen-lockfile → pnpm run build → 验证 → 部署 |
github-pages 发布分支更新 |
GitHub Pages | 发布新静态版本 |
9.2 推荐时序
- 知识库团队一天内多次提交文章,不触发正式发布;
- 每天固定时间,或操作者手动启动
cnb-hexo的内容同步任务; cnb-hexo读取知识库最新main并锁定具体提交 SHA;- 同步器生成完整发布源码快照并校验;
- 无差异时任务成功结束,后续镜像、构建与部署均不触发;
- 有差异时
cnb-hexo创建一条自动提交并推送main; cnb-hexo的镜像任务将该提交推送到github-hexomain;github-hexo的 GitHub Action 被 push 事件触发,执行唯一一次完整构建;- 构建与产物校验通过后更新
github-pages; - GitHub Pages 发布新版本。
9.3 同步器运行位置
**已确定:同步器在 cnb-hexo 侧由 CNB 流水线执行。**理由:
- CNB 与知识库同平台,读取
cnb.cool/<组织>/OneNote无需跨平台凭据,也不存在 GitHub Runner 访问 cnb.cool 的连通性风险; - 镜像任务本就需要在 CNB 侧执行,同步与镜像合并为一条流水线,减少一组跨平台凭据;
- 知识库侧只引入只读 Lint 流水线(不构建、不部署、不写下游),与 3.1 的边界约定一致。
实现要点:
.cnb.yml位于cnb-hexo仓库根目录,声明定时触发与手动触发两个入口,二者调用同一套同步实现,禁止维护两套规则;- 运行环境需提供 Node(与构建侧一致,建议 24)与 Git;同步器为 JS,放
cnb-hexo/tools/sync/; - 流水线内不得触发 LFS 拉取。
实际实现(cnb-hexo/.cnb.yml):
1 | main: |
三个入口共用同一份 job 定义与同一把 lock(key: kb-publish、wait: true),
刻意不监听 push —— 流水线自身会向 main 提交,监听 push 会自激成死循环。
单个 job 依次执行:克隆知识库 → sync --apply → push → pnpm install && pnpm build
→ readback → sync --apply → push → git-sync 强制镜像到 github-hexo。
9.4 手动入口
- 手动同步知识库:紧急发布或定时失败补跑;
- 手动重跑
github-hexo构建:源码不变,仅重新构建与部署(现有 workflow 已支持workflow_dispatch); - 手动回滚
github-pages:回退到上一成功静态提交。
9.5 CNB 与 GitHub 配置边界
- CNB 读取仓库根目录
.cnb.yml,用于定时同步与源码镜像; - GitHub 读取
.github/workflows/*.yml,用于 Hexo 构建与静态部署; - 两类配置在同一份源码中共存;镜像到 GitHub 后,GitHub 只执行自己的 workflow,忽略
.cnb.yml。
10. 分支与提交策略
10.1 分支
| 仓库 | 分支 | 规则 |
|---|---|---|
OneNote |
main |
创作与发布策略稳定基线 |
cnb-hexo |
main |
Hexo 发布源码主分支;接受人工源码变更与自动内容同步提交 |
github-hexo |
main |
只接受 cnb-hexo 镜像;禁止直接提交 |
github-pages |
main |
只接受 Action 写入的静态产物(publish_branch: main) |
流程变更提醒:现有本地开发目录
<博客源码目录>的origin指向 GitHub。切换到四仓库模型后,日常开发必须先推送cnb-hexo,再由镜像流向 GitHub。见 18.13。
10.2 自动提交规范
1 | 文档修改:同步知识库发布快照 |
正文附 Knowledge-Commit: <sha> 与 Protocol-Version: 1 尾注。提交信息风格遵循该仓库 .trae/rules/git-commit-message.md(<type>:<subject> + 原因分析/修改方法)。
10.3 提交一致性
理想情况下 cnb-hexo 与 github-hexo 是同一 Git 提交的镜像,保持相同 commit SHA,不应重新复制文件制造无关提交。
10.4 禁止双向编辑
github-hexo的任何修复必须回到cnb-hexo实施;github-pages的任何修复必须回到github-hexo源码或构建流程实施;- 同步文章不得在
cnb-hexo中人工修改,否则下次完整同步会覆盖; - 博客侧的人工改动会被知识库覆盖;确有价值的内容必须先回补到知识库再发布(
manual-diverged报告用于发现这类改动)。
11. 构建与部署
11.1 现状对齐(重要修正)
v1 写的 npm ci 不可用:本项目用 pnpm 10.11.0,且依赖 pnpm-workspace.yaml 的 patchedDependencies 应用 patches/hexo@8.1.2.patch(让 created 优先于 date)。用 npm 会安装失败或使日期补丁失效,导致全站日期错误。
现有 workflow(.github/workflows/pages.yaml)实际做法:
| 项 | 现值 |
|---|---|
| 触发 | push: branches: [main] + workflow_dispatch |
| Runner | ubuntu-latest |
| Node | 24 |
| 包管理 | pnpm(版本由 package.json 的 packageManager 决定),pnpm install --frozen-lockfile |
| 构建 | pnpm run build |
| 时区 | sudo timedatectl set-timezone "Asia/Shanghai"(日期正确性依赖) |
| 现有校验 | test -f public/index.html |
| 部署 | peaceiris/actions-gh-pages@v4,publish_branch: main,external_repository: <owner>/<owner>.github.io,keep_files: false,force_orphan: true |
11.2 需要补充的构建步骤
- 检出
github-hexo指定提交; - 安装固定 Node(24)与 pnpm;
pnpm install --frozen-lockfile(含 hexo 日期补丁应用校验);hexo clean与hexo generate;- 验证
public/index.html之外:分类页数量与source/_posts/分类目录数量一致、归档页存在、search.xml存在、关键静态资源(/glass/*)存在、abbrlink页面可访问; - 生成构建元数据
public/build-meta.json; - 校验构建后工作区无未提交改动(7.4 R5);
- 仅在全部验证通过后更新
github-pages。
11.3 构建元数据
1 | { |
cnbHexoCommit 与 githubHexoCommit 在镜像正常时应相同;若不同,说明镜像滞后,应告警。该文件不得包含私有仓库地址、Token、个人信息或内部文件路径。
11.4 静态产物历史(必须决策)
v1 要求 “ 静态产物保留 Git 历史用于审计与快速回滚 “,但现有 workflow 使用 force_orphan: true + keep_files: false,每次部署只有一个提交、无历史,两条要求直接矛盾。二选一(见 18.1):
- 方案 甲:去掉
force_orphan,保留历史 → 满足审计与快速回滚,仓库体积增长; - 方案 乙:保持
force_orphan,回滚改为 “ 重跑历史源码提交的 workflow”→ 放弃静态产物历史。
11.5 并发控制
1 | concurrency: |
github-hexo 连续收到多个提交时,只让最新提交完成部署;已进入写入静态产物的临界阶段不得产生半成品。CNB 侧的同步任务同样应设置并发串行,避免两次同步互相覆盖。
12. 权限、凭证与隐私
12.1 最小权限矩阵
| 执行方 | 目标 | 权限 | 建议凭证 |
|---|---|---|---|
cnb-hexo 同步任务 |
OneNote(cnb.cool) |
只读 | CNB 平台内仓库只读授权(同平台,无需跨平台 Token) |
cnb-hexo 镜像任务 |
github-hexo |
Contents 写 | Fine-grained PAT 或专用 Deploy Key,存于 CNB 密钥 |
cnb-hexo 同步任务 |
cnb-hexo |
Contents 写 | 平台内置凭据 |
| 构建部署 | github-pages |
Contents 写 | 现有 DEPLOY_TOKEN(仅授权该仓) |
| 构建 checkout | github-hexo |
读 | DEPLOY_TOKEN(现有做法) |
各凭证只允许单一目标仓库;禁止跨仓库复用同一 Token。cnb-hexo 侧只需一个 “ 能写 github-hexo“ 的凭据,且不得具备 github-pages 写权限。
12.2 隐私门禁(升级为阻断式)
- 知识库默认私有,缺少
publish: true时绝不公开; quarantine优先级最高,_草稿其次;0X0E_Custom/**硬排除:该目录被 Git 跟踪(20 个文件),其中0X0E_Custom/E100_慢煮生活/账户管理.md含明文账号与口令。除路径排除外,同步器必须执行敏感内容扫描(口令/Token/私钥/内部域名/本地绝对路径),命中即整批失败;- 同步前扫描目标内容,禁止包含 Token、私钥、内部域名、未授权个人信息与本地绝对路径;
github-pages为公开仓库,部署前检查产物中不存在源 Markdown、私有配置以及 source map 中的敏感路径;- 日志不得打印凭证或包含敏感内容的完整正文;
- 禁止将
.agents/、.obsidian/、Lark/、AGENTS.md等仓库元数据纳入发布树; - 注意:发布源码快照会同时存在于
cnb-hexo与github-hexo两个私有仓库,” 未公开内容不得进入发布树 “ 这一约束在这两个仓库同样适用。
13. 异常处理、回滚与恢复
13.1 失败隔离
| 失败位置 | 系统行为 | 恢复方式 |
|---|---|---|
| 知识库协议 Lint | 阻止错误规则进入稳定分支或告警 | 修复 frontmatter 或协议配置后重新提交 |
cnb-hexo 同步校验 |
不修改 cnb-hexo;整批失败 |
修复知识库或映射后手动重跑;已知问题文件走 quarantine |
| 语法转换 / 资源解析 | 同上(失败不落盘) | 修复源文件或加入 quarantine |
| CNB → GitHub 镜像 | cnb-hexo 已保存正确版本,github-hexo 保持旧版本 |
凭据或网络恢复后重试同一提交 |
| Hexo 构建 | 不更新 github-pages,线上保持上一版本 |
修复源码或依赖后重新触发 |
github-pages 写入 / Pages 发布 |
保留上一成功提交或部署 | 重试部署,或回滚到已知成功提交 |
13.2 回滚路径
- 内容回滚:知识库回退文章或发布策略,下次同步生成新的下线或恢复提交;
- 源码回滚:
cnb-hexo回退到历史发布源码提交,再镜像并重新构建; - 构建重放:对
github-hexo的历史提交手动运行 workflow(workflow_dispatch),生成相同版本静态站; - 线上快速回滚:取决于 11.4 的决策——甲方案直接回退静态产物提交;乙方案重放上一成功源码提交的构建。
应区分 “ 立即恢复线上 “ 的静态回滚与 “ 修复权威源 “ 的内容回滚;静态回滚止损后仍须在知识库或 cnb-hexo 修复事实源,避免下次自动任务再次部署错误版本。
14. 可观测性与审计
14.1 每次同步应记录
- 知识库提交 SHA 与协议版本;
- 扫描、排除、
quarantine、发布、下线、冲突与失败文章数量; - 新增/修改/删除/分类移动的目标文件清单,以及分类对账的补齐、删除与路径修正记录;
- 每条文章的命中路由(
routingRule:fileOverrides:<分类>或pathDefaults:<分类>); - 语法转换统计:
==转换数、wikilink 命中数、降级为纯文本的 wikilink 数(代表断链风险); - 敏感信息扫描结果;
cnb-hexo生成提交 SHA;触发方式(定时/手动)。
14.2 每次镜像应记录
- 源提交 SHA 与目标提交 SHA 是否一致;
- 镜像耗时与结果;失败原因。
14.3 每次构建应记录
github-hexo提交 SHA、Node 与 pnpm 版本;- 构建耗时、生成文件数量、分类页与关键资源验证结果;
github-pages目标 SHA 与最终 Pages URL。
14.4 告警建议
- 定时同步连续失败;
- 协议版本不兼容;
- 目标重名、分类非法或私有内容泄露风险;
quarantine命中数量增加;- wikilink 降级数量突增(内容断链信号);
- 镜像长期落后于
cnb-hexo(提交 SHA 不一致); - 构建失败或 Pages 发布失败;
- 同步后工作区不干净(
abbrlink写回冲突)。
15. 关键取舍与备选方案
15.1 为何采用四仓库
| 取舍 | 收益 | 代价 |
|---|---|---|
| 知识库与 Hexo 源码分离 | 私有内容不会自然进入博客源码;创作与发布职责清晰 | 需要发布协议与同步器 |
| CNB 与 GitHub 源码双仓 | CNB 侧原生访问知识库;保留 GitHub Actions 构建入口与跨平台冗余 | 增加一次镜像、一组凭据与一个监控点 |
| 静态产物独立公开仓库 | 产物与源码隔离,产物历史可审计、可直接回滚 | 需要跨仓写权限 |
15.2 已否决方案
- 每次知识库提交立即发布:构建频繁,草稿过程可能反复上线;
- 每天首次提交发布:当天后续提交无法及时进入发布结果;
- 由 GitHub Actions 直接拉取 cnb.cool 知识库:需要跨平台只读凭据并承担连通性风险,且与 CNB 侧镜像任务重复;
- 新增
blog-categoryfrontmatter 字段:破坏 SCHEMA 单一事实源契约,需为 300+ 篇补字段; - 只靠 front-matter 的
categories字段决定分类:auto_category.over_write: true会覆盖它; - 只按知识库目录决定分类:
原始资料/网络文章是 AI 测试与金融的混合目录,仍须fileOverrides; - 增量复制最近改动文件:无法可靠处理删除、下线、移动与分类变化;
- 无边界删除:可能误删 83 篇人工内容;
- 自动跳过校验失败文件:静默丢内容,必须用显式
quarantine清单; - 在
github-hexo或github-pages直接编辑:破坏单向事实链。
16. 实施阶段
16.0 阶段零:仓库引导与基线冻结
- 确认
cnb-hexo(https://cnb.cool/<组织>/hexo-butterfly-next)的初始状态:空仓则从github-hexomain导入;已是镜像则明确把镜像方向改为cnb-hexo→github-hexo; - 记录当前构建耗时与产物文件数,作为回归基线;
- 冻结存量 83 篇的
abbrlink快照(source/_posts/**→abbrlink),后续任何同步都以此为准; - 切换本地开发流程:本地仓库增加
cnb-hexo远端并以其为主(见 18.13)。
16.1 阶段一:协议与发布标注(前置工作量最大)
- 填充
.blog-publish.yml(v1 协议),并纳入 Git 跟踪; - 依据
pathDefaults/fileOverrides为候选文章逐篇标注publish: true(当前全库为 0 篇); - 决定新增分类(
读书笔记/金融投资)与其他内容的归属; - 实现 Schema 校验、include/quarantine、路径唯一性校验、分类解析、对账与冲突检查;
- 以 5 篇试点验证端到端。
16.2 阶段二:内容转换
- 实现
==→<mark>、wikilink → permalink、callout、注释删除; - 实现三种图片引用形态的解析与资源复制;
- 处理 LFS 附件引用策略;
- 输出转换报告,确认 wikilink 降级数为 0(或已接受)。
16.3 阶段三:CNB 自动同步与镜像
- 在
cnb-hexo配置.cnb.yml:定时与手动同步入口,无 diff 不提交; - 配置
cnb-hexo→github-hexo单向镜像; - 验证同一源码提交在两个仓库的 SHA 一致;
- 验证镜像失败时
github-hexo不被修改。
16.4 阶段四:存量接管
- 建立
adoption-map.yml(附录 A 的 38 篇 + 45 篇同名); - 逐批接管,每批后校验线上 URL、日期与渲染无变化;
- 处理 6 处
created冲突与manual-diverged文章。
16.5 阶段五:可靠性与运营
- 补齐构建验证、
build-meta.json、并发控制、构建后工作区校验; - 决策并落实 11.4 静态产物历史方案;
- 演练协议错误、凭证失效、镜像失败、构建失败与回滚;
- 建立分类变更、协议升级与密钥轮换流程。
17. 验收标准
17.1 功能验收
- 只有显式
publish: true且不是 wiki frontmatter 的文档进入博客; 0X0F_知识沙盒/下 wiki 目录的文章(实体信息/概念专题/对比分析/查询存档/计划方案)在任何情况下都不出现在发布树中;fileOverrides能把同一源目录的文章路由到不同分类(如原始资料/网络文章→ 软件测试 / 金融投资);pathDefaults能按最长前缀正确兜底;同一路径挂在两个分类下时整批失败;- 分类对账能补齐缺失项、修正路径,且不主动删除未声明的既有分类;
- 文章删除、
publish: false、移动与改分类后旧页面正确下线; - 目标重名、分类非法、资源缺失、敏感信息与协议不兼容会阻止整批发布;
- 同步无差异时,
cnb-hexo、github-hexo、github-pages均不产生提交。
17.2 存量与 URL 验收
- 83 篇存量文章的
abbrlink全部不变; - 「博客有、知识库无」的文章数为 0(当前实测为 0,见 7.2;每次同步须重新计算并写入报告);
- 38 篇「博客文件名 ≠ 知识库文件名」的文章同步后按 5.3 重命名,
abbrlink不变且 URL 可访问; - 38 篇改过标题的文章,URL 不变;
- 已接管文章的渲染结果与原文一致(除预期的语法转换外);
- 6 处
created冲突按 7.4 R4 处理,线上发布日期不变。
17.3 可靠性验收
- 相同输入重复执行不产生新提交;
- 构建后工作区干净(无
abbrlink写回); - 定时与手动同步并发时不互相覆盖;
- 镜像失败不改变
github-hexo; - 构建失败不改变
github-pages; cnb-hexo与github-hexo的对应提交 SHA 一致;- 可在不修改知识库的情况下重放历史构建;
- 可快速回退线上到上一成功版本(依 11.4 决策)。
17.4 安全验收
- 公开静态产物中不含私有 Markdown、凭证、内部地址或本地绝对路径;
0X0E_Custom/内容零泄漏,敏感信息扫描可拦截明文口令;- 各凭证只具备单目标仓库最小权限(镜像凭据不可写
github-pages); - 同步任务对知识库只有只读访问;
- 所有自动生成目录禁止人工直接修改。
18. 待决策项
| # | 事项 | 建议默认值 | 决策影响 |
|---|---|---|---|
| 1 | 静态产物历史(11.4 甲/乙) | 甲(去掉 force_orphan,保留历史) |
决定回滚方式与静态仓库体积 |
| 2 | 新增分类是否启用:读书笔记 / 金融投资 |
全部启用 | 决定 0X0D_三味书屋、A500_金融投资 的内容能否上线;视频剪辑 分类已存在,只需确认 A300 那篇是否发布 |
| 3 | 其他内容 是否继续作为 manual 分类 |
保留为 manual | 决定 A400 那篇文章是否纳入自动同步 |
| 4 | wiki 文档命中 publish: true 时的行为 |
整批失败 | 也可选择 “ 忽略并告警 “,但会掩盖误标 |
| 5 | _ 前缀草稿约定(见 4.5) |
已决定:启用(硬规则) | 文件名以 _ 开头一律不发布、也不参与差异计算;需发布时先重命名去掉前缀再标 publish: true |
| 7 | abbrlink 由同步器写回知识库,还是仅写在 cnb-hexo 侧 |
仅 cnb-hexo 侧 |
写回知识库会改动源文件、触发额外提交;不写回则知识库无 URL 视图 |
| 8 | 每日同步时间 | 内容团队停止编辑之后 | 决定内容最长发布延迟 |
| 9 | 目标文件名是否允许显式 slug | 已决定:不需要 | 文件名与知识库文件名完全一致、不做改写(见 5.3),故不存在 slug 需求 |
| 10 | 8 篇失效图片文章(附录 B.4)的处置 | 先 quarantine,再逐篇修复 |
决定 原始资料/网络文章 何时能上线 |
| 11 | 1. / 2. 数字前缀文件名的目标命名 |
已决定:不剥离 | 目标文件名沿用知识库原名(见 5.3);知识库侧命名已由内容负责人整理 |
| 12 | cnb-hexo → github-hexo 镜像语义 |
强制镜像(mirror),非快进 | 强制镜像会覆盖 GitHub 侧任何直接提交;若保留人工在 GitHub 打补丁的能力,需改为快进并接受分叉风险 |
| 13 | 本地开发仓库的远端迁移方式 | origin 改指 cnb-hexo,GitHub 降为只读远端 |
决定日常提交路径;不改则本地 push 会与镜像冲突 |
19. 最终推荐
采用四仓库、单向数据流、消费者定时拉取、完整状态生成、无差异不推进、存量显式接管的架构。
OneNote定义发布策略(.blog-publish.yml)与publish标注;wiki frontmatter 文档永不发布;cnb-hexo读取协议、生成发布源码、执行语法转换与分类对账,并镜像到 GitHub;github-hexo执行唯一一次完整 Hexo 构建;github-pages只保存成功构建并验证后的静态产物。
核心契约:每个阶段只有一个权威来源;每次转换确定且可重跑;任一阶段失败都不污染下一阶段;任何线上 URL 与发布日期都不因自动化而改变;任一线上版本都能追溯到知识库提交。
附录 A:38 篇重命名映射表
博客现有文件(source/_posts/<分类>/<文件名>) ↔ 知识库源文件。接管时以此表登记 adoption-map.yml。
| # | 博客分类 | 博客现有文件名 | 知识库源文件(0X0A_知识管理/ 下) |
|---|---|---|---|
| 1 | 人工智能 | AI 系列:人工智能的前世今生 & 必知基础原理.md | A200_ 人工智能/A220_AI 科普/AI 系列 -1:人工智能的前世今生 & 必知基础原理.md |
| 2 | 人工智能 | AI 系列:揭秘 LLM 大语言模型.md | A200_ 人工智能/A220_AI 科普/AI 系列 -2:揭秘 LLM 大语言模型.md |
| 3 | 人工智能 | AI 系列:搜索 - 从迷宫求解看人工智能关键技术.md | A200_ 人工智能/A220_AI 科普/AI 系列 -3:搜索 - 从迷宫求解看人工智能关键技术.md |
| 4 | 人工智能 | AI 系列:计算机视觉 - 给机器装上眼睛.md | A200_ 人工智能/A220_AI 科普/AI 系列 -4:计算机视觉 - 给机器装上眼睛.md |
| 5 | 其他内容 | 信息系项目管理师教程 - 第 4 版.md | A400_ 学习工坊/A410_ 信息系统项目管理师/1.信息系统项目管理师教程 - 第 4 版.md |
| 6 | 编程语言 | 如何快速运行 Golang 程序.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-01:如何快速运行 Golang 程序.md |
| 7 | 编程语言 | Golang 基础知识 & 数据类型详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-02:基础知识 & 数据类型详解.md |
| 8 | 编程语言 | Golang 条件循环 & 集合类型详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-03:条件循环 & 集合类型详解.md |
| 9 | 编程语言 | Golang 函数详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-04:函数详解.md |
| 10 | 编程语言 | Golang 闭包 & defer 详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-05:闭包 & defer 详解.md |
| 11 | 编程语言 | Golang 错误处理详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-06:错误处理详解.md |
| 12 | 编程语言 | Golang 指针 & 结构体详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-07:指针 & 结构体详解.md |
| 13 | 编程语言 | Golang 接口 & 类型断言详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-08:接口 & 类型断言详解.md |
| 14 | 编程语言 | Golang 反射详解 reflect.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-09:reflect 反射详解.md |
| 15 | 编程语言 | Golang 协程的执行过程.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-10:协程的执行过程.md |
| 16 | 编程语言 | Golang 内存回收 GC 详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-11:内存回收 GC 详解.md |
| 17 | 编程语言 | Golang Mutex 详解.md | A100_ 技术体系/A130_ 软件开发/A133_Golang 系列/Golang-12:Mutex 详解.md |
| 18 | 编程语言 | Python 介绍及环境搭建.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-01:介绍及环境搭建.md |
| 19 | 编程语言 | Python 编码规范.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-02:编码规范.md |
| 20 | 编程语言 | Python 基础内容.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-03:基础内容.md |
| 21 | 编程语言 | Python 数据结构.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-04:数据结构.md |
| 22 | 编程语言 | Python 控制结构 & 函数.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-05:控制结构 & 函数.md |
| 23 | 编程语言 | Python 文件操作.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-06:文件操作.md |
| 24 | 编程语言 | Python 并发编程.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-07:并发编程.md |
| 25 | 编程语言 | Python 网络编程.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-08:网络编程.md |
| 26 | 编程语言 | Python 包及模块.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-09:包及模块.md |
| 27 | 编程语言 | Python 异常类型 & 正则表达式.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-10:异常类型 & 正则表达式.md |
| 28 | 编程语言 | Python 常见设计模式.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-11:常见设计模式.md |
| 29 | 编程语言 | Python 面向对象.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-12:面向对象.md |
| 30 | 编程语言 | Python 学习路线.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-13:学习路线.md |
| 31 | 编程语言 | Python3 in one pic.md | A100_ 技术体系/A130_ 软件开发/A134_Python 系列/Python-14:Python3 in one pic.md |
| 32 | 软件测试 | Pytest 测试框架三部曲 - 上.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(一).md |
| 33 | 软件测试 | Pytest 测试框架三部曲 - 中.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(二).md |
| 34 | 软件测试 | Pytest 测试框架三部曲 - 下.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Pytest 测试框架三部曲(三).md |
| 35 | 软件测试 | Selenium 框架三部曲 - 上.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Selenium 框架三部曲(一).md |
| 36 | 软件测试 | Selenium 框架三部曲 - 中.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Selenium 框架三部曲(二).md |
| 37 | 软件测试 | Unittest 单元测试框架 - 上.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Unittest 单元测试框架(一).md |
| 38 | 软件测试 | Unittest 单元测试框架 - 下.md | A100_ 技术体系/A120_ 软件测试/A124_ 自动化测试/Unittest 单元测试框架(二).md |
序号 32–38 的 “ 上/中/下 “ 必须按系列顺序对应 “ 一/二/三 “,匹配算法需按序号而非字符串。
其余 45 篇与知识库文件同名,可直接按文件名建立映射。
附录 B:现状实测数据
B.1 知识库规模
| 目录 | Markdown 数 | 说明 |
|---|---|---|
0X0A_知识管理 |
104 | 可发布候选(通用 frontmatter) |
0X0F_知识沙盒 |
218 | 其中 原始资料 15 篇可发布;其余 203 篇为 wiki 页面或控制文件(SCHEMA.md/index.md/log.md) |
0X0D_三味书屋 |
3 | 可发布候选 |
0X0E_Custom |
19 | 禁止发布(含明文口令) |
0X00/0X0B/0X0C |
7 | 仪表盘与待办,排除 |
.agents/.trash/.dashboard-backup |
20 | 仓库元数据,排除 |
| 合计 | 385 |
0X0A_知识管理 内部:A100_技术体系 89(A110 操作系统 5、A120 软件测试 36、A130 软件开发 29、A140 开发工具 16、A150 博客搭建 3)、A200_人工智能 8、A300_创意工坊 1、A400_学习工坊 1、A500_金融投资 5、A600_时光余热 0。
B.2 博客规模
83 篇,8 个分类:编程语言 29、软件测试 23、开发工具 13、人工智能 8、操作系统 5、博客搭建 3、其他内容 1、视频剪辑 1。
B.3 Obsidian 语法与资源实测
| 指标 | 0X0A_知识管理 |
0X0F_知识沙盒 |
0X0D_三味书屋 |
|---|---|---|---|
| 文件数 | 104 | 218 | 3 |
| 远程图片引用 | 957 | 2 | 0 |
| 本地图片引用 | 7 | 106 | 0 |
[[wikilink]] |
5 | 855 | 0 |
==高亮== |
285 | 114 | 0 |
| Obsidian callout | 4 | 0 | 1 |
关键配置(.obsidian/app.json,已随 Git 跟踪):attachmentFolderPath: "RESOURCES/images/wiki-images"、newLinkFormat: "shortest"、useMarkdownLinks: true。
B.4 已知内容缺陷:网络文章图片失效
0X0F_知识沙盒/原始资料/网络文章/ 下 8 篇顶层文章的本地图片引用全部无法按路径解析(其 images/ 目录在 Git 历史中从未存在):
| 文件 | 失效引用数 |
|---|---|
| 腾讯 WorkBuddy 实践:如何把 Agent 做成可用产品.md | 30 |
| 大模型测试用例生成与优化.md | 4 |
| 提示词工程在智能测试中的实践 - 设计方法.md | 4 |
| 提示词工程在智能测试中的实践 - 优化技巧.md | 4 |
| AI 大模型与智能测试融合 - 基础认知.md | 4 |
| AI 大模型与智能测试融合 - 评估体系.md | 4 |
| GameDevBench 智能体评测.md | 4 |
| 合计 | 54 |
补充事实:img_030.png 等在全库不存在;img_001.jpeg/img_002.png/img_003.png/img_004.png 仅存在于 原始资料/资源素材/。另有 1 篇(你能做到训练有素的收割吗.md)使用裸文件名引用 资源素材/ 中的 3 张图,可按 5.4 的全局附件规则解析。output/ 下的 2 篇与独立目录形式的 1 篇图片全部可正常解析。
这批文件即 4.6
quarantine清单的来源;不隔离则整批同步在第一天即失败。
B.5 仓库状态
知识库 OneNote:
- 远程
https://cnb.cool/<组织>/OneNote,分支main; - 无 CI 配置(不存在
.cnb.yml或.github/); .blog-publish.yml存在但为 0 字节且未纳入 Git 跟踪;.gitignore已忽略.trae、.trash、.dashboard-backup、.workbuddy、.sync_temp_dir;- 工作区存在未提交改动(
AGENTS.md、SCHEMA.md、log.md、frontmatter-规范.md、金融模板.md); - 存在外部自动同步工具产生的提交(
Auto Sync: <时间> | <主机名>),印证 “ 高频提交但不应每次发布 “ 的前提。
发布源码(本地 <博客源码目录>):
origin当前指向git@github.com:<GitHub 账号>/hexo-butterfly-next.git,尚未配置cnb-hexo远端;- 仓库内不存在
.cnb.yml; - 本地实测已存在
patches/、pnpm-workspace.yaml、scripts/tag-colors.js、tools/patch-hexo-date.py、source/glass/与.github/workflows/pages.yaml。
B.6 实施后实测(2026-10)
| 指标 | 数值 |
|---|---|
| 可发布文章 | 100 篇(候选 119 / _ 草稿 4 / 未发布 15 / quarantine 0) |
| 分类分布 | 软件测试 38、编程语言 27、开发工具 14、人工智能 9、操作系统 5、博客搭建 3、金融投资 2、三味书屋 1、视频剪辑 1 |
| 远程图片引用 | 939 处,全部原样保留(不下载、不重写) |
| 本地图片引用 | 0 处(失效引用已全部删除) |
| callout 转换 | 4 处 |
[[双链]] 命中 / 保留原文 |
0 / 2(后者为 shell 条件表达式) |
%%注释%% 删除 |
0 处 |
| 附件行剥离 | 0 处 |
| 同步器测试用例 | 77 个,全部通过 |
| 幂等 | 连续两次运行产物 sha256 完全一致 |
| 构建 | pnpm install --frozen-lockfile + hexo generate 通过,生成 270 个文件 |
| abbrlink | 100/100 由插件写入,readback 全部回填;二次运行 0 差异 |
产物位置
| 产物 | 路径 |
|---|---|
| 同步器 | cnb-hexo/tools/sync/(sync.js、readback.js、lib/*.js) |
| 测试套件 | cnb-hexo/tools/sync/test/run.js |
| Manifest | cnb-hexo/tools/sync/manifest.json(随每次同步提交) |
| 流水线 | cnb-hexo/.cnb.yml |
| 发布树 | cnb-hexo/source/_posts/、cnb-hexo/source/assets/images/_sync/ |
参考资料
- 腾讯云 CNB 流水线配置说明
- cnb-hexo 仓库
- GitHub Actions 工作流语法
- GitHub Pages 自定义工作流
- 仓库内规范:
AGENTS.md、0X0F_知识沙盒/SCHEMA.md - 博客仓库规范:
hexo-butterfly-next/AGENTS.md、hexo-butterfly-next/_config.yml、hexo-butterfly-next/.github/workflows/pages.yaml