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,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Halo 全量导出(Phase 2 T1):posts/photos/moments → .migration/*.json
|
||||
|
||||
对生产仅 GET;正文优先取列表 status.content,缺则回落 head-content 端点。
|
||||
产物为迁移中间件,不入库(.gitignore .migration/)。
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
BASE = "http://127.0.0.1:18090"
|
||||
OUT = Path(".migration")
|
||||
OUT.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def get(path: str) -> object:
|
||||
with urllib.request.urlopen(BASE + path, timeout=30) as r:
|
||||
return json.load(r)
|
||||
|
||||
|
||||
def export_list(api: str, size: int = 100) -> list[dict]:
|
||||
"""翻页取全量 items。"""
|
||||
items, page = [], 1
|
||||
while True:
|
||||
data = get(f"{api}?page={page}&size={size}")
|
||||
batch = data.get("items", [])
|
||||
items.extend(batch)
|
||||
if page * size >= data.get("total", 0) or not batch:
|
||||
return items
|
||||
page += 1
|
||||
|
||||
|
||||
def main() -> int:
|
||||
posts = export_list("/apis/api.content.halo.run/v1alpha1/posts")
|
||||
# 正文兜底:head-content 拿完整 HTML(status.content 可能是截断摘要)
|
||||
for p in posts:
|
||||
name = p["metadata"]["name"]
|
||||
content = (p.get("status") or {}).get("content")
|
||||
if not content:
|
||||
try:
|
||||
head = get(f"/apis/api.content.halo.run/v1alpha1/posts/{name}/head-content")
|
||||
p["_fullContent"] = head.get("content", "")
|
||||
except Exception as e: # noqa: BLE001 - 单篇失败不阻断全量导出
|
||||
p["_fullContent"] = ""
|
||||
print(f" warn: {name} head-content 失败: {e}", file=sys.stderr)
|
||||
photos = export_list("/apis/api.photo.halo.run/v1alpha1/photos")
|
||||
try:
|
||||
groups = get("/apis/api.photo.halo.run/v1alpha1/groups")
|
||||
except Exception: # noqa: BLE001 - 分组端点缺失时空兜底
|
||||
groups = {"items": []}
|
||||
moments = export_list("/apis/api.moment.halo.run/v1alpha1/moments")
|
||||
|
||||
(OUT / "posts.json").write_text(json.dumps(posts, ensure_ascii=False, indent=1))
|
||||
(OUT / "photos.json").write_text(json.dumps(photos, ensure_ascii=False, indent=1))
|
||||
(OUT / "photo_groups.json").write_text(json.dumps(groups, ensure_ascii=False, indent=1))
|
||||
(OUT / "moments.json").write_text(json.dumps(moments, ensure_ascii=False, indent=1))
|
||||
|
||||
full = sum(1 for p in posts if p.get("_fullContent") or (p.get("status") or {}).get("content"))
|
||||
print(f"exported: posts={len(posts)}(含正文 {full})photos={len(photos)} groups={len(groups.get('items', []))} moments={len(moments)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user