# 模块理解确认书 — core-edu

> AI 标识：ai08（按 [ai-allocation.md §3.2](../../../docs/architecture/ai-allocation.md) 接管，原 ai03 阶段 1 文档已归位）
> 负责模块：core-edu（P3）
> 阶段：架构设计外包 · 阶段 1（全局理解）· ai08 审计补全版
> 日期：2026-07-09（初稿）/ 2026-07-09（ai08 审计补全）
> 关联文档：[ai-allocation.md](../../../docs/architecture/ai-allocation.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[pending-features.md](../../../docs/architecture/roadmap/pending-features.md)、[coord 交叉审查报告](../../../docs/architecture/coord-cross-review.md)

---

## 1. 我在架构中的位置

- **层级**：业务微服务层（L5），同时承载 **D2 教学组织** 与 **D3 教学核心** 两个限界上下文（见 [004 §1.1b](../../../docs/architecture/004_architecture_impact_map.md#11b-业务领域视角)）
- **上游**：
  - teacher-bff（P2 已 REST fetch 调用 `/exams` `/homework` `/grades`）
  - student-bff（P3 依赖，作答/查成绩）
  - parent-bff（P4 依赖，查看子女成绩/考勤）
  - api-gateway（直接路由 `/api/v1/exams` 等，绕过 BFF 的内部直连场景）
- **下游**：MySQL（独占库 `core_edu_*` 表前缀）、Kafka（Outbox 事件发布）、Redis（P3 引入：作业高并发提交锁 + DataScope 缓存 + 短期聚合缓存）
- **通信方式**：
  - 入口 HTTP：当前 REST（`/exams`、`/homework`、`/grades`），端口 **3004**
  - 入口 gRPC：**P3 启用**（[004 §4.2](../../../docs/architecture/004_architecture_impact_map.md#42-grpc-启用阶段矩阵) 裁决），端口 **50053**，proto 已定义 `ExamService/HomeworkService/GradeService`
  - 出口：Kafka 事件（Outbox 模式，[004 §12.2](../../../docs/architecture/004_architecture_impact_map.md#12-架构约束) 强制，core-edu 不享受派生数据豁免）
- **端口声明**（[coord 全局端口矩阵](../../../docs/architecture/coord-cross-review.md#43-全局端口矩阵coord-裁决基线已同步至-004-12)）：
  - HTTP：3004（P3）
  - gRPC：50053（P3 启用）
  - /metrics：随 HTTP 3004（Prometheus 抓取）

## 2. 我的限界上下文

- **聚合职责**：跨 **D2 教学组织**（classes 模块，待合并）+ **D3 教学核心**（exams / homework / grades + 排课/考勤 course/lesson/schedule/attendance 四表，[pending-features P3](../../../docs/architecture/roadmap/pending-features.md#p3-核心教学阶段m7-m10) 要求）
- **聚合根**：Exam、Homework、Grade、Class（待合并）、Course、Lesson、Schedule、Attendance（P3 新增）
- **我不负责**：
  - 不负责题库内容（→ content 服务，core-edu 通过事件通知 content 教学内容变更，[004 §4 服务依赖图](../../../docs/architecture/004_architecture_impact_map.md#4-服务依赖图) `CoreEdu -.事件.-> Content`）
  - 不负责学情分析/掌握度计算（→ data-ana 服务，消费 core-edu 事件）
  - 不负责通知投递（→ msg 服务，消费 core-edu 事件）
  - 不负责 AI 出题（→ ai 服务，ai 通过 gRPC 调 content 查题库）
  - 不负责权限/角色管理（→ iam，core-edu 仅消费 iam 事件同步教师关联）
- **数据自治**：独占 `core_edu` 数据库（init-sql 中为 `next_edu_cloud.core_edu_*`），表前缀 `core_edu_*`，**禁止跨库联表**（[004 §12.1](../../../docs/architecture/004_architecture_impact_map.md#12-架构约束)）
- **CDC 联动**：MySQL binlog 已通过 Debezium 投递到 Kafka 的 `edu-cdc.next_edu_cloud.core_edu_grades` / `core_edu_exams` / `core_edu_homework` / `core_edu_attendance` topic，由 data-ana 消费写 ClickHouse 宽表（[known-issues §2.6](../../../docs/troubleshooting/known-issues.md) 已实施）

## 3. 我与外部的契约

- **暴露的 gRPC 契约**（[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)，包名 `next_edu_cloud.core_edu.v1`，已定义待 P3 启用 server）：
  - `ExamService`：CreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExam
  - `HomeworkService`：AssignHomework / GetHomework / ListHomeworkByClass / SubmitHomework
  - `GradeService`：RecordGrade / GetGrade / ListGradesByStudent/Exam/Homework
  - **缺口**：缺 `AttendanceService`（coord 整改清单 #14，P3 补全，[coord §6](../../../docs/architecture/coord-cross-review.md#6-裁决整改清单汇总)）
- **发布的领域事件**（[events.proto](../../../packages/shared-proto/proto/events.proto) + [outbox.publisher.ts TOPIC_MAP](../src/shared/outbox/outbox.publisher.ts)）：

> **coord 已仲裁**（[coord §3.1](../../../docs/architecture/coord-cross-review.md#31-core-edu-教学事件-topic-命名双轨p0-最严重)）：topic 命名**统一为 `edu.teaching.<aggregate>.<action>`**，原代码中的 `edu.exam.events` / `edu.homework.events` / `edu.grade.events` / `edu.class.events` 风格**违规**，P3 必须改 TOPIC_MAP。下表已按裁决后规范列出。

| 事件（eventType）   | Topic（裁决后）                            | 触发时机           | 消费者        | 当前状态   |
| ------------------- | ------------------------------------------ | ------------------ | ------------- | ---------- |
| exam.created        | `edu.teaching.exam.created`                | CreateExam 事务内  | msg、data-ana | ✅ 已发布  |
| exam.updated        | `edu.teaching.exam.updated`                | UpdateExam         | msg           | ✅ 已发布  |
| exam.deleted        | `edu.teaching.exam.deleted`                | DeleteExam         | data-ana      | ✅ 已发布  |
| exam.published      | `edu.teaching.exam.published`              | 考试发布（状态机） | msg、data-ana | ❌ P3 新增 |
| homework.assigned   | `edu.teaching.homework.assigned`           | AssignHomework     | msg、data-ana | ✅ 已发布  |
| homework.submitted  | `edu.teaching.homework.submitted`          | SubmitHomework     | data-ana、msg | ✅ 已发布  |
| homework.graded     | `edu.teaching.homework.graded`             | 教师批改完成       | msg、data-ana | ❌ P3 新增 |
| grade.recorded      | `edu.teaching.grade.recorded`              | RecordGrade        | data-ana、msg | ✅ 已发布  |
| grade.updated       | `edu.teaching.grade.updated`               | 批改后修正         | data-ana      | ❌ P3 新增 |
| class.transferred   | `edu.org.class.created`（合并后归 org 域） | classes 合并后     | data-ana      | ⚠️ 待合并  |
| attendance.recorded | `edu.teaching.attendance.recorded`         | 考勤录入           | data-ana、msg | ❌ P3 新增 |

- **事件 schema 版本化**：所有事件 payload 必须含 `schema_version` 字段（默认 `"v1"`），消费端按版本处理（[known-issues §1.3](../../../docs/troubleshooting/known-issues.md)）。当前 outbox payload 仅含业务字段，**P3 必须补 `schema_version`**。
- **消费的事件**：
  - `edu.identity.user.created` / `edu.identity.user.updated` / `edu.identity.user.deleted`（IAM，初始化/同步教师默认班级关联，[004 §7.2](../../../docs/architecture/004_architecture_impact_map.md#72-事件-topic-分类) 已定义，**当前未消费**，P3 待补）
  - `edu.insight.mastery.updated`（data-ana，掌握度更新后 core-edu 可消费用于推荐个性化练习，[004 §8.3](../../../docs/architecture/004_architecture_impact_map.md#83-异步事件闭环)）— P3 可选，P4 强制
- **错误码前缀**：`CORE_EDU_*`（见 [application-error.ts CoreEduErrorCode](../src/shared/errors/application-error.ts)），子域（exams/homework/grades/attendance）**统一用 `CORE_EDU_*`**，不再细分 `EXAMS_` / `HOMEWORK_` / `GRADES_`（[coord §5.5](../../../docs/architecture/coord-cross-review.md#55-p1-问题core-edu-子模块前缀) 仲裁）
- **响应信封**：必须遵循 [004 §11.5](../../../docs/architecture/004_architecture_impact_map.md#115-统一响应信封actionstate) ActionState 信封 `{success, data? | error: {code, message, details?, traceId?}}`，GlobalErrorFilter 已注册

## 4. 我的技术栈

- 语言：TypeScript 5.5+（ESM 模式，NestJS ESM 模式下相对 import 必须 `.js` 后缀）
- 框架：NestJS 10
- ORM：Drizzle ORM（mysql2 driver，直接 `db` 导出，**与 classes 的 `getDb()` 不一致**，需 P3 统一）
- 存储：MySQL 8（独占库 `core_edu_*` 表前缀）、Redis（P3 引入）、Kafka（kafkajs，idempotent + transactionalId `core-edu-tx`）
- **gRPC server**：P3 启用（`@grpc/grpc-js` + `@bufbuild/protobuf`，coord 已在 [buf.gen.yaml](../../../packages/shared-proto/buf.gen.yaml) 补 gRPC 插件，[coord 整改 #16](../../../docs/architecture/coord-cross-review.md#6-裁决整改清单汇总)）
- **Temporal**：P3 引入，仅试点 1 个工作流（考试发布编排：创建作业→通知，[pending-features P3](../../../docs/architecture/roadmap/pending-features.md#p3-核心教学阶段m7-m10)）
- 可观测：pino + prom-client（已 4 指标：http_requests_total / request_duration_seconds / outbox_pending / outbox_published_total）+ OTel auto-instrumentations（已具备）

## 5. 我的阶段归属

- **P3 核心教学**：考试全生命周期 + Outbox + Kafka 事件落地
- **退出标准**（[pending-features P3](../../../docs/architecture/roadmap/pending-features.md#p3-核心教学阶段m7-m10)）：教师创建考试 → 发布 → 学生作答 → 教师批改 → 事件到 Kafka → 成绩统计更新 → 全链路可观测
- **P3 关键功能**：
  1. CoreEdu 服务考试/作业/成绩域 CRUD + 批改业务编排 + Outbox 事件发布
  2. MySQL schema：exams / exam_questions / homework_assignments / homework_submissions / homework_answers / grade_records / outbox_events（**当前 schema 仅 4 表**，缺 exam_questions / homework_submissions / homework_answers，P3 必须补全）
  3. Outbox 模式 + Outbox relay worker（**当前 relay 在 NestJS 进程内 setInterval 5s，pending-features 提及"独立 Go 服务 `services/outbox-relay/`"是远期目标，P3 保留进程内模式**）
  4. Kafka topics：`edu.teaching.exam.published` / `edu.teaching.homework.graded` / `edu.teaching.grade.recorded`（按 coord 仲裁后的命名）
  5. Teacher BFF 扩展（考试/作业/成绩的查询与 mutation）
  6. teacher-portal 扩展（考试创建/作业批改/成绩查看页面）
  7. student-portal 微前端（学生作答作业页面）
  8. Temporal 试点 1 个工作流（考试发布编排）
  9. **gRPC server 启用（端口 50053）**
  10. **排课/考勤数据模型（course/lesson/schedule/attendance 四表）+ AttendanceService proto 补全**
- **依赖上游**：P1 classes 黄金模板、P2 iam（用户身份 + 权限 + DataScope）
- **下游依赖方**：student-bff（P3）、parent-bff（P4，查看子女成绩/考勤）、data-ana（消费 CDC + 领域事件）、msg（消费事件触发通知）、content（接收教学内容变更事件）

## 6. 我需要对齐的黄金模板项（对照 classes 服务）

- [x] 权限装饰器 `@RequirePermission`（exams.controller 全覆盖；需核对 homework/grades controller）
- [x] 错误码前缀 `CORE_EDU_*`（已用 [CoreEduErrorCode 枚举](../src/shared/errors/application-error.ts)）
- [x] logger / metrics / tracer 三支柱
- [x] `/healthz` 健康检查
- [x] `/readyz`（已实现 DB SELECT 1 探针，[health.controller.ts](../src/shared/health/health.controller.ts) Drizzle `db.execute(sql\`SELECT 1\`)`，**需补 Kafka 连接探针 + Redis ping 探针**）
- [x] 优雅关闭 SIGTERM（main.ts 已处理 outboxPublisher.stop + disconnectKafka）
- [ ] 测试覆盖率 ≥ 80%（**当前 0%**，无测试文件，仅 vitest.config.ts 配置就绪）
- [ ] Dockerfile 多阶段构建（需核对）
- [ ] Zod 输入验证（**当前 Controller 直接接收 body，未 Zod 校验**；classes 用 zod schema）
- [x] GlobalErrorFilter 统一兜底（响应信封 ActionState 对齐，[coord §5.7](../../../docs/architecture/coord-cross-review.md#57-p2-问题错误信封结构不一致)）
- [x] Outbox 模式（事务内写业务表 + outbox 表，独立 publisher 投递，**TOPIC_MAP 命名违规待修正**）
- [ ] **gRPC server 实现**（P3 启用，端口 50053，proto 已定义）
- [ ] **ActionState 信封 traceId**（GlobalErrorFilter 已输出 traceId，需验证）
- [ ] **DataScope 下推**（004 §5.3 要求 Repository 层根据 dataScope 注入 WHERE，当前未实现）
- [ ] **Drizzle 访问方式统一**（core-edu 用 `export const db`，classes 用 `getDb()` 函数式，需 P3 统一为 `getDb()`）
- [ ] **kafka.ts 用 logger 替代 console.log/warn**（[config/kafka.ts](../src/config/kafka.ts) L20/L22 违反 [known-issues §1.4](../../../docs/troubleshooting/known-issues.md) "禁止 console.*" 规则）

---

## 服务审计表 — ai08（core-edu）

> 对照 [黄金模板 classes 服务](../../classes/src/)，审计已实现的 core-edu 服务。状态：✅ 达标 / ⚠️ 部分 / ❌ 缺失

| 服务     | 权限装饰器                             | 错误码前缀      | logger                           | metrics               | tracer       | /healthz | /readyz                               | 优雅关闭 | 测试覆盖率 | Dockerfile | gRPC server      | ActionState |
| -------- | -------------------------------------- | --------------- | -------------------------------- | --------------------- | ------------ | -------- | ------------------------------------- | -------- | ---------- | ---------- | ---------------- | ----------- |
| core-edu | ✅ `@RequirePermission(EXAM_*)` 全覆盖 | ✅ `CORE_EDU_*` | ⚠️ pino（kafka.ts 仍用 console） | ✅ prom-client 4 指标 | ✅ OTel auto | ✅       | ⚠️ DB SELECT 1（缺 Kafka/Redis 探针） | ✅       | 0% ❌      | 待核对     | ❌ P3 启用 50053 | ✅ 已对齐   |

### 审计发现的关键差距（P3 阶段 2 设计需解决）

> ai03 原列 12 项 + ai08 审计新增 8 项 = 20 项

1. ❌ 考试生命周期状态机缺失（当前仅 `draft` 初值，无 `published → in_progress → grading → graded → archived` 转换与校验）
2. ❌ 作业状态机不完整（仅 `assigned → submitted`，缺 `graded`；pending-features 要求 `HomeworkGraded` 事件）
3. ❌ 成绩录入无业务校验（不校验 exam/homework 是否存在、score 是否在 totalScore 范围内、是否重复录入）
4. ❌ 作业提交高并发优化缺失（[004 §9.2](../../../docs/architecture/004_architecture_impact_map.md#92-高并发提交) 要求 Redis 分布式锁 + 排队）
5. ❌ 无 `grade.updated` / `homework.graded` 事件触发点（proto 已定义，service 未实现）
6. ❌ 未消费 IAM `user.created` / `user.updated` / `user.deleted` 事件（初始化教师默认关联）
7. ⚠️ Drizzle `db` 直接导出 vs classes 的 `getDb()` 函数式 — **不一致**，建议统一为 `getDb()`
8. ⚠️ [kafka.ts](../src/config/kafka.ts) 用 `console.log`/`console.warn`，应改用结构化 logger
9. ⚠️ classes 模块在 core-edu 仅有 `classes.module.ts` 占位，**P3 待合并**（classes 服务代码迁入 + 删除独立 services/classes）
10. ⚠️ 入口仍为 REST，proto gRPC 契约已定义但未接入 `@grpc/grpc-js` + buf generate 代码
11. ❌ 无 Zod 输入验证（Controller 直接接收 `body: CreateExamInput`，未走 zod schema）
12. ❌ 无测试
13. ❌ **TOPIC_MAP 命名违规**（coord 已仲裁统一为 `edu.teaching.<aggregate>.<action>`，当前代码用 `edu.exam.events` 等表名分组风格，[coord §3.1](../../../docs/architecture/coord-cross-review.md#31-core-edu-教学事件-topic-命名双轨p0-最严重)）
14. ❌ **排课/考勤数据模型缺失**（pending-features P3 要求 course/lesson/schedule/attendance 四表，当前 core-edu 仅 exams/homework/grades/outbox 四表）
15. ❌ **core_edu.proto 缺 AttendanceService**（coord 整改 #14，P3 必须补全）
16. ❌ **DataScope 下推未实现**（[004 §5.3](../../../docs/architecture/004_architecture_impact_map.md#53-datascop-6-级数据范围) 要求 Repository 层根据 dataScope 注入 WHERE，当前 PermissionGuard 仅做粗粒度角色判断，未做行级数据过滤）
17. ❌ **事件 schema_version 字段缺失**（[known-issues §1.3](../../../docs/troubleshooting/known-issues.md) 要求 Kafka 事件带 `schema_version`，当前 outbox payload 仅含业务字段）
18. ❌ **Temporal 工作流未引入**（pending-features P3 要求试点 1 个工作流：考试发布编排，当前未集成）
19. ⚠️ **outbox schema 缺少 event_id 字段**（消费端幂等去重要求 event_id，当前 outbox 仅用 id 作主键，但 event_id 应独立于 outbox id 以支持重投递幂等）
20. ❌ **成绩计算公式未配置化**（ai-allocation §5 ai08 设计重点要求支持加权/平均/自定义公式，当前仅原始 score 存储）

### 跨模块契约对齐（ai08 接管后核对 coord 仲裁结果）

| 待确认项                              | coord 仲裁结论                                                                                                                                                | 状态                                     |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| iam `user.created` 等事件 topic       | `edu.identity.user.created` / `.updated` / `.deleted`（[004 §7.2](../../../docs/architecture/004_architecture_impact_map.md#72-事件-topic-分类)）             | ✅ 已仲裁，core-edu P3 实现消费端        |
| core-edu 端口 3004 + gRPC 50053       | 不冲突，已纳入 [coord 全局端口矩阵](../../../docs/architecture/coord-cross-review.md#43-全局端口矩阵coord-裁决基线已同步至-004-12)                            | ✅ 已仲裁                                |
| Kafka topic 命名                      | **统一为 `edu.teaching.<aggregate>.<action>`**（[coord §3.1](../../../docs/architecture/coord-cross-review.md#31-core-edu-教学事件-topic-命名双轨p0-最严重)） | ✅ 已仲裁，core-edu P3 修 TOPIC_MAP      |
| data-ana 消费 core-edu 事件           | 消费 `edu.teaching.exam.created` / `homework.submitted` / `grade.recorded` + CDC topics                                                                       | ✅ ai06 已确认，data-ana 已实施 CDC 消费 |
| msg 消费 core-edu 事件                | 消费 `edu.teaching.exam.created` / `homework.assigned` / `grade.recorded` 触发通知                                                                            | ⚠️ 待 ai05 在 msg 02 文档确认消费契约    |
| proto 包名规范                        | 保持 `next_edu_cloud.core_edu.v1`（[coord §2.1](../../../docs/architecture/coord-cross-review.md#21-proto-包名规范冲突p0-全局)）                              | ✅ 已仲裁                                |
| 错误码前缀                            | `CORE_EDU_*` 统一（不再细分 `EXAMS_`/`HOMEWORK_`/`GRADES_`，[coord §5.5](../../../docs/architecture/coord-cross-review.md#55-p1-问题core-edu-子模块前缀)）    | ✅ 已仲裁                                |
| core_edu.proto AttendanceService 缺失 | P3 补全（[coord 整改 #14](../../../docs/architecture/coord-cross-review.md#6-裁决整改清单汇总)）                                                              | ❌ 待 ai08 P3 补全 proto + service 实现  |

---

## 下一步（阶段 2 入口）

ai08 进入阶段 2，按 [ai-allocation.md §5 ai08 设计重点](../../../docs/architecture/ai-allocation.md) 产出模块架构设计文档 `02-architecture-design.md`：

- **core-edu 模块架构设计**：
  - classes 黄金模板对齐（Drizzle `getDb()` 统一 + Zod + 测试 + Dockerfile 多阶段）
  - 考试生命周期状态机（草稿 → 已发布 → 作答中 → 批改中 → 已出分 → 已归档）
  - Outbox 事件定义与发布（补全 `exam.published` / `homework.graded` / `grade.updated` / `attendance.recorded`，TOPIC_MAP 重命名为 `edu.teaching.*`，payload 补 `schema_version` + `event_id`）
  - 成绩计算公式与配置化（加权/平均/自定义公式）
  - 作业提交高并发优化（Redis 分布式锁 + 排队机制）
  - 排课/考勤数据模型（course/lesson/schedule/attendance 四表）+ AttendanceService proto 补全
  - 与 Kafka 的 Relay Worker 轮询逻辑（保留进程内模式，远期迁出独立 Go 服务）
  - gRPC server 启用（端口 50053）
  - DataScope 下推（Repository 层 WHERE 注入）
  - Temporal 工作流试点（考试发布编排）
  - 消费 IAM 事件（user.created/updated/deleted）

阶段 2 设计需先解决上述 20 项差距与跨模块契约对齐。

---

**AI Agent**: ai08 (core-edu)
**Coordinator**: coord-ai
**Branch**: 单仓库并行模式（直接 push main）
