From 2922bb2e1073e4808d33e5a5b1b376c5ac26fdaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 20 Oct 2026 16:17:38 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E6=8C=89=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E5=A4=8D=E8=A7=84=E6=A0=BC=E4=BA=94?= =?UTF-8?q?=E9=A1=B9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 媒体缺失两级处置(悬空引用=构建失败;文件缺失=门禁🔴) - 发布=条目级状态指针更新,修订不可变性不受影响 - 路由表补 /products/ /news/ 主列表页;门禁查 redirect 目标存在性 - 作废引用清理(V1.0→V2.0/V3.0 §2.1);scripts 补 publish.ts - 采纳建议:sitemap 改手动文件输入(零服务端抓取)、保留设备名检查、 报告持久化 artifacts/jobs/、悬空外键重映射 Co-Authored-By: Claude Code --- ...6-10-20-siteforge-vertical-slice-design.md | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/superpowers/specs/2026-10-20-siteforge-vertical-slice-design.md b/docs/superpowers/specs/2026-10-20-siteforge-vertical-slice-design.md index b3c04a3..d95532e 100644 --- a/docs/superpowers/specs/2026-10-20-siteforge-vertical-slice-design.md +++ b/docs/superpowers/specs/2026-10-20-siteforge-vertical-slice-design.md @@ -2,7 +2,7 @@ > 日期:2026-10-20|状态:待评审 > 上位文档:`SiteForge_完整项目计划方案_V3.0.md`(执行基线) -> 姊妹文档:`cms现状与SiteForge差距分析_V1.0.md`(D:\project\cms 的教训清单) +> 姊妹文档:`cms现状与SiteForge差距分析_V2.0.md`(D:\project\cms 的现状基线与教训清单;V1.0 差距分析已作废) > 本文决策已经用户逐项确认(建设顺序、切片范围、数据、路线、九项修正)。 --- @@ -61,9 +61,9 @@ WXR 导入 → 统一内容模型 → URL 注册表 → 极简主题 → 静态 cms-one/ apps/api/ # NestJS:sites/content/routes/media/import/build/audit 模块 packages/contracts/ # UniversalRow、内容 DTO、路由 DTO(冻结+契约测试) - packages/adapter-wordpress/ # WXR 解析器 + WP REST 客户端(REST 通道切片内可选) + packages/adapter-wordpress/ # WXR 解析器(WP REST 客户端为后续阶段实现,本切片不含) themes/pilot-minimal/ # 极简 Astro 主题 - scripts/ # CLI:import.ts / build.ts / gate.ts / preview.ts + scripts/ # CLI:import.ts / publish.ts / build.ts / gate.ts / preview.ts storage/wxr/ storage/media/ # 试点数据副本(git 忽略) infra/docker-compose.yml # 仅 PostgreSQL tests/fixtures/ # WXR/媒体夹具 @@ -83,6 +83,7 @@ cms-one/ |---|---|---| | 内容页 permalink(含 `.html`/`.php` 后缀、任意目录深度) | **产物按原路径原样输出**(路径级保真,非仅 slug) | html | | 首页 `/` | 首页 | html | +| 主列表页 `/products/`、`/news/`(含其 `/page/N/` 分页) | 构建固定生成的主列表页路由(站点配置声明其路径) | html | | `product-category/{slug}`(产品分类) | 生成分类列表页,路径原样 | html | | `category/{slug}`(文章分类) | 生成文章分类列表页 | html | | 列表分页 `/page/N/`(含分类下分页) | 列表页分页输出 | html | @@ -99,13 +100,13 @@ cms-one/ ### 5.3 URL 对照报告(门禁输入) -老 URL 全集 = WXR 可推导 URL(内容 permalink ∪ 术语归档 ∪ 分页推导)∪ 老站 sitemap.xml(若可拉取;被挑战页拦截则记说明)。三色口径与 cms 一致:🟢 产物存在同路径页面|🔵 route_registry 有 redirect|🔴 无去处(必须为 0)。 +老 URL 全集 = WXR 可推导 URL(内容 permalink ∪ 术语归档 ∪ 分页推导)∪ 手动放置的老站 sitemap.xml 文件(`storage/sitemap/`,随 WXR 一起复制;未提供则记说明)。**本切片不做任何服务端抓取(SSRF 面为零)**。三色口径与 cms 一致:🟢 产物存在同路径页面|🔵 route_registry 有 redirect|🔴 无去处(必须为 0)。 ### 5.4 媒体旧路径保持 - `media_assets.source_url` 存旧完整 URL,`storage_path` 指向本地副本文件,`public_path` = 旧 URL 路径部分(`/wp-content/uploads/...`)。 - 构建时媒体复制到产物的 `public_path` 位置;正文 ``、封面、相册引用全部重写为根相对 `public_path`。 -- 未匹配到本地副本的媒体:列入导入报告(引用它的页面数、URL 清单),不阻塞导入;若被已发布页面引用则门禁降级为 🔴 由用户决定补图或改链。 +- **媒体缺失分两级处置**(消除歧义):① 内容引用的媒体 URL **不在 media_assets 注册表中**(悬空引用)→ **构建失败**(属输入不完整,fail-fast);② 已注册但**本地文件缺失** → 导入报告列出(引用页面数、URL 清单),不阻塞导入,门禁标 🔴,由用户决定补图或改链。 - 登记时校验文件魔数与声明 MIME 一致,不一致入报告;**SVG 一律隔离**(不复制进产物、预览不可达),报告中单列清单。 ## 6. 管道命令与数据流 @@ -118,7 +119,7 @@ cms-one/ → locale 取站点配置;未知 locale 记错误并跳过该条(禁止静默归 en——cms 实际踩坑) → 每条计算 normalized checksum: 与最新 content_revision.checksum 相同 → skip(不产生新修订) - 不同或不存在 → 写入新 content_revision(kind=draft,不可变,source=import) + 不同或不存在 → 写入新 content_revision(不可变,source=import;发布状态不写在修订上,见 6.2) ※ 导入不发布:发布是独立显式动作(见 6.2,V2.0 P0-6) → 媒体登记:按 WXR attachment 的 source_url 与本地副本文件名匹配; 全角/编码损坏文件名按规范化通配匹配(cms 踩坑),匹配结果写报告 @@ -132,10 +133,10 @@ cms-one/ ### 6.2 `scripts/publish.ts`(显式发布) -导入只产生 draft;发布是独立、显式、可审计的动作(V2.0 P0-6:禁止"迁移即发布"): +导入只产生未发布内容;发布是独立、显式、可审计的动作(V2.0 P0-6:禁止"迁移即发布"): -- 按批次/类型/条目批量将 draft 修订置为 published(更新 entry_locales.published_revision_id 指针)。 -- 每次发布动作写 audit_logs(actor、对象、前后状态、时间)。 +- **发布 = 更新条目级状态,不动修订**:content_entries.status → published,entry_locales.published_revision_id 指向当前修订。content_revisions 永不 UPDATE——不可变约束不因发布破坏(消除"修订上改 kind"的歧义)。 +- 按批次/类型/条目批量执行;每次发布动作写 audit_logs(actor、对象、前后状态、时间)。 - 构建只消费 published 修订集合——与 cms「导入后逐条自动发布」的行为刻意相反。 ### 6.3 `scripts/build.ts`(构建) @@ -162,10 +163,12 @@ Astro 构建(themes/pilot-minimal): | 内部死链 | 产物内 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 不代表无环) | +| 大小写冲突 | 产物相对路径 lowercase 后无重复,且不含 Windows 保留设备名(CON/PRN/AUX/NUL/COM1…——V3.0 §6.5) | +| 重定向图 | redirect 关系无环;**每个 redirect_location 必须解析为已存在的 html/asset 路由**;关键路径多跳(≥3 跳)告警(V2.0 §5.3:🔴=0 不代表无环、目标存在) | | 兜底数据回归 | 输入损坏时构建必须失败(负向测试) | +导入与门禁报告均持久化至 `artifacts/jobs//`(JSON + HTML 双格式),§11.5 的"可复查"即指此目录。 + ### 6.5 `scripts/preview.ts`(预览) 自研 Node 静态服务器(不引入 nginx 依赖): @@ -183,8 +186,8 @@ Astro 构建(themes/pilot-minimal): | 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 字段;独立表 | +| content_revisions | **不可变**(插入式,禁 UPDATE);data jsonb、checksum、source=import、import_job_id(→ import_jobs);预留 `overridden_fields`(下阶段冲突队列用) | +| route_registry | §5.2 字段(其中来源指向 `source_import_job_id` → import_jobs);独立表 | | media_assets | (site_id, source_url) 唯一幂等;sha256、public_path(旧路径)、alt、尺寸 | | build_jobs / artifacts | 构建记录 + 产物目录 + 输入快照哈希 | | audit_logs | 追加式;actor(切片为 system/importer)、action、对象、前后值 | @@ -246,4 +249,4 @@ SP4' 部署器(rsync/SSH → OSS/CDN)+ 发布锁/回滚 SP5' 表单服务 + 备份监控 ``` -每阶段对照 `cms现状与SiteForge差距分析_V1.0.md` 的 17 项清单打钩。 +每阶段对照 **V3.0 §2.1 的 17 项必须交付能力**打钩(评级基线见 V2.0 §4)。