# ai05 阶段 1 交付物:模块理解确认书 > AI 标识:ai05 > 负责模块:content(P4)、msg(P5) > 阶段:架构设计外包 · 阶段 1(全局理解) > 日期:2026-07-09 > 关联文档:[ai-allocation.md](./ai-allocation.md)、[004 架构影响地图](./004_architecture_impact_map.md)、[pending-features.md](./roadmap/pending-features.md)、[known-issues.md](../troubleshooting/known-issues.md) --- ## 模块理解确认书 — content ### 1. 我在架构中的位置 - **层级**:业务微服务层(L5),DDD 限界上下文 - **业务领域**:D4 内容资源领域(004 §1.1b) - **上游**(谁调用我): - teacher-bff(3003):教学场景聚合,教师查教材/知识点/题库 - student-bff(学习场景,P3 起):学生查学习路径、知识点前置 - ai(Python,P5):gRPC 查询知识点/题库用于 AI 出题(004 §4.1 `AI -.gRPC.-> Content`,§9.3 AI 辅助出题流程) - **下游**(我调用谁): - MySQL(写模型主库,独占) - Neo4j(知识图谱,前置依赖图) - Elasticsearch(题库全文检索,P4 后续补充,当前未实现) - **通信方式**: - 当前:HTTP REST(Controller,无 gRPC controller) - 目标态(004 §4.1 / pending-features P4):gRPC 暴露 `TextbookService` + `KnowledgeGraphService` - Kafka:消费 core-edu 教学内容变更通知(004 §4.1 `CoreEdu -.事件.-> Content`);发布 `edu.content.question.published`(004 §7.2) - **端口**:3005(见 [content env.ts](../../services/content/src/config/env.ts)) ### 2. 我的限界上下文 - **聚合职责**:管理 Textbook(教材)、Chapter(章节)、KnowledgePoint(知识点)、Question(题库)四个聚合 - **我的数据属于**:D4 内容资源领域 - **我不负责**: - 不负责学情分析(D6,由 data-ana 承载) - 不负责考试/作业/成绩(D3,由 core-edu 承载) - 不负责通知分发(D5,由 msg 承载) - 不直接访问 core-edu / iam / msg 的数据库 - **现有骨架领域模块**(4 个,见 [content/src](../../services/content/src)): - `textbooks/`:教材 CRUD(5 端点) - `chapters/`:章节 CRUD(5 端点,按 textbook 查询) - `knowledge-points/`:知识点 CRUD + Neo4j 知识图谱(7 端点,含前置链路查询/添加) - `questions/`:题库 CRUD(5 端点,4 种题型校验) ### 3. 我与外部的契约 - **消费的 proto message**(从 shared-proto): - 无直接消费其他服务 proto;通过 Kafka 事件接收 core-edu 教学内容变更(事件契约见 `events.proto`) - **暴露的契约**(见 [content.proto](../../packages/shared-proto/proto/content.proto),包名 `next_edu_cloud.content.v1`): - `TextbookService`:CreateTextbook / GetTextbook / ListTextbooks - `KnowledgeGraphService`:GetPrerequisites / GetLearningPath - **当前实现均为 REST,gRPC controller 未实现**(proto 已定义待迁移) - **事件契约**: - 发布:`edu.content.question.published`(题库新增/发布,消费者 AI、ES,见 004 §7.2) - 消费:core-edu 教学内容变更事件(004 §4.1,具体 topic 待 core-edu ai03 设计确认) - **错误码前缀**:`CONTENT_*`(CONTENT_VALIDATION_ERROR / NOT_FOUND / PERMISSION_DENIED / CONFLICT / BUSINESS_ERROR / DATABASE_ERROR / INTERNAL_ERROR,见 [application-error.ts](../../services/content/src/shared/errors/application-error.ts)) - **权限点**(16 个,见 [permission.guard.ts](../../services/content/src/middleware/permission.guard.ts)): - `CONTENT_TEXTBOOK_{CREATE,READ,UPDATE,DELETE}` - `CONTENT_CHAPTER_{CREATE,READ,UPDATE,DELETE}` - `CONTENT_QUESTION_{CREATE,READ,UPDATE,DELETE}` - `CONTENT_KNOWLEDGE_POINT_{CREATE,READ,UPDATE,DELETE}` ### 4. 我的技术栈 - 语言:TypeScript 5.6(ESM 模式,相对 import 带 `.js` 后缀) - 框架:NestJS 10 - ORM:Drizzle ORM 0.31 + mysql2 3.11 - 知识图谱:neo4j-driver 5.23(PREREQUISITE_OF 关系,Cypher 查询深度 1..5) - 全文检索:Elasticsearch(**待引入**,env.ts 预留 ES_URL 但 package.json 未装 @elastic/elasticsearch) - 可观测:pino logger + prom-client metrics + OpenTelemetry tracer(三支柱已具备) - 消息总线:Kafka(**待引入**,pending-features P4 未明确要求 content 发事件,但 004 §7.2 列了 `edu.content.question.published`) ### 5. 我的阶段归属 - **P4 内容分析阶段**(pending-features §P4): - 教材/章节/知识点 CRUD(仅 CRUD,不实现检索)+ 知识图谱查询(Neo4j)+ 题库 CRUD(不实现检索) - MySQL schema:textbooks / chapters / knowledge_points / questions - Neo4j 数据:知识点前置依赖图(从 MySQL 同步) - CDC 链路:Debezium 监听 MySQL binlog → Kafka → data-ana 消费写 ClickHouse - **退出标准**:教师查看知识图谱前置依赖(Neo4j 秒级返回)→ 学生查看学情诊断(ClickHouse 宽表 5s 内返回)→ CDC 链路延迟 < 5s - **依赖上游**:P1 黄金模板 classes(横切关注点对齐)、P3 core-edu(教学内容变更事件,待 ai03 设计确认 topic) ### 6. 我需要对齐的黄金模板项(对照 classes 服务) - [x] 权限装饰器 `@RequirePermission`(16 个 CONTENT_* 权限点,全部 Controller 方法已覆盖) - [x] 错误码前缀统一(`CONTENT_*`) - [x] logger / metrics / tracer 三支柱(已具备) - [x] `/healthz` 健康检查(HealthModule 已注册) - [⚠️] `/readyz`(**仅检查 DB `SELECT 1`,未检查 Neo4j 连通性**,Neo4j 故障时仍返回 ok) - [x] 优雅关闭 SIGTERM(main.ts 已处理:closeNeo4j → closeDb → shutdownTracer) - [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件) - [x] Dockerfile 多阶段构建(已具备) - [⚠️] Zod 输入验证(questions 用 service 层手动 if 校验抛 ValidationError,**非 Controller 层 schema.parse**) - [x] GlobalErrorFilter 统一兜底 ### 7. 现有骨架差距与待决策(提请 coord 仲裁) | # | 差距 | 影响 | 提请决策 | | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | C1 | **ES 完全未实现**:env.ts 预留 ES_URL 但无 @elastic/elasticsearch 依赖、无 config/elasticsearch.ts、无 service 调用 | P4 退出标准要求题库检索,pending-features 注明"P4 仅 CRUD,P5 引入 ES" | 确认 content 的 ES 检索归属 P4 还是 P5(004 §2.2 列 ES 使用者为 Content、AI) | | C2 | **无 Outbox / Kafka**:骨架无 shared/outbox/,无 Kafka producer/consumer | 004 §7.2 列 `edu.content.question.published` 事件需发布 | 确认 content 是否需 Outbox(iam 无 Outbox,core-edu 有) | | C3 | **README 与实现脱节**:README 声称 `TextbooksService.createKnowledgeGraph` 和 `getPrerequisites`,源码中 createKnowledgeGraph 不存在,getPrerequisites 在 KnowledgePointsService | 文档误导 | 阶段 2 设计需同步修正 README | | C4 | **gRPC 未实现**:proto 定义了 TextbookService/KnowledgeGraphService,但无 gRPC controller | pending-features P4 未强制 gRPC,004 §4.1 目标态 gRPC | 确认 P4 是否启用 gRPC(ai03 提请统一决策) | | C5 | **schema 定义分散**:textbooks.schema.ts 定义 3 张表(textbooks/chapters/knowledgePoints),chapters/knowledge-points schema 仅 re-export;questions.schema.ts 独立 | 维护成本 | 阶段 2 设计统一 schema 归属 | | C6 | **DB 连接模式与 iam 不一致**:content 用模块级 `const db`,iam 用 `getDb()` 函数式懒加载 | 测试 mock 困难 | coord 已在 known-issues 记录"db 常量导出对齐黄金模板",需确认统一方向 | | C7 | **表时间戳不统一**:textbooks/questions 有 created_at+updated_at,chapters/knowledge_points 无时间戳 | 审计追踪缺失 | 阶段 2 设计补齐 | | C8 | **无外键约束**:questions.knowledge_point_id → knowledge_points.id 等无 Drizzle 外键 | 引用完整性靠应用层 | 阶段 2 设计评估是否加外键 | | C9 | **addPrerequisite 降级策略不一致**:safeCreateNode 非阻塞(Neo4j 失败仅 warn),addPrerequisite 在 Neo4j 不可用时抛 InternalError | 行为不一致 | 阶段 2 设计统一降级策略 | | C10 | **proto 包名**:实际 `next_edu_cloud.content.v1`,project_rules §5 规定 `edu.content.v1` | 命名规范不一致 | **coord 仲裁**(ai03 已提请) | --- ## 模块理解确认书 — msg ### 1. 我在架构中的位置 - **层级**:业务微服务层(L5),DDD 限界上下文 - **业务领域**:D5 沟通通知领域(004 §1.1b) - **上游**(谁调用我): - teacher-bff / student-bff / parent-bff:聚合层调 msg 查询用户通知列表、发送通知 - api-gateway:REST 转发通知请求 - Kafka:消费 core-edu / iam 事件触发通知(004 §4.1 `CoreEdu -.事件.-> Msg`、`IAM -.事件.-> Msg`) - **下游**(我调用谁): - MySQL(写模型主库,独占) - Elasticsearch(全文检索,已实现 safeSearch) - push-gateway(gRPC 推送通道,004 §4.1 `PushGW → Msg`,当前用 fetch POST /internal/push 降级) - **通信方式**: - 当前:HTTP REST(Controller,无 gRPC controller) - 目标态(004 §4.1 / pending-features P5):gRPC 暴露 `NotificationService` - Kafka:消费 core-edu(ExamPublished/HomeworkGraded/GradeRecorded)、iam(UserRegistered)事件(004 §7.3) - **端口**:3007(见 [msg env.ts](../../services/msg/src/config/env.ts)) ### 2. 我的限界上下文 - **聚合职责**:管理 Notification(通知)聚合 + NotificationPreference(用户通知偏好) - **我的数据属于**:D5 沟通通知领域 - **我不负责**: - 不负责 WebSocket 长连接管理(由 push-gateway 承载) - 不负责业务数据变更(仅消费事件触发通知) - 不直接访问 core-edu / iam 的数据库 - **现有骨架领域模块**(1 个,见 [msg/src](../../services/msg/src)): - `notifications/`:通知 CRUD + ES 全文检索 + Push Gateway 推送 + 用户偏好(6 端点) ### 3. 我与外部的契约 - **消费的 proto message**(从 shared-proto): - 消费 `events.proto` 的事件 message(ClassEvent/ExamEvent/HomeworkEvent/GradeEvent) - **events.proto 当前无 NotificationEvent**,若 msg 发事件需补充 - **暴露的契约**(见 [msg.proto](../../packages/shared-proto/proto/msg.proto),包名 `next_edu_cloud.msg.v1`): - `NotificationService`:SendNotification / ListNotifications / MarkAsRead / SearchNotifications - **当前实现均为 REST,gRPC controller 未实现** - **事件契约**: - 消费(004 §7.2 / §7.3): - `edu.identity.user.created` / `edu.identity.user.updated`(IAM 发,msg 发欢迎通知) - `edu.teaching.exam.published`(core-edu 发,msg 推送考试通知给学生) - `edu.teaching.assignment.submitted`(core-edu 发,msg 通知教师) - `edu.teaching.grade.recorded`(core-edu 发,msg 通知学生) - `edu.insight.mastery.updated`(data-ana 发,msg 触发预警) - 发布:无明确(pending-features 未要求 msg 发事件) - **错误码前缀**:`MSG_*`(MSG_VALIDATION_ERROR / NOT_FOUND / PERMISSION_DENIED / CONFLICT / BUSINESS_ERROR / DATABASE_ERROR / INTERNAL_ERROR,见 [application-error.ts](../../services/msg/src/shared/errors/application-error.ts)) - **权限点**(3 个,见 [permission.guard.ts](../../services/msg/src/middleware/permission.guard.ts)): - `MSG_NOTIFICATION_SEND`、`MSG_NOTIFICATION_READ`、`MSG_NOTIFICATION_MANAGE` ### 4. 我的技术栈 - 语言:TypeScript 5.6(ESM 模式) - 框架:NestJS 10 - ORM:Drizzle ORM 0.31 + mysql2 3.11 - 全文检索:@elastic/elasticsearch 8.15(已实现 safeIndex/safeSearch,**无 mapping 定义**,依赖动态 mapping) - 消息总线:kafkajs 2.2(**已装依赖但无 consumer/producer 代码**,env.ts 有 KAFKA_BROKERS 默认值) - 推送:fetch POST 到 push-gateway `/internal/push`(降级模式,PUSH_GATEWAY_URL 未配置或失败时跳过) - 幂等去重:Redis(**env.ts 预留 REDIS_URL 但无 redis 客户端依赖**,pending-features P5 要求 event_id 去重) - 可观测:pino + prom-client + OpenTelemetry(三支柱已具备) ### 5. 我的阶段归属 - **P5 沟通与 AI 阶段**(pending-features §P5): - 会话/消息 CRUD + 调 Push Gateway 推送 + 通知偏好 - 多渠道(站内/SMS/邮件/微信),沿用旧项目 dispatcher 模式 - Elasticsearch 题库全文检索(从 MySQL 同步) - **退出标准**:教师发广播通知 → 全在线学生实时收到(Push Gateway)→ AI 辅助出题流式返回 → 题库全文检索 < 200ms - **依赖上游**:P1 黄金模板 classes、P3 core-edu(事件来源)、P5 push-gateway(推送通道,ai01 设计)、P5 ai(无直接依赖) ### 6. 我需要对齐的黄金模板项(对照 classes 服务) - [x] 权限装饰器 `@RequirePermission`(3 个 MSG_* 权限点,全部 Controller 方法已覆盖) - [x] 错误码前缀统一(`MSG_*`) - [x] logger / metrics / tracer 三支柱(已具备) - [x] `/healthz` 健康检查(HealthModule 已注册) - [⚠️] `/readyz`(**仅检查 DB `SELECT 1`,未检查 ES 连通性**,ES 故障时仍返回 ok) - [x] 优雅关闭 SIGTERM(main.ts 已处理:app.close → closeEs → closeDb → shutdownTracer) - [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件) - [x] Dockerfile 多阶段构建(已具备) - [⚠️] Zod 输入验证(Controller 用 `schema.parse(body)`,**但 ZodError 未在 GlobalErrorFilter 特殊处理,走默认 500 而非 400**) - [x] GlobalErrorFilter 统一兜底 ### 7. 现有骨架差距与待决策(提请 coord 仲裁) | # | 差距 | 影响 | 提请决策 | | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | M1 | **Kafka 消费未实现**:装了 kafkajs 但无 consumer 代码,无 shared/kafka/ 目录 | 004 §7.3 列 msg 消费 5 类事件(ExamPublished/HomeworkGraded/GradeRecorded/UserRegistered/MasteryUpdated) | 阶段 2 设计需补 Kafka consumer + 幂等去重 | | M2 | **无 Outbox**:骨架无 shared/outbox/ | msg 作为通知中心,发送通知后可能需发事件(如 NotificationSent) | 确认 msg 是否需 Outbox(iam 无,core-edu 有) | | M3 | **ES 无 mapping 定义**:依赖动态 mapping,索引名 "notifications" 硬编码在 service | 检索质量不稳定,索引管理缺失 | 阶段 2 设计补 mapping + ensureIndex | | M4 | **无独立 repository**:notifications.service.ts 直接用 db,无 repository 抽象 | 与黄金模板分层不一致(iam 有 repository) | 阶段 2 设计补 repository 层 | | M5 | **Redis 未引入**:env.ts 预留 REDIS_URL 但无 redis 客户端依赖 | pending-features P5 要求 event_id 去重(Redis SETNX 或 DB 唯一索引) | 阶段 2 设计决策:Redis SETNX vs DB 唯一索引 | | M6 | **createBatch 无事务/无批量优化**:for 循环串行调 send,无批量 INSERT | 性能瓶颈(广播场景) | 阶段 2 设计改为批量 INSERT | | M7 | **NotificationsModule 缺 exports**:notifications.module.ts 无 `exports: [NotificationsService]` | 未来 BFF 注入受阻 | 阶段 2 设计补 exports | | M8 | **ZodError 未特殊处理**:GlobalErrorFilter 未识别 ZodError,走默认 500 | 输入校验错误返回码错误(500 而非 400) | 阶段 2 设计 GlobalErrorFilter 补 ZodError 分支(iam 已有可参考) | | M9 | **gRPC 未实现**:proto 定义了 NotificationService,但无 gRPC controller | pending-features P5 未强制 gRPC | 确认 P5 是否启用 gRPC(ai03 提请统一决策) | | M10 | **README 与实现脱节**:README API 表标 `POST /notifications/:id/read`,实际是 PUT;漏 batch/user/:userId/user/:userId/page 端点;声称"消费 Kafka 事件"但无代码 | 文档误导 | 阶段 2 设计同步修正 README | | M11 | **main.ts 与 LifecycleService 重复关闭资源**:两者都调 closeDb/closeEs | 重复关闭可能报错(虽有 try-catch) | 阶段 2 设计统一关闭逻辑到 LifecycleService | | M12 | **proto 包名**:实际 `next_edu_cloud.msg.v1`,project_rules §5 规定 `edu.msg.v1` | 命名规范不一致 | **coord 仲裁**(ai03 已提请) | --- ## 三、服务审计表 — ai05 > 审计标准对照 [project_rules §3](../../.trae/rules/project_rules.md) 与 [known-issues §2.2 classes 黄金模板](../troubleshooting/known-issues.md) | 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile | | ------- | ---------------- | -------------- | ------- | -------------- | ------------------------------- | -------- | ------------------- | --------------------- | ---------- | ---------- | | content | ✅ 16 端点全覆盖 | ✅ `CONTENT_*` | ✅ pino | ✅ prom-client | ✅ OTel + auto-instrumentations | ✅ | ⚠️ 仅 DB 不查 Neo4j | ✅ closeNeo4j→closeDb | 0% | ✅ | | msg | ✅ 6 端点全覆盖 | ✅ `MSG_*` | ✅ pino | ✅ prom-client | ✅ OTel + auto-instrumentations | ✅ | ⚠️ 仅 DB 不查 ES | ✅ closeEs→closeDb | 0% | ✅ | --- ## 四、跨模块契约对齐提请(coord 交叉审查) ### 4.1 接口一致性检查 | 本服务声明 | 对方服务声明 | 是否匹配 | 备注 | | --------------------------------------------------------- | ------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ | | ai05 content: 暴露 KnowledgeGraphService.GetPrerequisites | ai06 ai: 调 content 查知识点(004 §9.3) | ⚠️ 待确认 | ai06 设计文档需确认调用签名 | | ai05 content: 暴露 TextbookService.ListTextbooks | ai03 teacher-bff: 聚合 content 查教材(004 §4.1) | ⚠️ 待确认 | ai03 设计文档需确认调用 | | ai05 content: 发布 `edu.content.question.published` | ai06 ai: 消费题库事件(004 §7.2 消费者 AI) | ⚠️ 待确认 | ai06 设计需确认是否消费 | | ai05 msg: 消费 `edu.teaching.exam.published` | ai03 core-edu: 发布考试事件 | ⚠️ 待确认 | ai03 提请 topic 命名统一(004 `edu.teaching.exam.published` vs core-edu 代码 `edu.exam.events`) | | ai05 msg: 消费 `edu.identity.user.created` | ai02 iam: 发布用户创建事件 | ⚠️ 待确认 | ai02 设计需确认 topic | | ai05 msg: 调 push-gateway `/internal/push` | ai01 push-gateway: 暴露推送端点 | ✅ 已实现 | msg 当前用 fetch POST,push-gateway 已有 /internal/push 端点 | ### 4.2 全局冲突检查 | 检查项 | 检查结果 | 备注 | | -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | 端口不冲突 | ✅ content 3005、msg 3007 | 与 iam(3002)/classes(3001)/teacher-bff(3003)/core-edu(3004)/data-ana(3006)/ai(3008) 不冲突 | | Topic 不重复 | ⚠️ 待汇总 | content 发布 `edu.content.question.published`;msg 仅消费不发布 | | 错误码前缀不重叠 | ✅ `CONTENT_*` / `MSG_*` 唯一 | 与 iam `IAM_*` / core-edu `CORE_EDU_*` 不重叠 | | Proto message 不遗漏 | ⚠️ 待确认 | events.proto 无 NotificationEvent,若 msg 发事件需补充;content.proto 缺 Update/Delete/分页(对比 classes.proto 有 page_token) | | proto 包名规范 | ⚠️ 不一致 | 实际 `next_edu_cloud..v1`,规则要求 `edu..v1`,**提请 coord 仲裁**(ai03 已提请) | ### 4.3 待 coord 仲裁的决策项 1. **proto 包名统一**:`next_edu_cloud.*` vs `edu.*`(影响全部 proto,需 coord 决策) 2. **gRPC 启用时机**:P4/P5 是否启用 gRPC controller,还是继续 REST(影响 content/msg/iam/core-edu 全部服务) 3. **content ES 检索归属**:P4(pending-features 注明"P4 仅 CRUD,P5 引入 ES")vs 004 §2.2(列 ES 使用者 Content、AI)—— 需明确 content 何时引入 ES 4. **content Outbox**:是否需要(004 §7.2 列 content 发事件,但 pending-features P4 未要求) 5. **msg Outbox**:是否需要(msg 仅消费事件触发通知,是否需发 NotificationSent 事件) 6. **msg Redis 引入**:env.ts 预留 REDIS_URL 但无依赖,幂等去重用 Redis SETNX 还是 DB 唯一索引 7. **events.proto 补充**:若 msg 发事件需追加 NotificationEvent message --- ## 五、下一步 1. **等待 coord 审核本确认书**(§4 跨模块契约对齐 + §4.3 仲裁项) 2. coord 放行后进入**阶段 2:模块架构设计文档**,按 ai-allocation.md §7 模板产出: - content 模块架构设计文档(含 Neo4j 图模型、ES 索引 mapping、题库 CRUD API、与 ai 的 gRPC 接口、教材/章节结构树) - msg 模块架构设计文档(含通知渠道抽象策略模式、ES 降级查询、Kafka 消费幂等、push-gateway 推送协议、通知模板、已读/未读状态管理) 3. 阶段 2 设计完成后同步更新 README(修正 §C3/M10 文档脱节问题)