chore(content): merge content full implementation into main
Merge feat/content-ai09 with complete content service
This commit is contained in:
@@ -1,26 +1,30 @@
|
||||
# content 内容资源服务
|
||||
|
||||
> 版本:0.1(P4 骨架)
|
||||
> 端口:3005
|
||||
> 版本:1.0(P4 完整实现)
|
||||
> 端口:HTTP 3005 / gRPC 50054
|
||||
|
||||
## 职责
|
||||
|
||||
内容资源限界上下文,管理 Textbook、Chapter、KnowledgePoint 聚合。
|
||||
支持多存储:MySQL(教材/章节/知识点写模型)+ Neo4j(知识图谱前置依赖)+ Elasticsearch(题库全文检索,P4 后续补充)。
|
||||
内容资源限界上下文,管理 Textbook、Chapter、KnowledgePoint、Question 四个聚合。
|
||||
支持多存储:MySQL(写模型权威源)+ Neo4j(知识图谱派生读模型)+ Elasticsearch(P5 引入,题库全文检索)。
|
||||
事件驱动:Outbox 模式发布 4 个聚合 topic,Neo4j Sync Worker 异步消费事件同步图谱。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- NestJS 10.x + TypeScript 5.6(ESM)
|
||||
- Drizzle ORM(MySQL)
|
||||
- neo4j-driver(知识图谱)
|
||||
- Zod(运行时校验)
|
||||
- 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 # 端口 3005
|
||||
pnpm dev # HTTP 3005 + gRPC 50054
|
||||
pnpm build
|
||||
pnpm typecheck
|
||||
pnpm lint
|
||||
@@ -29,46 +33,150 @@ pnpm test
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
| ----------------------------- | ------------------------------- |
|
||||
| `PORT` | 服务端口(默认 3005) |
|
||||
| `DATABASE_URL` | MySQL 连接串 |
|
||||
| `NEO4J_URL` | Neo4j Bolt 连接 URL |
|
||||
| `NEO4J_PASSWORD` | Neo4j 密码 |
|
||||
| `ES_URL` | Elasticsearch 地址 |
|
||||
| `JWT_SECRET` | JWT 密钥 |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry OTLP 端点(可选) |
|
||||
| 变量 | 说明 |
|
||||
| ----------------------------- | -------------------------------------------------- |
|
||||
| `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(MySQL)、neo4j
|
||||
├─ 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_* 前缀)
|
||||
│ └─ observability/# logger、metrics、tracer
|
||||
├─ textbooks/ # 教材/章节/知识点 + 知识图谱
|
||||
│ ├─ 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
|
||||
└─ main.ts # HTTP + gRPC hybrid app
|
||||
```
|
||||
|
||||
## 关键端点
|
||||
## REST API 端点
|
||||
|
||||
- `POST /textbooks` 创建教材
|
||||
- `GET /textbooks` 教材列表
|
||||
- `GET /textbooks/:id` 教材详情
|
||||
- `TextbooksService.createKnowledgeGraph` 在 Neo4j 构建知识点前置依赖
|
||||
- `TextbooksService.getPrerequisites` 查询知识点前置链路
|
||||
### 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 连接,失败返回 503 | 无 |
|
||||
| 端点 | 用途 | 鉴权 |
|
||||
| -------------- | -------------------------------------------------------- | ---- |
|
||||
| `GET /healthz` | 存活探针(liveness),仅返回进程状态 | 无 |
|
||||
| `GET /readyz` | 就绪探针(readiness),检查 DB/Neo4j/Kafka,失败返回 503 | 无 |
|
||||
|
||||
实现见 `src/shared/health/health.controller.ts`,5 个 NestJS 服务(iam/core-edu/content/msg/classes)一致。
|
||||
## 质量校验
|
||||
|
||||
| 检查项 | 命令 | 状态 |
|
||||
| -------- | ----------------------- | ----------------- |
|
||||
| 类型检查 | `pnpm typecheck` | ✅ 0 错误 |
|
||||
| Lint | `pnpm lint` | ✅ 0 错误 |
|
||||
| 单元测试 | `pnpm test` | ✅ 182 tests 通过 |
|
||||
| 覆盖率 | `vitest run --coverage` | ✅ 66.94%(≥60%) |
|
||||
|
||||
## 对外契约
|
||||
|
||||
gRPC 服务 `TextbookService`、`KnowledgeGraphService` 定义见 `packages/shared-proto/proto/content.proto`。
|
||||
- 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`
|
||||
|
||||
Reference in New Issue
Block a user