# content 工作排期

> 负责人：ai09
> 关联：[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/content_contract.md](../contracts/content_contract.md)、[objections/content_issue.md](../objections/content_issue.md)、[../../services/content/docs/02-architecture-design.md](../../../services/content/docs/02-architecture-design.md)
> 模式：全并行（各 AI 一口气完成 P2-P6 全部代码，最后统一集成测试）
> 当前分支：`feat-review-content-module-docs-WAIyMA`

---

## §1 总览

content 是内容资源中台服务（P4 阶段），承载 D4 内容资源限界上下文，提供 Textbook / Chapter / KnowledgePoint / Question 四个聚合的 CRUD 与知识图谱查询。

**关键交付**：
- gRPC 50054 + 4 Service（Textbook/Chapter/KnowledgeGraph/Question），按 [coord-final-decisions.md §3.3](../../coord-final-decisions.md) N1/N3/N5 仲裁，P4 首次实现即启用 gRPC + 补全 QuestionService/ChapterService proto
- MySQL 写模型 + Neo4j 知识图谱 + Outbox 事件驱动异步同步（禁止业务事务内同步双写 Neo4j）
- Kafka 发布 `edu.content.knowledge_point.events` / `edu.content.question.events`（聚合 topic 策略，待 ISSUE-002 仲裁）
- P5 引入 Elasticsearch 全文检索 + AI 出题入库（QuestionService.BatchCreateQuestions）
- P6+ 长远演进：教材版本管理 / 跨租户内容共享 / 个性化学习路径推荐

**关键路径位置**：批次 3（P4），依赖批次 2 core-edu 完成（实际可并行：content 不强依赖 core-edu）

---

## §2 全阶段甘特图（P4-P6）

```mermaid
gantt
    title ai09 content 全阶段排期（P4-P6）
    dateFormat YYYY-MM-DD
    axisFormat %m-%d

    section P4 基础设施
    P4.1 schema 迁移补字段 :crit, c4a, 2026-07-19, 2d
    P4.2 Outbox 表+Publisher worker :crit, c4b, after c4a, 3d
    P4.3 Kafka producer(idempotent+txn) :crit, c4c, after c4b, 2d
    P4.4 Neo4j Sync Worker(异步) :crit, c4d, after c4c, 2d
    P4.5 重构 kp.service 移除同步双写 :crit, c4e, after c4d, 1d

    section P4 gRPC 契约
    P4.6 content.proto 补 ChapterService/QuestionService :crit, c4f, 2026-07-19, 1d
    P4.7 gRPC controller 实现(4 Service) :crit, c4g, after c4f, 4d
    P4.8 buf generate + 类型校验 :c4h, after c4g, 1d

    section P4 横切与质量
    P4.9 /readyz 多依赖(DB/Neo4j/Kafka) :c4i, after c4e, 1d
    P4.10 ZodError GlobalErrorFilter 分支 :c4j, after c4i, 1d
    P4.11 DB 改 getDb()+ID 改 cuid2 :c4k, after c4j, 1d
    P4.12 Repository 抽象补齐 :c4l, after c4k, 2d
    P4.13 单元测试(Service/Repository)≥60% :c4m, after c4l, 3d
    P4.14 修正 README 与实现对齐 :c4n, after c4m, 1d

    section P5 ES+AI 集成
    P5.1 引入 @elastic/elasticsearch :crit, c5a, after c4m, 1d
    P5.2 ES mapping+ensureIndex :crit, c5b, after c5a, 1d
    P5.3 ES Sync Worker(消费事件同步索引) :crit, c5c, after c5b, 2d
    P5.4 GET /questions/search 检索 API :crit, c5d, after c5c, 2d
    P5.5 QuestionService gRPC 完善Publish/Search :c5e, after c5d, 1d
    P5.6 AI 出题 BatchCreateQuestions 联调 :c5f, after c5e, 2d
    P5.7 检索性能优化(<200ms) :c5g, after c5f, 2d
    P5.8 测试覆盖率≥80% :c5h, after c5g, 2d

    section P6+ 演进
    P6.1 Question 审核工作流状态机 :c6a, after c5h, 3d
    P6.2 知识图谱可视化 API :c6b, after c6a, 3d
    P6.3 教材版本管理 :c6c, after c6b, 2d
    P6.4 /readyz 硬化+监控告警完善 :c6d, after c6c, 2d
```

**预估总工期**：P4 约 21 天 + P5 约 13 天 + P6+ 约 10 天 = **44 天**（与 workline.md §1 批次 3+4 时间窗口一致）

---

## §3 详细任务

### 3.1 P4 阶段任务

#### P4.1 schema 迁移补字段

- **负责人**：ai09
- **依赖**：无（自身 schema 现状）
- **交付物**：
  - [textbooks.schema.ts](../../../services/content/src/textbooks/textbooks.schema.ts) textbooks 表补 `status` / `tenant_id` / `metadata` 字段
  - chapters 表补 `created_at` / `updated_at` / `status`（解决 [01-understanding.md](../../../services/content/docs/01-understanding.md) C7）
  - knowledge_points 表补 `difficulty` / `metadata` / `created_at` / `updated_at`（解决 ISSUE-009）
  - questions 表补 `status` / `source` / `created_by`（NULL 起步，解决 ISSUE-007）/ `metadata`
- **验收标准**：`pnpm typecheck` 通过；Drizzle 类型重新生成；迁移脚本可幂等执行

#### P4.2 Outbox 表 + Publisher worker

- **负责人**：ai09
- **依赖**：P4.1
- **交付物**：
  - 新建 `src/shared/outbox/outbox.schema.ts`（content_outbox_events 表，见 design doc §3.1.5）
  - 新建 `src/shared/outbox/outbox.publisher.ts`（轮询 PENDING 事件投递 Kafka，指数退避重试）
  - 新建 `src/shared/outbox/outbox.module.ts`
- **验收标准**：业务事务内写 questions + outbox 同事务提交；Publisher worker 独立轮询；retry_count 累加正确

#### P4.3 Kafka producer（idempotent + transactionalId）

- **负责人**：ai09
- **依赖**：P4.2
- **交付物**：
  - 新建 `src/shared/kafka/producer.ts`（kafkajs 客户端，idempotent=true，transactionalId=content-producer）
  - 新建 `src/shared/kafka/kafka.module.ts`
  - package.json 添加 kafkajs 依赖
- **验收标准**：producer 启动成功；transactionalId 唯一；幂等投递无重复

#### P4.4 Neo4j Sync Worker（异步同步）

- **负责人**：ai09
- **依赖**：P4.3
- **交付物**：
  - 新建 `src/shared/sync/neo4j-sync.worker.ts`（消费 content 自身 Outbox 事件，异步创建/更新 Neo4j 节点与关系）
  - 消费 `KnowledgePointCreated` / `KnowledgePointPrerequisiteAdded` 等事件
- **验收标准**：MySQL 写知识点后，Neo4j 节点最终一致出现（延迟 < 2s）；Neo4j 故障时事件不丢失，恢复后补齐

#### P4.5 重构 knowledge-points.service.ts 移除同步双写

- **负责人**：ai09
- **依赖**：P4.4
- **交付物**：
  - [knowledge-points.service.ts](../../../services/content/src/knowledge-points/knowledge-points.service.ts) 删除 `safeCreateNode` 同步写 Neo4j 逻辑
  - 改为发 Outbox 事件 `KnowledgePointCreated`
  - `addPrerequisite` 改为发 Outbox 事件 `KnowledgePointPrerequisiteAdded`
- **验收标准**：业务事务内不再直接写 Neo4j；Neo4j 写入全部走异步 Sync Worker（解决 ISSUE-010 + 01-understanding C9）

#### P4.6 content.proto 补 ChapterService / QuestionService

- **负责人**：ai09
- **依赖**：无（自身 proto 现状）
- **交付物**：
  - [content.proto](../../../packages/shared-proto/proto/content.proto) 补 ChapterService（CreateChapter/ListChapters/GetChapter/UpdateChapter/DeleteChapter）
  - 补 QuestionService（CreateQuestion/BatchCreateQuestions/GetQuestion/ListQuestions/UpdateQuestion/DeleteQuestion/PublishQuestion/SearchQuestions）
  - 补 TextbookService.UpdateTextbook / DeleteTextbook
  - 补全 message 定义（Chapter / Question / QuestionRequest 等）
  - 同步补 events.proto 的 KnowledgePointEvent / QuestionEvent / TextbookEvent / ChapterEvent（待 ISSUE-002 仲裁后定）
- **验收标准**：`buf lint` 通过；`buf breaking` 无破坏性变更（新增字段 OK）；contract.md 与 design doc §4.2 RPC 数对齐

#### P4.7 gRPC controller 实现（4 Service）

- **负责人**：ai09
- **依赖**：P4.6
- **交付物**：
  - 新建 `src/textbooks/textbooks.grpc.controller.ts`
  - 新建 `src/chapters/chapters.grpc.controller.ts`
  - 新建 `src/knowledge-points/knowledge-points.grpc.controller.ts`
  - 新建 `src/questions/questions.grpc.controller.ts`
  - main.ts 启用 gRPC server 50054
- **验收标准**：`grpcurl` 调用 4 Service 全部 RPC 返回正确；HealthService.Check 返回 SERVING（解决 N1）

#### P4.8 buf generate + 类型校验

- **负责人**：ai09
- **依赖**：P4.7
- **交付物**：`pnpm buf:generate` 生成 TS 类型；content 服务引用生成类型
- **验收标准**：`pnpm typecheck` 通过

#### P4.9 /readyz 多依赖检查

- **负责人**：ai09
- **依赖**：P4.5
- **交付物**：[health.controller.ts](../../../services/content/src/shared/health/health.controller.ts) 改造 /readyz，检查 DB / Neo4j / Kafka producer / Kafka consumer lag
- **验收标准**：返回 design doc §6.6 格式；Neo4j 不可用 → status=degraded；DB 不可用 → status=down（解决 N2 + 01-understanding C-section readyz 问题）

#### P4.10 ZodError GlobalErrorFilter 分支

- **负责人**：ai09
- **依赖**：无
- **交付物**：[global-error.filter.ts](../../../services/content/src/shared/errors/global-error.filter.ts) 增加 ZodError 识别分支，返回 400 + 字段级错误详情
- **验收标准**：Zod 校验失败返回 design doc §4.3 错误结构

#### P4.11 DB 改 getDb() + ID 改 cuid2

- **负责人**：ai09
- **依赖**：无
- **交付物**：
  - [database.ts](../../../services/content/src/config/database.ts) 改为 `getDb()` 函数式懒加载（对齐 classes 黄金模板）
  - service 层 `randomUUID()` 改为 `cuid2()`（package.json 添加 @paralleldrive/cuid2）
- **验收标准**：所有 service 使用 getDb()；所有 ID 生成用 cuid2

#### P4.12 Repository 抽象补齐

- **负责人**：ai09
- **依赖**：P4.11
- **交付物**：补齐 textbooks/questions 的 Repository 抽象（与 chapters/knowledge-points 一致）
- **验收标准**：Service 层不直接调用 Drizzle API，全部走 Repository

#### P4.13 单元测试 ≥ 60%

- **负责人**：ai09
- **依赖**：P4.12
- **交付物**：
  - 新建 `*.spec.ts` 覆盖 Service 层 + Repository 层
  - 重点覆盖 QuestionsService 题型校验 / KnowledgePointsService 前置依赖 / Outbox Publisher 重试逻辑
- **验收标准**：`pnpm test` 通过；覆盖率 ≥ 60%

#### P4.14 修正 README 与实现对齐

- **负责人**：ai09
- **依赖**：P4.13
- **交付物**：[README.md](../../../services/content/README.md) 修正 `TextbooksService.createKnowledgeGraph` 错误描述（实际在 KnowledgePointsService）；补齐 4 个领域模块说明
- **验收标准**：README 与源码完全一致（解决 01-understanding C3）

### 3.2 P5 阶段任务

#### P5.1 引入 @elastic/elasticsearch

- **负责人**：ai09
- **依赖**：P4 全部完成
- **交付物**：package.json 添加 @elastic/elasticsearch；新建 `src/config/elasticsearch.ts`
- **验收标准**：esClient 单例；ES_URL 未配置时 esClient=null 降级

#### P5.2 ES mapping + ensureIndex

- **负责人**：ai09
- **依赖**：P5.1
- **交付物**：按 design doc §3.3.1 实现 questions 索引 mapping；启动时 `ensureIndex` 幂等
- **验收标准**：索引创建成功；ik_max_word / ik_smart 分词器配置正确

#### P5.3 ES Sync Worker

- **负责人**：ai09
- **依赖**：P5.2
- **交付物**：新建 `src/shared/sync/es-sync.worker.ts`，消费 `QuestionCreated` / `QuestionUpdated` / `QuestionPublished` / `QuestionDeleted` 事件增量更新索引
- **验收标准**：MySQL 写题目后，ES 索引最终一致（延迟 < 2s）

#### P5.4 GET /questions/search 检索 API

- **负责人**：ai09
- **依赖**：P5.3
- **交付物**：[questions.controller.ts](../../../services/content/src/questions/questions.controller.ts) 增加 `@Get("search")` 端点；ES 查询支持 q / type / difficulty / knowledgePointId 过滤
- **验收标准**：检索延迟 < 200ms（P5 退出标准）；返回分页结构

#### P5.5 QuestionService gRPC 完善 Publish/Search

- **负责人**：ai09
- **依赖**：P5.4
- **交付物**：gRPC controller 补 PublishQuestion / SearchQuestions RPC 实现
- **验收标准**：与 contract.md §1.1 完全对齐（解决 ISSUE-004 部分）

#### P5.6 AI 出题 BatchCreateQuestions 联调

- **负责人**：ai09
- **依赖**：P5.5 + ai12 ai 服务就绪
- **交付物**：与 ai12 联调 BatchCreateQuestions RPC；服务账号权限校验
- **验收标准**：AI 服务调用成功入库；batch_size ≤ 100 限制生效

#### P5.7 检索性能优化

- **负责人**：ai09
- **依赖**：P5.6
- **交付物**：ES 查询 DSL 优化；缓存热点查询结果（Redis）
- **验收标准**：P95 延迟 < 200ms

#### P5.8 测试覆盖率 ≥ 80%

- **负责人**：ai09
- **依赖**：P5.7
- **交付物**：补集成测试（gRPC + ES + Neo4j 端到端）
- **验收标准**：覆盖率 ≥ 80%

### 3.3 P6+ 演进任务

#### P6.1 Question 审核工作流状态机

- **负责人**：ai09
- **依赖**：P5 完成
- **交付物**：Question.status 状态机完整实现（draft → pending_review → published/rejected → archived）；审核日志表
- **验收标准**：状态转换校验正确；非法转换返回 409

#### P6.2 知识图谱可视化 API

- **负责人**：ai09
- **依赖**：P6.1
- **交付物**：`GET /knowledge-graph/visualization` 返回 nodes/edges 结构（解决 01-understanding L3）
- **验收标准**：返回 D3.js / vis.js 可消费的图结构

#### P6.3 教材版本管理

- **负责人**：ai09
- **依赖**：P6.2
- **交付物**：Textbook.version 字段启用；版本切换不破坏题库引用
- **验收标准**：新旧版本教材并存；题库引用按版本隔离

#### P6.4 /readyz 硬化 + 监控告警完善

- **负责人**：ai09
- **依赖**：P6.3
- **交付物**：/readyz 探针列表完善；Prometheus 告警规则补齐（consumer lag / outbox pending / ES latency）
- **验收标准**：告警阈值合理；故障演练通过

---

## §4 依赖与就绪信号

### 4.1 我依赖的上游就绪标志

| 上游           | 就绪标志                                              | 必需性                 | mock 策略                                  |
| -------------- | ----------------------------------------------------- | ---------------------- | ------------------------------------------ |
| infra          | MySQL 8 / Neo4j 5 / Kafka 集群可用                    | 🔴 必需                | 本地 docker-compose                         |
| infra          | Redis 可用（P5+ 缓存用，env.ts 已预留 REDIS_URL）     | 🟢 P5+ 可选            | 未配置时跳过缓存                            |
| infra          | Elasticsearch 8 可用（P5+）                           | 🟢 P5+ 必需            | 未配置时检索降级到 MySQL LIKE              |
| shared-proto   | content.proto / events.proto 补全                     | 🔴 必需（P4.6 自身完成） | 自行修改                                   |
| core-edu (ai08) | gRPC 50053 启用                                       | 🟢 可选（content 独立） | 不依赖 core-edu 实时数据                    |
| ai (ai12)      | gRPC 50058 启用                                       | 🟢 P5 联调时必需       | grpc-mock 拦截                              |

### 4.2 我的就绪标志（供下游消费）

- [ ] **P4 就绪**：
  - [ ] content gRPC 50054 启用（HealthService.Check 返回 SERVING）
  - [ ] TextbookService 5 RPC 可调用（Create/Get/List/Update/Delete）
  - [ ] ChapterService 5 RPC 可调用（Create/Get/List/Update/Delete）
  - [ ] KnowledgeGraphService 4 RPC 可调用（GetPrerequisites/GetLearningPath/AddPrerequisite/RemovePrerequisite）
  - [ ] QuestionService 7 RPC 可调用（Create/BatchCreate/Get/List/Update/Delete/Publish/Search）
  - [ ] edu.content.knowledge_point.events / edu.content.question.events topic 可发布
  - [ ] /readyz 返回 DB/Neo4j/Kafka 三依赖状态
- [ ] **P5 就绪**：
  - [ ] GET /questions/search 检索 API 可用（延迟 < 200ms）
  - [ ] QuestionService.SearchQuestions gRPC 可调用
  - [ ] BatchCreateQuestions 与 ai12 联调通过
- [ ] **P6+ 就绪**：
  - [ ] 审核工作流状态机完整
  - [ ] 知识图谱可视化 API 可用

### 4.3 我提供的 mock（供下游消费）

在 content 真实服务就绪前，为下游（teacher-bff / student-bff / ai / data-ana）提供以下 mock：

- **gRPC mock**（grpc-mock 拦截 50054 端口）：
  - TextbookService.ListTextbooks 返回固定 5 个 Textbook（语数英理化）
  - ChapterService.ListChapters 返回固定章节树（每教材 10 章）
  - KnowledgeGraphService.GetLearningPath 返回固定 8 个 KnowledgePoint 推荐顺序
  - KnowledgeGraphService.GetPrerequisites 返回固定 3 个前置知识点
  - QuestionService.SearchQuestions 返回固定 20 个 Question（含 options）
  - QuestionService.BatchCreateQuestions 返回成功 + 生成 20 个 ID
- **Kafka mock**：content 就绪前不发布真实事件，下游使用本地 stub

---

## §5 风险与缓解

| 风险                                       | 阶段 | 缓解措施                                                     |
| ------------------------------------------ | ---- | ------------------------------------------------------------ |
| ISSUE-002 topic 策略未仲裁导致 Outbox 阻塞 | P4   | 优先推动 coord 仲裁；开发期间用 stub topic，仲裁后切换        |
| ISSUE-004 RPC 数量未仲裁导致 proto 阻塞    | P4   | 优先推动 coord 仲裁；按建议方案 21 RPC 实现，仲裁后调整       |
| Neo4j 与 MySQL 双向一致性                 | P4   | Outbox 事件驱动 + 幂等去重 + consumer lag 监控                |
| ES 索引重建期间检索不可用                  | P5   | alias 切换模式（双索引蓝绿）                                  |
| AI 批量出题 CreateQuestions 高并发         | P5   | batch_size ≤ 100 + 异步队列 + 限流                            |
| schema 迁移期间历史数据 created_by 缺失    | P4   | 按 ISSUE-007 方案：NULL 起步或 'system' 默认值回填            |

---

## §6 与 workline.md §1 对齐

- 批次 3（P4）：ai09 content 11 天（workline.md §1 排期 `after b2b, 11d`）
- 本排期 P4 实际 21 天（含测试 + README 修正），与 workline.md 11 天存在差距
- **差距原因**：workline.md §1 为 coord 初始规划（标注"ai09 接管后必须自行细化"），本文件为 ai09 细化后的实际排期
- **同步动作**：提请 coord 在 workline.md §1 更新 content 工期为 21 天（或协调压缩测试任务）
