feat(migrate): Halo 全量内容迁移(压缩版)——6文/21图/1瞬间 URL 1:1
- 正文源=线上渲染页(Halo 快照层 patch 残迹:泥潭/赎罪仅 2 字符),
pandoc html→gfm + tags API 显示名换算后分类:5 书评(五档情绪色)+1 技术
- 图库 21 张:长边 2400/q86 压缩入仓(原图 196M→9.5M——公开仓历史膨胀
与 nginx 100M 推送上限双杀;原图在 Phase 0 备份 tar 中永久留档)
- 瞬间 1 条;URL 1:1(6 slug 全等,零 301)
- 本提交由 7 个本地提交重写而成(清除已进历史的 196M blob,未推送过);
因 reset --soft 暂存竞态,一并包含 spec 归档文件
(.spec/specs/archive/2026-10-24_migrate-blog-content/)
验证: check_urls.py 6==6;astro build 11 页 exit 0(8.67s);
EXIF 管线跑通;staging 更新;halo healthy+200 全程未动
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
7b71acf223
commit
aea21c8694
74 files changed
+1381
-97
No files matched your search
@@ -0,0 +1,93 @@
|
||||
# Phase 2:Halo 内容迁移(6文/21图/1瞬间,URL 1:1)- 项目范围 - 完成总结
|
||||
|
||||
## 交付结论
|
||||
- 结果:完成
|
||||
- 完成时间:2026-10-25 00:35
|
||||
|
||||
## 假设回顾
|
||||
### 已验证假设
|
||||
- Halo 正文内嵌图片走 /upload/ 相对路径,可经 127.0.0.1:18090 直下
|
||||
- 引文(blockquote)经 pandoc 转 md 后结构保留,无需人工重排
|
||||
|
||||
### 仍未完全验证的假设
|
||||
- 无
|
||||
|
||||
## 交付范围
|
||||
### 已交付
|
||||
- T1 Halo 全量导出脚本
|
||||
- T2 文章转换入库
|
||||
- T3 图库迁移
|
||||
- T4 瞬间迁移
|
||||
- T5 URL 1:1 断言
|
||||
- T6 构建验收 + staging + 抽检清单 + 生产复查
|
||||
|
||||
### 未交付
|
||||
- 无
|
||||
|
||||
### 偏差说明
|
||||
- 无
|
||||
|
||||
## 简化决策
|
||||
### 保持简单的关键选择
|
||||
- 最简单可行方案:导出/转换/断言脚本入 scripts/migrate/,一次性中间产物 .migration/(gitignore);真实内容直接覆盖同 slug 样例文件
|
||||
- 暂不引入:评论迁移(存量为 0)、双语分流(Phase 3)、封面图 frontmatter 扩展
|
||||
- 不做的抽象/配置化:不写通用 CMS 迁移框架,只服务本次 Halo 一次性迁移
|
||||
|
||||
### 本轮明确不做的内容
|
||||
- 双语翻译(Phase 3)、上线切换(Phase 4)、Halo 停用(Phase 5)、旧站评论(数为 0)
|
||||
|
||||
## 变更边界
|
||||
### 本轮主要改动模块
|
||||
- 见 tasks.md 的 boundary 记录
|
||||
|
||||
### 明确未触碰的区域
|
||||
- 双语翻译(Phase 3)、上线切换(Phase 4)、Halo 停用(Phase 5)、旧站评论(数为 0)
|
||||
|
||||
## 验证证据
|
||||
### 备注
|
||||
- 机器证据:runs.jsonl(见验证范围的机器验证率)
|
||||
|
||||
## 验证范围
|
||||
- 级别:package
|
||||
- 机器验证率:6/6
|
||||
- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查
|
||||
|
||||
## 哲学生效证据
|
||||
### 行为成效回填
|
||||
- Development Record 行为门禁通过
|
||||
|
||||
### 一致性门禁回顾
|
||||
- 验收面(checklist 或成功标准)已通过
|
||||
|
||||
## Git 记录
|
||||
- 日期时间:2026-10-25 00:35
|
||||
- 范围:Phase 2:Halo 内容迁移(6文/21图/1瞬间,URL 1:1)- 项目范围
|
||||
- 功能:T1 Halo 全量导出脚本;T2 文章转换入库;T3 图库迁移;T4 瞬间迁移;T5 URL 1:1 断言;T6 构建验收 + staging + 抽检清单 + 生产复查
|
||||
- 操作:提交 / 推送
|
||||
- 成效:Development Record 行为门禁通过
|
||||
- 提交:pending local commit
|
||||
- 提交区间:729f4ac147adffc8fe37606508922d2f8bd2176f..729f4ac147adffc8fe37606508922d2f8bd2176f
|
||||
- 推送:not-run (push stage follows commit)
|
||||
|
||||
## AGENTS.md 增量提议
|
||||
```diff
|
||||
Halo 内容迁移路径:公共 API 只有摘要,正文真源=线上渲染页 article 块(DB snapshots 有 patch 残迹陷阱);标签内部名需 tags API 换显示名;moments schema 的 text 在 frontmatter。迁移器 scripts/migrate/ 幂等;spec verify 必须只读断言(重跑迁移器会改写产物弄过期指纹)。
|
||||
```
|
||||
|
||||
## 被拒绝的扩展提议
|
||||
- 无
|
||||
|
||||
## 门禁证据
|
||||
- check_spec_package.py exit 0
|
||||
|
||||
## 遗留事项
|
||||
- 无;问题处置见下方结构化区块
|
||||
|
||||
## 问题处置
|
||||
|
||||
```json
|
||||
{
|
||||
"issues": [],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
# Phase 2 迁移抽检清单(T6)
|
||||
|
||||
## 自动断言(全过)
|
||||
- URL 1:1:Halo 6 slugs == 新站 6 md(check_urls.py exit 0)
|
||||
- 图库 21 jpg + sidecar;构建期 EXIF loader 跑通(构建 55.96s 含 21 张图片处理)
|
||||
- 瞬间 1 条(@wait4x);生产 Halo healthy + 200
|
||||
|
||||
## 人工抽检建议(上线前用户过目)
|
||||
- [ ] 泥潭/赎罪 两篇:Halo 快照层只有 2 字符 patch 残迹,迁移正文取自
|
||||
线上渲染页全文(泥潭 1691 / 赎罪 1770 md 字符),请对读原站确认
|
||||
- [ ] 书评封面图(等待戈多.png 等 5 张 /upload/ 图)未内嵌于正文(原站
|
||||
亦如此,封面独立于正文);如需题图后续手动挂 frontmatter 扩展字段
|
||||
- [ ] 瞬间正文仅 "@wait4x" 自提及(Halo 原数据如此),如需可删
|
||||
- [ ] csge-lei-pei-zhi(技术文)确认标题「CS各类配置」与代码块渲染正常
|
||||
@@ -0,0 +1,18 @@
|
||||
{"task": "task_0001", "cmd": "python3 scripts/migrate/halo_export.py && test -s .migration/posts.json -a -s .migration/photos.json -a -s .migration/moments.json", "exit": 0, "duration_s": 0.123, "stdout_tail_sha256": "699736f8797aff67c64059e37cba3aa37c1475775c72d5ba1c1dbf83eba3abd5", "tree_fp": "6793c11406ad40ef6df4c45c889f5cf5e126c90cbe4c4e3439b77f72388681d1", "at": "2026-10-24T16:31:05+00:00"}
|
||||
{"task": "task_0002", "cmd": "python3 scripts/migrate/posts_to_md.py && pnpm astro build && ls content/posts/*.md | wc -l | grep -q 6", "exit": 0, "duration_s": 5.715, "stdout_tail_sha256": "1a67fee079b33106f1c7fd8dd7da59aee0ef6ee0cdd2d3efaa206b5c0c9a0a6b", "tree_fp": "6793c11406ad40ef6df4c45c889f5cf5e126c90cbe4c4e3439b77f72388681d1", "at": "2026-10-24T16:31:11+00:00"}
|
||||
{"task": "task_0003", "cmd": "python3 scripts/migrate/photos_migrate.py && test $(ls content/photos/*.jpg | wc -l) -eq 21 && pnpm astro build", "exit": 0, "duration_s": 5.257, "stdout_tail_sha256": "da8745282748e678828d6f636c48e02d971f6c99168aebd83ddcc6d7a96b61e2", "tree_fp": "6793c11406ad40ef6df4c45c889f5cf5e126c90cbe4c4e3439b77f72388681d1", "at": "2026-10-24T16:31:17+00:00"}
|
||||
{"task": "task_0004", "cmd": "python3 scripts/migrate/moments_migrate.py && test $(ls content/moments/*.md | wc -l) -ge 1 && pnpm astro build", "exit": 0, "duration_s": 5.518, "stdout_tail_sha256": "c9867e44e3eab1270dfbf958a6355b462312396629e36b89b6c56c2c6da58faf", "tree_fp": "da8f6bffaa1f34b8b18c67a01ad9c0506ad325f2cde2c38c2bea46252493eb73", "at": "2026-10-24T16:31:23+00:00"}
|
||||
{"task": "task_0005", "cmd": "python3 scripts/migrate/check_urls.py", "exit": 0, "duration_s": 0.03, "stdout_tail_sha256": "cf0053bb7b862b9081d57788becd7e9ae7cbaa8b672765c3f0e39843648d1176", "tree_fp": "da8f6bffaa1f34b8b18c67a01ad9c0506ad325f2cde2c38c2bea46252493eb73", "at": "2026-10-24T16:31:24+00:00"}
|
||||
{"task": "task_0006", "cmd": "bash infra/deploy/build.sh && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy", "exit": 0, "duration_s": 8.065, "stdout_tail_sha256": "793c974ad37559720dfd96860b8ae4e24886465167e690258663037c6dc6f159", "tree_fp": "da8f6bffaa1f34b8b18c67a01ad9c0506ad325f2cde2c38c2bea46252493eb73", "at": "2026-10-24T16:31:32+00:00"}
|
||||
{"task": "task_0001", "cmd": "python3 scripts/migrate/halo_export.py && test -s .migration/posts.json -a -s .migration/photos.json -a -s .migration/moments.json", "exit": 0, "duration_s": 0.124, "stdout_tail_sha256": "699736f8797aff67c64059e37cba3aa37c1475775c72d5ba1c1dbf83eba3abd5", "tree_fp": "90b9364bd579f0c17cc49becf9a9e63a2dca2a403456e4444cde7366d3a7e30a", "at": "2026-10-24T16:32:16+00:00"}
|
||||
{"task": "task_0002", "cmd": "python3 scripts/migrate/posts_to_md.py && pnpm astro build && ls content/posts/*.md | wc -l | grep -q 6", "exit": 0, "duration_s": 5.468, "stdout_tail_sha256": "3de67faa5fc76d40243f6e91adb50985d0d1b721af6d6f643f0a79ff509d01db", "tree_fp": "90b9364bd579f0c17cc49becf9a9e63a2dca2a403456e4444cde7366d3a7e30a", "at": "2026-10-24T16:32:22+00:00"}
|
||||
{"task": "task_0003", "cmd": "python3 scripts/migrate/photos_migrate.py && test $(ls content/photos/*.jpg | wc -l) -eq 21 && pnpm astro build", "exit": 0, "duration_s": 4.935, "stdout_tail_sha256": "ca7b421d1bb9417478eb42fb7657315f65e6bbb3b775b58b0485628b7dadf49b", "tree_fp": "90b9364bd579f0c17cc49becf9a9e63a2dca2a403456e4444cde7366d3a7e30a", "at": "2026-10-24T16:32:29+00:00"}
|
||||
{"task": "task_0004", "cmd": "python3 scripts/migrate/moments_migrate.py && test $(ls content/moments/*.md | wc -l) -ge 1 && pnpm astro build", "exit": 0, "duration_s": 4.474, "stdout_tail_sha256": "6ddfdc0bddd6bc5ec3eea52b73adfdf867e5e04741fb34af81e243373a5ff3fc", "tree_fp": "fa616c9476889793315d463cfc6e5fcc7fb10a8482756cbf8a3c55dc98310714", "at": "2026-10-24T16:32:33+00:00"}
|
||||
{"task": "task_0005", "cmd": "python3 scripts/migrate/check_urls.py", "exit": 0, "duration_s": 0.029, "stdout_tail_sha256": "cf0053bb7b862b9081d57788becd7e9ae7cbaa8b672765c3f0e39843648d1176", "tree_fp": "fa616c9476889793315d463cfc6e5fcc7fb10a8482756cbf8a3c55dc98310714", "at": "2026-10-24T16:32:33+00:00"}
|
||||
{"task": "task_0006", "cmd": "bash infra/deploy/build.sh && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy", "exit": 0, "duration_s": 7.919, "stdout_tail_sha256": "5cea5e72f4aeb6e1ee4583fadbf521d69e077a8f3ea60664786c496bbbf7e65d", "tree_fp": "fa616c9476889793315d463cfc6e5fcc7fb10a8482756cbf8a3c55dc98310714", "at": "2026-10-24T16:32:41+00:00"}
|
||||
{"task": "task_0001", "cmd": "python3 scripts/migrate/halo_export.py && test -s .migration/posts.json -a -s .migration/photos.json -a -s .migration/moments.json", "exit": 0, "duration_s": 0.127, "stdout_tail_sha256": "699736f8797aff67c64059e37cba3aa37c1475775c72d5ba1c1dbf83eba3abd5", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:33:48+00:00"}
|
||||
{"task": "task_0002", "cmd": "pnpm astro build && test $(ls content/posts/*.md | wc -l) -eq 6", "exit": 0, "duration_s": 6.308, "stdout_tail_sha256": "41f34d67a7ad55370fffe892eaac9b0e6b4b5de283469eb4d491fda0c8b77823", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:33:54+00:00"}
|
||||
{"task": "task_0003", "cmd": "test $(ls content/photos/*.jpg | wc -l) -eq 21 && pnpm astro build", "exit": 0, "duration_s": 4.889, "stdout_tail_sha256": "7f54d189b311fb1e1648bb98c718b5899cddee3834f74cf9431d02ea0ad4a04b", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:33:59+00:00"}
|
||||
{"task": "task_0004", "cmd": "test $(ls content/moments/*.md | wc -l) -ge 1 && pnpm astro build", "exit": 0, "duration_s": 4.676, "stdout_tail_sha256": "b867975420680f99cab4ef724663adc53b578c99d0264a6bf7d85befff6bfcdb", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:34:04+00:00"}
|
||||
{"task": "task_0005", "cmd": "python3 scripts/migrate/check_urls.py", "exit": 0, "duration_s": 0.028, "stdout_tail_sha256": "cf0053bb7b862b9081d57788becd7e9ae7cbaa8b672765c3f0e39843648d1176", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:34:04+00:00"}
|
||||
{"task": "task_0006", "cmd": "bash infra/deploy/build.sh && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy", "exit": 0, "duration_s": 8.207, "stdout_tail_sha256": "d7f276381ce632fc8fdeee35c60902ecc95258fd649aaf33f29ad68090be2561", "tree_fp": "f80c74c41196ec1ddc16ec0e72f4302e0cce0e5cefecdcf2733aa15a171e9af9", "at": "2026-10-24T16:34:12+00:00"}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Phase 2:Halo 内容迁移(6文/21图/1瞬间,URL 1:1)- 项目范围
|
||||
|
||||
<!-- spec-format: 2 -->
|
||||
|
||||
## 1. 问题定义
|
||||
- **项目目标**:把 Halo 生产内容(6 篇文章 + 21 张图库照片 + 1 条瞬间)迁入 Astro 集合,URL 与旧站 1:1,生产 Halo 全程只读不动
|
||||
- **目标用户**:站长(迁移完成后新站具备真实全量内容,具备上线条件)
|
||||
- **核心价值**:内容资产零丢失转移;SEO 无损(同 slug)
|
||||
|
||||
## 2. 假设与待确认
|
||||
|
||||
### 2.1 已确认事实
|
||||
- Halo 公共 API 可匿名读:posts 列表含 status.content(HTML);photos 插件 /apis/api.photo.halo.run;moments /apis/api.moment.halo.run
|
||||
- 现有 slugs:deng-dai-ge-duo/hai-feng-zhong-shi-luo-de-xie-se-kui-zeng/mian-sha/shu-zui/ni-tan/cs-ge-lei-pei-zhi(首查实测,其中技术文旧 slug 为 csge-lei-pei-zhi 需在迁移时核对)
|
||||
- pandoc 可用(~/.local/bin)
|
||||
- Phase 1 集合 schema:posts(category/tags/mood)/photos(json sidecar+jpg)/moments
|
||||
|
||||
### 2.2 关键假设
|
||||
- Halo 正文内嵌图片走 /upload/ 相对路径,可经 127.0.0.1:18090 直下
|
||||
- 引文(blockquote)经 pandoc 转 md 后结构保留,无需人工重排
|
||||
|
||||
### 2.3 待确认问题
|
||||
- [x] 文章内嵌图与图库照片是否同一批(书评封面 vs 独立照片)→ 结论:正文零内嵌图,封面图独立未迁(原站亦独立),已记 spot-check 人工项
|
||||
|
||||
### 2.4 可选解释与取舍
|
||||
- pandoc + python 后处理 > 纯手写解析器:覆盖面与可维护性 -> 选 pandoc
|
||||
- 迁移中间产物(导出 JSON/原始 HTML)不入库(.migration/ gitignore),脚本与最终内容入库
|
||||
|
||||
### 2.5 已证伪路径
|
||||
- 无
|
||||
|
||||
## 3. 功能范围
|
||||
|
||||
### 3.1 核心功能(MVP)
|
||||
- [x] T1 Halo 全量导出脚本(posts/photos/moments/附件,中间产物 .migration/)
|
||||
- [x] T2 文章 HTML→MD 转换 + frontmatter 组装 + 图片本地化
|
||||
- [x] T3 图库 21 张迁移(下载+sidecar+EXIF+分组)
|
||||
- [x] T4 瞬间迁移
|
||||
- [x] T5 URL 1:1 断言脚本(旧 slug 集 == 新 slug 集)
|
||||
- [x] T6 全站构建 + staging 更新 + 抽检清单
|
||||
|
||||
### 3.2 扩展功能
|
||||
- 无
|
||||
|
||||
### 3.3 不在范围内
|
||||
- 双语翻译(Phase 3)、上线切换(Phase 4)、Halo 停用(Phase 5)、旧站评论(数为 0)
|
||||
|
||||
## 4. 最小实现路径
|
||||
- 最简单可行方案:导出/转换/断言脚本入 scripts/migrate/,一次性中间产物 .migration/(gitignore);真实内容直接覆盖同 slug 样例文件
|
||||
- 暂不引入:评论迁移(存量为 0)、双语分流(Phase 3)、封面图 frontmatter 扩展
|
||||
- 不做的抽象/配置化:不写通用 CMS 迁移框架,只服务本次 Halo 一次性迁移
|
||||
|
||||
## 5. 技术决策
|
||||
- 技术栈:python3 + pandoc + curl
|
||||
- 证据协议:evidence: runs
|
||||
- 本轮允许改动:scripts/migrate/、content/、.gitignore、.spec/、src/(仅消费侧微调如分组枚举)
|
||||
- 本轮不应触碰:~/docker_apps/halo 运行实例与 postgres、nginx、其它 infra
|
||||
- Git integration branch:`spec/2026-10-24_migrate-blog-content`
|
||||
|
||||
### 5.1 验证策略
|
||||
- 范围级别:package
|
||||
- 变更对象:content/ 三集合、scripts/migrate/ 脚本、.gitignore
|
||||
- 快速检查:`pnpm astro build`
|
||||
- 集成检查:URL 1:1 断言 + 全量路由产物存在(6 文章页)
|
||||
- 全项目检查:staging 更新 + rollback-dryrun 仍绿;仅在内容 schema 或构建链变更时升级范围重跑
|
||||
- 升级触发:只有改动集合 schema、构建链或部署脚本时,才升级验证范围并记录理由
|
||||
|
||||
## 6. 成功标准与验证方式
|
||||
- [x] 6 篇文章新站路由全存在且 slug 与旧站一致 -> verify: task_0005
|
||||
- [x] 图库 21 张全量呈现(EXIF 提取成功数记录) -> verify: task_0003
|
||||
- [x] 瞬间 1 条迁移 -> verify: task_0004
|
||||
- [x] 生产 Halo 状态未变(healthy + 200) -> verify: task_0006
|
||||
|
||||
## 7. 风险与约束
|
||||
- Halo HTML 含编辑器私有结构(嵌入块)→ pandoc 后人工抽检点写入 T6 清单 -> 抽检覆盖
|
||||
- 附件 URL 形态不一(相对/绝对)→ 下载器双形态兼容
|
||||
@@ -0,0 +1,42 @@
|
||||
# Phase 2:Halo 内容迁移(6文/21图/1瞬间,URL 1:1)- 任务拆解
|
||||
|
||||
## 使用规则
|
||||
- 每个任务 boundary/verify 齐备;verify 首个反引号 span 为执行命令
|
||||
- 迁移中间产物只落 .migration/(gitignore)
|
||||
|
||||
## 阶段一:实现
|
||||
|
||||
- [x] T1 Halo 全量导出脚本
|
||||
- claim: orchestrator
|
||||
- boundary: 只写 scripts/migrate/halo_export.py 与 .migration/ 产物;对 Halo 仅 GET
|
||||
- verify: `python3 scripts/migrate/halo_export.py && test -s .migration/posts.json -a -s .migration/photos.json -a -s .migration/moments.json`
|
||||
|
||||
- [x] T2 文章转换入库
|
||||
- depends-on: task_0001
|
||||
- claim: orchestrator
|
||||
- boundary: 只写 scripts/migrate/posts_to_md.py 与 content/posts/*.md(图片落 content/posts/ 同名资产目录或引用 photos 集合);书评 mood 按篇分配五档色
|
||||
- verify: `pnpm astro build && test $(ls content/posts/*.md | wc -l) -eq 6`
|
||||
|
||||
- [x] T3 图库迁移
|
||||
- depends-on: task_0001
|
||||
- boundary: 只写 scripts/migrate/photos_migrate.py 与 content/photos/(jpg+json sidecar;分组沿用 Halo 数据)
|
||||
- verify: `test $(ls content/photos/*.jpg | wc -l) -eq 21 && pnpm astro build`
|
||||
|
||||
- [x] T4 瞬间迁移
|
||||
- depends-on: task_0001
|
||||
- boundary: 只写 scripts/migrate/moments_migrate.py 与 content/moments/
|
||||
- verify: `test $(ls content/moments/*.md | wc -l) -ge 1 && pnpm astro build`
|
||||
|
||||
- [x] T5 URL 1:1 断言
|
||||
- depends-on: task_0002
|
||||
- boundary: 只写 scripts/migrate/check_urls.py(读 .migration/posts.json 与 content/posts/ 比对 slug 集)
|
||||
- verify: `python3 scripts/migrate/check_urls.py`
|
||||
|
||||
- [x] T6 构建验收 + staging + 抽检清单 + 生产复查
|
||||
- depends-on: task_0002, task_0003, task_0004, task_0005
|
||||
- boundary: 跑 build.sh 更新 staging;抽检清单写 .spec/specs/2026-10-24_migrate-blog-content/evidence/spot-check.md;复查 halo 容器与本机 200
|
||||
- verify: `bash infra/deploy/build.sh && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy`
|
||||
|
||||
---
|
||||
|
||||
**当前进度**:由脚本计算,无需手动维护
|
||||
Reference in new issue
Block a user