# M3 双语内容模型到公开展示闭环教程大纲

## 一、文章定位

- 目标读者：已经理解 monorepo 基础工程和最小鉴权闭环，准备进入“真实简历内容流”的开发者
- 文章目标：带读者理解如何从“内容模型”一路走到“草稿 / 发布态”与“公开展示端”
- 文章形式：可拆为 3~4 篇教程，也可作为 M3 里程碑总览文章

## 二、M3 为什么重要

- `M1` 解决的是工程骨架
- `M2` 解决的是登录和角色边界
- `M3` 才真正把“简历内容”作为产品核心引入系统
- 如果没有 `M3`，后续 AI、导出、部署都只能是空壳

## 三、本里程碑解决了什么

- 建立标准版双语简历的数据结构
- 建立草稿 / 已发布状态与最小发布闭环
- 建立公开展示端，并只读取已发布内容
- 补上最小 `zh/en` 语言切换
- 补上最小 `light / dark` 展示钩子

## 四、里程碑拆解过程

### 1. issue-10 双语内容模型

- 为什么先做内容模型，而不是先接数据库
- 为什么采用字段级 `zh/en` 双语结构
- 为什么当前只维护一份标准版简历，不做 JD 多版本
- 关键文件：
  - `apps/server/src/modules/resume/domain/standard-resume.ts`
  - `apps/server/src/modules/resume/domain/standard-resume.spec.ts`

### 2. issue-11 草稿与发布态

- 为什么后台编辑态和公开展示态必须拆开
- 为什么先用内存态服务跑通发布闭环，再进入数据库
- 为什么 `viewer` 在这里依旧只读
- 关键文件：
  - `apps/server/src/modules/resume/resume-publication.service.ts`
  - `apps/server/src/modules/resume/resume.controller.ts`
  - `apps/server/test/resume-publication.e2e-spec.ts`

### 3. issue-23 主题模式与展示配置边界

- 为什么展示型项目要尽早约束 `light / dark`
- 为什么主题能力应该属于展示层，而不是内容层
- 为什么多模板这轮不做实现，但必须预留扩展钩子
- 关键文件：
  - `docs/10-架构设计/01-个人简历-monorepo-重构总方案-v1学习版.md`
  - `docs/20-研发流程/01-GitHub-标准开发流程.md`

### 4. issue-12 web 公开读取已发布版本

- 为什么 `apps/web` 现在就要落地最小公开壳
- 为什么公开站只读已发布内容，绝不直接读草稿
- 为什么语言切换与主题切换可以先做最小版本
- 关键文件：
  - `apps/web/app/page.tsx`
  - `apps/web/components/published-resume-shell.tsx`
  - `apps/web/lib/published-resume-api.ts`

## 五、关键设计决策

### 1. 只维护一份标准双语简历

- 避免 M3 过早进入 JD 派生、多版本、商业版复杂度
- 先把标准版内容流讲清楚，比一次性追求“全”更适合教程

### 2. 先不接数据库持久化

- 先通过领域模型和发布态把行为边界锁定
- 等读者理解“为什么这样分层”之后，再接数据库更自然

### 3. 内容模型与展示配置分离

- 简历内容是数据
- 主题、模板、布局是展示策略
- 两者混在一起，后续导出、AI、模板系统都会很痛苦

### 4. 公开站只读已发布内容

- 保证对外输出稳定
- 保证后台草稿不外泄
- 保证 AI、导出、SEO 后续都能围绕同一份“公开内容”展开

## 六、关键测试如何设计

### 内容模型测试

- 锁定模块边界
- 锁定双语字段结构
- 锁定非法数据形状报错

### 发布流测试

- 锁定草稿更新行为
- 锁定发布快照行为
- 锁定“草稿变化不影响当前已发布内容”

### 公开站测试

- 锁定公开 API 读取行为
- 锁定 `zh/en` 切换
- 锁定 `light / dark` 切换
- 锁定未发布时的空状态

## 七、关键联调路径

- 后台或接口更新草稿
- 管理员执行发布动作
- 公开端读取 `/resume/published`
- 页面按当前语言渲染内容
- 页面按当前主题切换展示变量

## 八、踩坑与取舍

- `NestJS` 装饰器元数据下的 `import type` 问题
- 服务端渲染场景下，API 地址不能只依赖浏览器端环境变量
- 教程型项目里，主题能力不能做太重，但也不能完全后置
- 如果这轮就引入数据库，会掩盖“内容流”和“发布态”本身的教学重点

## 九、与后续里程碑的衔接

- `M4`：基于已发布内容与上传文本，进入 AI 分析与缓存报告
- `M5`：基于已发布内容输出 Markdown / PDF 导出
- `M6`：把当前链路纳入 CI、测试和部署

## 十、文章拆分建议

### 文章 1：为什么简历项目要先定义双语内容模型

- 重点：字段级双语、标准版边界、为什么不先接数据库

### 文章 2：如何用最小草稿 / 发布态跑通内容闭环

- 重点：后台草稿、公开发布、只读边界、测试设计

### 文章 3：如何给公开简历站搭一个最小 Next.js 展示壳

- 重点：SSR 读取、已发布内容、语言切换、主题钩子

### 文章 4：为什么展示配置必须和内容模型分离

- 重点：`light / dark`、模板扩展、多主题与导出的关系

## 十一、可回链的开发日志

- `docs/30-开发日志/M3-issue-10-双语内容模型.md`
- `docs/30-开发日志/M3-issue-11-草稿与发布态.md`
- `docs/30-开发日志/M3-issue-12-web-公开读取已发布版本.md`
- `docs/30-开发日志/M3-issue-23-主题模式与展示配置边界.md`

## 十二、完成标准

- 能让读者清楚理解 M3 为什么这样拆
- 能让读者按 Issue 顺序复现完整内容闭环
- 能解释本轮没有做什么，以及为什么没做
- 能自然衔接到 M4 / M5，而不用推翻已有结构
