Files
cms-website/docs/superpowers/specs/2026-10-20-siteforge-vertical-slice-design.md
T
浅念 d60123ba41 docs: SiteForge 规划文档基线 + 纵向切片设计规格 V1
- V3.0 总体方案、主题与板块设计、实施计划
- cms 现状差距分析 V2.0(V1.0 已标注作废)与复用评估 V1.0
- 纵向切片设计规格(路线 C:全新架构 + 白名单资产移植)
- .gitignore 先行(storage/artifacts/env 永不入库)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 16:10:34 +08:00

17 KiB
Raw Blame History

SiteForge 最小纵向切片 · 设计文档

日期:2026-10-20|状态:待评审 上位文档:SiteForge_完整项目计划方案_V3.0.md(执行基线) 姊妹文档:cms现状与SiteForge差距分析_V1.0.md(D:\project\cms 的教训清单) 本文决策已经用户逐项确认(建设顺序、切片范围、数据、路线、九项修正)。


1. 背景与定位

SiteForge 是多站点静态迁移与发布平台(V3.0 定义 17 项必须交付能力)。本切片是整个平台的第一个里程碑:用真实试点数据(hesupipe.com 的 WordPress WXR 导出 + 图片包副本)打通最险的核心链路一次:

WXR 导入 → 统一内容模型 → URL 注册表 → 极简主题 → 静态构建 → 门禁 → 本地预览

三条前提约束:

  1. 所有代码是正式资产:数据模型、契约、目录结构按 V3.0 设计,后续能力在此之上加厚,不推倒重写。
  2. 不改动 D:\project\cms,但白名单资产可移植(路线 C,2026-10-20 确认):cms 冻结为只读源;试点数据以只读复制方式进入本仓库 storage/。可移植资产(白名单):WXR 解析规则与测试夹具(重写到流式 XML 解析器之上)、Astro 页面模板(作为 pilot-minimal 主题起点——其产品 Tabs/相册/表格刚对齐过 hesupipe 老站)、门禁脚本逻辑、docker-compose/CI 配置。不继承、按 V3.0 重写:认证、BuildService、release/rollback 脚本、MigrationService 编排(迁移即发布)、settings JSONB 路由、Lead 服务、后台业务页面。
  3. 完整性由清单驱动:每完成一个阶段对照 17 项能力清单打钩,全绿才是项目完成;本切片覆盖其中 4 项的骨架(WordPress 导入、统一内容与修订、URL 注册表、Astro 静态构建)。

文档权威关系:SiteForge_完整项目计划方案_V3.0.md 是唯一产品与架构基线;cms现状与SiteForge差距分析_V2.0.md 是 cms 现状与改造优先级依据(V1.0 差距分析已作废);SiteForge_现有CMS项目复用评估_V1.0.md 是复用边界参考。

2. 目标与非目标

2.1 目标(本切片交付)

# 交付 验收口径
G1 WXR 全量导入管道(幂等,导入不发布) hesupipe 全量导入成功(全部为 draft);显式 publish 后转 published;重复导入第二次产生 0 条新修订
G2 统一内容模型 产品/新闻/页面落库,不可变修订 + externalRef 幂等键 + checksum
G3 URL 注册表与路由策略 路径级保真 + 分类/归档/标签处置;URL 对照报告 🔴=0
G4 极简主题静态构建 全站页面 + sitemap.xml + robots.txt + 媒体旧路径输出
G5 门禁 死链=0、sitemap 一致、每页 title/canonical/唯一 H1、产物大小写冲突=0
G6 本地预览服务器 按 route_registry 真实响应(html/301/404),noindex 头

2.2 非目标(明确不做,后续阶段做)

后台管理 UI、部署器(rsync/OSS)、差量同步与冲突队列(本切片只预留 override 字段)、SEO 三套数据 diff(P0–P3)、爬虫/Playwright、多语言(en 单语)、表单服务、MFA/认证体系(本地脚本无 HTTP 暴露)、Redis/BullMQ(任务先进程内)。

3. 技术选型与决策记录

决策 选择 理由(ADR 摘要,正式 ADR 实施时落 docs/adr/)
D1 ORM Drizzle + PostgreSQL 16 cms 实战验证;迁移即 SQL 可审查;SQL 控制力强(ADR-001)
D2 任务执行 进程内 JobRunner,接口抽象 切片无长任务并发需求;接口按 job 语义设计,BullMQ 后置替换不死代码(ADR-002)
D3 媒体存储 本地磁盘 storage/,产物保持旧路径 cms 教训:MinIO 容器起了没人用;对象存储后置(ADR-003)
D4 URL 策略 路径级保真优先,301 兜底 V3.0 §6/§7 要求;详见 §5(ADR-004)
D5 服务形态 NestJS 模块化单体 + CLI 脚本复用服务层 架构与 V3.0 一致;切片不启动 HTTP 监听,脚本以 standalone app context 调用模块服务
D6 契约范围 packages/contracts 只含 UniversalRow + 内容/路由 DTO YAGNI:无队列即无事件目录
D7 WXR 解析 流式 XML 解析器(saxes 类)+ 资源限额 cms 的正则解析在 33MB 级 WXR、CDATA/实体/命名空间边界有风险(V2.0 §5.1);限额:文件大小、节点数、处理时限;恶意 XML 防护(ADR-005)
D8 媒体安全底线 登记时魔数校验 + SVG 隔离 不信扩展名与声明 MIME;SVG 默认不进产物、单列报告(V2.0 P0-5)(ADR-006)

运行环境:Node 22 LTS、pnpm workspace、TypeScript strict、vitest。开发机为 Windows(大小写陷阱见 §7 G5)。

4. 仓库结构(切片实际创建的部分)

cms-one/
  apps/api/                    # NestJS:sites/content/routes/media/import/build/audit 模块
  packages/contracts/          # UniversalRow、内容 DTO、路由 DTO(冻结+契约测试)
  packages/adapter-wordpress/  # WXR 解析器 + WP REST 客户端(REST 通道切片内可选)
  themes/pilot-minimal/        # 极简 Astro 主题
  scripts/                     # CLI:import.ts / build.ts / gate.ts / preview.ts
  storage/wxr/ storage/media/  # 试点数据副本(git 忽略)
  infra/docker-compose.yml     # 仅 PostgreSQL
  tests/fixtures/              # WXR/媒体夹具
  docs/
    plans/                     # 根目录现有 4 份规划文档移入(实施第一步)
    adr/  specs/  superpowers/
  .gitignore                   # 仓库第一个 commit:node_modules/dist/artifacts/storage//.env/coverage

不预建空包(cms README 纪律);V3.0 §22.2 的其余包在对应阶段创建。

5. URL 与路由策略(本切片的核心正确性)

5.1 目标路径规则(按优先级)

旧 URL 形态 处置 route_kind
内容页 permalink(含 .html/.php 后缀、任意目录深度) 产物按原路径原样输出(路径级保真,非仅 slug) html
首页 / 首页 html
product-category/{slug}(产品分类) 生成分类列表页,路径原样 html
category/{slug}(文章分类) 生成文章分类列表页 html
列表分页 /page/N/(含分类下分页) 列表页分页输出 html
tag/{slug}、product_tag/{slug}、日期归档、作者页 301 → 对应主列表页(/products/ 或 /news/) redirect
附件/媒体 wp-content/uploads/... 媒体文件按原路径进产物 asset
无法保真的个别内容路径 301 → 新路径 redirect
WP ?p=123 短链 记录为已知限制,对照报告单列(不算 🔴,见 §8) —

5.2 route_registry(独立表,禁止塞 settings JSONB——cms 教训)

切片字段子集(V3.0 §6.4):site_id / original_path / normalized_lookup_key / route_kind / target_content_id / target_file / redirect_location / indexable / source_status / source_snapshot_id。

规范化键规则(仅作查重与对照,原始路径永不改写):percent-decode → Unicode NFC → 小写 → 去尾部斜杠。查重命中仅大小写/尾斜杠差异时报告冲突,不静默合并(V3.0 §6.3)。

5.3 URL 对照报告(门禁输入)

老 URL 全集 = WXR 可推导 URL(内容 permalink ∪ 术语归档 ∪ 分页推导)∪ 老站 sitemap.xml(若可拉取;被挑战页拦截则记说明)。三色口径与 cms 一致:🟢 产物存在同路径页面|🔵 route_registry 有 redirect|🔴 无去处(必须为 0)。

5.4 媒体旧路径保持

  • media_assets.source_url 存旧完整 URL,storage_path 指向本地副本文件,public_path = 旧 URL 路径部分(/wp-content/uploads/...)。
  • 构建时媒体复制到产物的 public_path 位置;正文 <img>、封面、相册引用全部重写为根相对 public_path。
  • 未匹配到本地副本的媒体:列入导入报告(引用它的页面数、URL 清单),不阻塞导入;若被已发布页面引用则门禁降级为 🔴 由用户决定补图或改链。
  • 登记时校验文件魔数与声明 MIME 一致,不一致入报告;SVG 一律隔离(不复制进产物、预览不可达),报告中单列清单。

6. 管道命令与数据流

6.1 scripts/import.ts(导入)

读 storage/wxr/*.xml → adapter-wordpress 解析为 UniversalRow[]
→ 类型映射(post→news_article;product CPT→product;page→basic_page)
→ locale 取站点配置;未知 locale 记错误并跳过该条(禁止静默归 en——cms 实际踩坑)
→ 每条计算 normalized checksum:
    与最新 content_revision.checksum 相同 → skip(不产生新修订)
    不同或不存在 → 写入新 content_revision(kind=draft,不可变,source=import)
    ※ 导入不发布:发布是独立显式动作(见 6.2,V2.0 P0-6)
→ 媒体登记:按 WXR attachment 的 source_url 与本地副本文件名匹配;
    全角/编码损坏文件名按规范化通配匹配(cms 踩坑),匹配结果写报告
→ 生成/更新 route_registry(§5.1 规则)与 301 规则
→ 未知 WP 字段保留进 data.extras(不丢弃)
→ 输出导入报告:条目数对照、skip/update/create 计数、媒体匹配率、
    URL 冲突清单、行级错误定位

幂等键:(site_id, source_connection_id, source_type, source_id, locale),与 externalRef(旧 permalink)双保险。

6.2 scripts/publish.ts(显式发布)

导入只产生 draft;发布是独立、显式、可审计的动作(V2.0 P0-6:禁止"迁移即发布"):

  • 按批次/类型/条目批量将 draft 修订置为 published(更新 entry_locales.published_revision_id 指针)。
  • 每次发布动作写 audit_logs(actor、对象、前后状态、时间)。
  • 构建只消费 published 修订集合——与 cms「导入后逐条自动发布」的行为刻意相反。

6.3 scripts/build.ts(构建)

物化 build_input_snapshot:
    内容(published 修订集合)+ 路由清单 + 媒体清单 + 站点配置
    → 冻结为 artifacts/<buildId>/input/ 下带哈希的 JSON 文件
Astro 构建(themes/pilot-minimal):
    只读输入快照文件,禁止实时查库,禁止任何 seed/演示兜底数据
    (输入不完整 → 构建立即失败;此行为写回归测试)
    SITE_URL 取站点配置的生产域名(hesupipe.com)→ canonical/og/sitemap 正确
产物:HTML 页面 + sitemap.xml(只含 route_kind=html 且 indexable)+ robots.txt
    + 媒体(旧路径)+ _redirects / nginx-redirects.conf(从 route_registry 编译,验证路由模型)
    + build-manifest.json(输入快照哈希、页面数、checksums)
落 artifacts/<buildId>/,build_jobs 表记录

6.4 scripts/gate.ts(门禁)

检查 通过条件
URL 对照(§5.3) 🔴 = 0
内部死链 产物内 href/src/srcset 指向的内部目标全部存在(html/asset 命中 route_registry)
sitemap 一致 sitemap 条目 ⊆ 实际页面;indexable 页面 ⊆ sitemap
页面 SEO 基线 每页有 title、canonical;H1 至多 1 个
大小写冲突 产物相对路径 lowercase 后无重复(Windows 开发盘不敏感、Linux 部署敏感——V3.0 §6.5)
重定向图 route_registry 的 redirect 关系无环;关键路径多跳(≥3 跳)告警(V2.0 §5.3:🔴=0 不代表无环)
兜底数据回归 输入损坏时构建必须失败(负向测试)

6.5 scripts/preview.ts(预览)

自研 Node 静态服务器(不引入 nginx 依赖):

  • 按 route_registry 响应:html → 伺服产物文件;redirect → 301 + Location;无匹配 → 产物 404 页。
  • 所有响应加 X-Robots-Tag: noindex;禁用目录列表。
  • 用途:在本地验证"真实 URL 行为"(V3.0 §7.4 精神),并支撑 §8 的人工抽检。

7. 数据模型(切片子集,V3.0 §9 对齐)

表 关键字段与约束
sites site_id PK、name、default_locale、production_domain
source_connections 类型=wxr、文件路径 + sha256、adapter 版本
import_jobs / source_records 批次状态机;源记录存原始条目位置 + checksum + 解析状态
content_entries (site_id, type_key, external_ref) 唯一;status: draft/published/archived
entry_locales (site_id, locale, slug) 唯一;current/published_revision_id 指针
content_revisions 不可变(插入式,禁 UPDATE);data jsonb、checksum、source=import、import_batch_id;预留 overridden_fields(下阶段冲突队列用)
route_registry §5.2 字段;独立表
media_assets (site_id, source_url) 唯一幂等;sha256、public_path(旧路径)、alt、尺寸
build_jobs / artifacts 构建记录 + 产物目录 + 输入快照哈希
audit_logs 追加式;actor(切片为 system/importer)、action、对象、前后值

多语言位:模型带 locale,切片只写 en。

8. 错误处理

  • 行级容错:单条解析/映射失败记录行号与原因后继续;整批结构错误(文件损坏、站点不存在)才中断。
  • 未知字段:一律进 data.extras 保留,报告中汇总清单。
  • 构建 fail-fast:输入快照不完整(内容缺失、路由空、媒体引用悬空)立即失败,无任何降级/兜底路径。
  • 已知限制:?p= 短链、WXR 外的老站 URL(日志/GSC 来源)不在本切片对照范围,报告中单列"未覆盖来源"节——不算 🔴,进入下阶段 SEO diff。
  • 重跑语义:无变化 skip / 有变化新修订 / 人工改动保护(override 标记)下阶段实现,本切片在修订上预留字段。

9. 测试策略(vitest)

层 内容
单元:WXR 解析 夹具必须覆盖:_thumbnail_id、_product_image_gallery、wppop_sections(PHP serialize 的 Tab 数据)、Yoast _yoast_wpseo_title/metadesc、全角/编码损坏文件名、分页与术语 URL 推导、.html 后缀 permalink(全部来自 cms 实际踩坑清单)
单元:路由规则 §5.1 每行规则一个用例;大小写/尾斜杠冲突报告
单元:解析器限额 超大/畸形 XML(节点数、大小、时限超限)→ 明确失败不崩溃;CDATA、实体、命名空间边界夹具
单元:媒体安全 魔数与扩展名不符 → 报告;SVG → 进隔离清单
单元:重定向图 环 → 🔴;多跳 → 告警
集成:导入 真实 WXR 小切片夹具 → 落库 → 断言修订/checksum/幂等(第二次 0 新修订)
集成:构建 快照物化 → Astro 构建 → 产物结构断言(sitemap/robots/媒体旧路径/_redirects)
负向 输入损坏 → 构建失败(兜底禁令);route_registry 缺目标 → 门禁 🔴
契约 UniversalRow/DTO 字段冻结(packages/contracts)
验收 §2.1 G1–G6 全部可执行复验(真实 hesupipe 数据 + 抽检 20 个 URL:10 内容页 + 5 分类页 + 3 redirect + 2 404)

10. 实施注意(从 cms 教训直接落地的防线)

  1. 动工第零步:手动备份 cms 数据(pg_dump + storage/ 拷贝至第二存储,约半小时——V2.0 阶段 A 要求)。
  2. .gitignore 是仓库第一个 commit(storage/、artifacts/、node_modules、.env)。
  3. 构建输入快照先于一切(seed 兜底事故的根治)。
  4. redirects 走独立表(不进 settings JSONB)。
  5. WXR 原件 + sha256 留档 source_connections。
  6. 未知字段不丢弃(extras)。
  7. 门禁 🔴=0 才算过。
  8. Windows 长路径:副本拷贝与构建脚本启用长路径处理(全角文件名目录)。

11. 验收标准(切片完成的定义)

口径采用 V2.0 §2.2 成熟度分级:本切片验收 = L3(真实数据本地验证);L4(生产部署、HTTP 验收、回滚演练)属后续部署阶段。

  1. import 两连跑:第一次全量入库为 draft(条目数与 WXR 基数对照一致),publish 后转 published,第二次 import 0 新修订。
  2. build 产出全站产物,gate 七项全绿。
  3. preview 启动后按 §9 抽检 20 URL 行为正确(含 301 跳转与 404)。
  4. pnpm test 全绿(含负向与契约)。
  5. 导入报告与 URL 对照报告可读、可复查(含媒体匹配率与未覆盖来源说明)。

12. 切片之后的路(指引后续子项目,不在本切片内)

SP0' 后台 UI(Art Design Pro + 认证/MFA)
SP1' 差量同步与冲突队列(override 字段启用)
SP2' SEO 三套数据 diff(P0–P3)+ 爬虫扩充 URL 全集
SP3' 主题/板块协议(Block Schema/模板约束/第二主题)
SP4' 部署器(rsync/SSH → OSS/CDN)+ 发布锁/回滚
SP5' 表单服务 + 备份监控

每阶段对照 cms现状与SiteForge差距分析_V1.0.md 的 17 项清单打钩。