# content 内容资源服务

> 版本：1.0（P4 完整实现）
> 端口：HTTP 3005 / gRPC 50054

## 职责

内容资源限界上下文，管理 Textbook、Chapter、KnowledgePoint、Question 四个聚合。
支持多存储：MySQL（写模型权威源）+ Neo4j（知识图谱派生读模型）+ Elasticsearch（P5 引入，题库全文检索）。
事件驱动：Outbox 模式发布 4 个聚合 topic，Neo4j Sync Worker 异步消费事件同步图谱。

## 技术栈

- NestJS 10.x + TypeScript 5.6（ESM，相对 import 带 `.js` 后缀）
- Drizzle ORM 0.31（MySQL，`getDb()` 函数式懒加载）
- neo4j-driver 5.x（知识图谱，仅读模型查询）
- KafkaJS 2.2（idempotent producer + transactionalId）
- @grpc/grpc-js + @nestjs/microservices（gRPC server 50054）
- @paralleldrive/cuid2（统一 ID 策略）
- Zod 3.x（Controller 层全量 schema.parse）
- pino（日志）+ prom-client（指标）+ OpenTelemetry（追踪）

## 开发

```bash
pnpm install
pnpm dev        # HTTP 3005 + gRPC 50054
pnpm build
pnpm typecheck
pnpm lint
pnpm test
```

## 环境变量

| 变量                          | 说明                                               |
| ----------------------------- | -------------------------------------------------- |
| `PORT`                        | HTTP 服务端口（默认 3005）                         |
| `GRPC_PORT`                   | gRPC 服务端口（默认 50054）                        |
| `DATABASE_URL`                | MySQL 连接串（必填）                               |
| `KAFKA_BROKERS`               | Kafka broker 列表（逗号分隔，默认 localhost:9092） |
| `NEO4J_URL`                   | Neo4j Bolt 连接 URL（可选）                        |
| `NEO4J_PASSWORD`              | Neo4j 密码（可选）                                 |
| `REDIS_URL`                   | Redis 连接（预留，P5+ 缓存）                       |
| `ES_URL`                      | Elasticsearch 地址（P5 引入）                      |
| `JWT_SECRET`                  | JWT 密钥（Gateway 校验，服务可选）                 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry OTLP 端点（可选）                    |

## 模块结构

```
src/
├─ config/              # env / database(getDb) / kafka / neo4j
├─ grpc/                # gRPC controllers（4 Service, 22 RPC）
│  ├─ grpc.module.ts
│  ├─ grpc-types.ts     # 手动定义 proto message TS 类型
│  ├─ textbook.grpc.controller.ts
│  ├─ chapter.grpc.controller.ts
│  ├─ knowledge-graph.grpc.controller.ts
│  └─ question.grpc.controller.ts
├─ textbooks/           # 教材领域（schema/repository/service/controller/dto）
├─ chapters/            # 章节领域
├─ knowledge-points/    # 知识点领域（含前置依赖管理）
├─ questions/           # 题库领域
├─ shared/
│  ├─ errors/           # ApplicationError + GlobalErrorFilter（CONTENT_* 前缀 + ZodError 分支）
│  ├─ health/           # /healthz + /readyz（DB/Neo4j/Kafka 多依赖检查）
│  ├─ lifecycle/        # 优雅关闭
│  ├─ observability/    # logger / metrics / tracer
│  ├─ outbox/           # Outbox 模式（schema/repository/publisher/service/module/events）
│  └─ sync/             # Neo4j Sync Worker（消费 Kafka 事件异步同步图谱）
├─ middleware/          # auth.middleware + permission.guard
├─ app.module.ts
└─ main.ts              # HTTP + gRPC hybrid app
```

## REST API 端点

### Textbooks

| 方法     | 路径             | 说明                      |
| -------- | ---------------- | ------------------------- |
| `POST`   | `/textbooks`     | 创建教材                  |
| `GET`    | `/textbooks`     | 教材列表（支持分页/过滤） |
| `GET`    | `/textbooks/:id` | 教材详情                  |
| `PUT`    | `/textbooks/:id` | 更新教材                  |
| `DELETE` | `/textbooks/:id` | 删除教材                  |

### Chapters

| 方法     | 路径                             | 说明           |
| -------- | -------------------------------- | -------------- |
| `POST`   | `/chapters`                      | 创建章节       |
| `GET`    | `/chapters/textbook/:textbookId` | 按教材列出章节 |
| `GET`    | `/chapters/:id`                  | 章节详情       |
| `PUT`    | `/chapters/:id`                  | 更新章节       |
| `DELETE` | `/chapters/:id`                  | 删除章节       |

### Knowledge Points

| 方法     | 路径                                                  | 说明                           |
| -------- | ----------------------------------------------------- | ------------------------------ |
| `POST`   | `/knowledge-points`                                   | 创建知识点                     |
| `GET`    | `/knowledge-points/chapter/:chapterId`                | 按章节列出知识点               |
| `GET`    | `/knowledge-points/:id`                               | 知识点详情                     |
| `GET`    | `/knowledge-points/:id/prerequisites`                 | 查询前置依赖（读 Neo4j）       |
| `POST`   | `/knowledge-points/:id/prerequisites`                 | 添加前置依赖（发 Outbox 事件） |
| `DELETE` | `/knowledge-points/:id/prerequisites/:prerequisiteId` | 移除前置依赖（发 Outbox 事件） |
| `PUT`    | `/knowledge-points/:id`                               | 更新知识点                     |
| `DELETE` | `/knowledge-points/:id`                               | 删除知识点                     |

### Questions

| 方法     | 路径                                           | 说明                      |
| -------- | ---------------------------------------------- | ------------------------- |
| `POST`   | `/questions`                                   | 创建题目                  |
| `GET`    | `/questions`                                   | 题目列表（支持分页/过滤） |
| `GET`    | `/questions/knowledge-point/:knowledgePointId` | 按知识点列出题目          |
| `GET`    | `/questions/:id`                               | 题目详情                  |
| `PUT`    | `/questions/:id`                               | 更新题目                  |
| `DELETE` | `/questions/:id`                               | 删除题目                  |

## gRPC API（端口 50054）

Proto 定义：`packages/shared-proto/proto/content.proto`（包名 `next_edu_cloud.content.v1`）

| Service                 | RPC 数 | 关键方法                                                                                                                                  |
| ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `TextbookService`       | 5      | CreateTextbook / GetTextbook / ListTextbooks / UpdateTextbook / DeleteTextbook                                                            |
| `ChapterService`        | 5      | CreateChapter / GetChapter / ListChapters / UpdateChapter / DeleteChapter                                                                 |
| `KnowledgeGraphService` | 4      | GetPrerequisites / GetLearningPath / AddPrerequisite / RemovePrerequisite                                                                 |
| `QuestionService`       | 8      | CreateQuestion / BatchCreateQuestions / GetQuestion / ListQuestions / UpdateQuestion / DeleteQuestion / PublishQuestion / SearchQuestions |

**合计 22 RPC。**

## 事件驱动

### Outbox 模式

业务操作（create/update/delete）在同一事务内写业务表 + `content_outbox_events` 表。
独立 `OutboxPublisher` worker 轮询 pending 记录投递 Kafka（at-least-once，最大重试 5 次，指数退避）。

### Kafka Topics（4 个聚合 topic，action 字段区分事件类型）

| Topic                                | 事件类型（action）                                            |
| ------------------------------------ | ------------------------------------------------------------- |
| `edu.content.textbook.events`        | created / updated / published / archived                      |
| `edu.content.chapter.events`         | created / updated / deleted                                   |
| `edu.content.knowledge_point.events` | created / updated / prerequisite_added / prerequisite_removed |
| `edu.content.question.events`        | created / updated / published / deleted                       |

### Neo4j Sync Worker

消费 `edu.content.knowledge_point.events` topic，异步同步知识图谱：

- `kp.created` / `kp.updated` → MERGE 节点
- `prerequisite_added` → MERGE 关系
- `prerequisite_removed` → DELETE 关系

Neo4j 不可用时非阻塞跳过（不影响 MySQL 写入和主流程）。

## 健康检查

| 端点           | 用途                                                     | 鉴权 |
| -------------- | -------------------------------------------------------- | ---- |
| `GET /healthz` | 存活探针（liveness），仅返回进程状态                     | 无   |
| `GET /readyz`  | 就绪探针（readiness），检查 DB/Neo4j/Kafka，失败返回 503 | 无   |

## 质量校验

| 检查项   | 命令                    | 状态              |
| -------- | ----------------------- | ----------------- |
| 类型检查 | `pnpm typecheck`        | ✅ 0 错误         |
| Lint     | `pnpm lint`             | ✅ 0 错误         |
| 单元测试 | `pnpm test`             | ✅ 182 tests 通过 |
| 覆盖率   | `vitest run --coverage` | ✅ 66.94%（≥60%） |

## 对外契约

- gRPC proto：`packages/shared-proto/proto/content.proto`
- 事件 proto：`packages/shared-proto/proto/events.proto`（TextbookEvent / ChapterEvent / KnowledgePointEvent / QuestionEvent）
- 契约文档：`docs/architecture/issues/contracts/content_contract.md`
