1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
21 KiB
21 KiB
模块理解确认书 — core-edu
AI 标识:ai08(按 ai-allocation.md §3.2 接管,原 ai03 阶段 1 文档已归位) 负责模块:core-edu(P3) 阶段:架构设计外包 · 阶段 1(全局理解)· ai08 审计补全版 日期:2026-07-09(初稿)/ 2026-07-09(ai08 审计补全) 关联文档:ai-allocation.md、004 架构影响地图、pending-features.md、coord 交叉审查报告
1. 我在架构中的位置
- 层级:业务微服务层(L5),同时承载 D2 教学组织 与 D3 教学核心 两个限界上下文(见 004 §1.1b)
- 上游:
- teacher-bff(P2 已 REST fetch 调用
/exams/homework/grades) - student-bff(P3 依赖,作答/查成绩)
- parent-bff(P4 依赖,查看子女成绩/考勤)
- api-gateway(直接路由
/api/v1/exams等,绕过 BFF 的内部直连场景)
- teacher-bff(P2 已 REST fetch 调用
- 下游:MySQL(独占库
core_edu_*表前缀)、Kafka(Outbox 事件发布)、Redis(P3 引入:作业高并发提交锁 + DataScope 缓存 + 短期聚合缓存) - 通信方式:
- 端口声明(coord 全局端口矩阵):
- HTTP:3004(P3)
- gRPC:50053(P3 启用)
- /metrics:随 HTTP 3004(Prometheus 抓取)
2. 我的限界上下文
- 聚合职责:跨 D2 教学组织(classes 模块,待合并)+ D3 教学核心(exams / homework / grades + 排课/考勤 course/lesson/schedule/attendance 四表,pending-features P3 要求)
- 聚合根:Exam、Homework、Grade、Class(待合并)、Course、Lesson、Schedule、Attendance(P3 新增)
- 我不负责:
- 不负责题库内容(→ content 服务,core-edu 通过事件通知 content 教学内容变更,004 §4 服务依赖图
CoreEdu -.事件.-> Content) - 不负责学情分析/掌握度计算(→ data-ana 服务,消费 core-edu 事件)
- 不负责通知投递(→ msg 服务,消费 core-edu 事件)
- 不负责 AI 出题(→ ai 服务,ai 通过 gRPC 调 content 查题库)
- 不负责权限/角色管理(→ iam,core-edu 仅消费 iam 事件同步教师关联)
- 不负责题库内容(→ content 服务,core-edu 通过事件通知 content 教学内容变更,004 §4 服务依赖图
- 数据自治:独占
core_edu数据库(init-sql 中为next_edu_cloud.core_edu_*),表前缀core_edu_*,禁止跨库联表(004 §12.1) - CDC 联动:MySQL binlog 已通过 Debezium 投递到 Kafka 的
edu-cdc.next_edu_cloud.core_edu_grades/core_edu_exams/core_edu_homework/core_edu_attendancetopic,由 data-ana 消费写 ClickHouse 宽表(known-issues §2.6 已实施)
3. 我与外部的契约
- 暴露的 gRPC 契约(core_edu.proto,包名
next_edu_cloud.core_edu.v1,已定义待 P3 启用 server):ExamService:CreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExamHomeworkService:AssignHomework / GetHomework / ListHomeworkByClass / SubmitHomeworkGradeService:RecordGrade / GetGrade / ListGradesByStudent/Exam/Homework- 缺口:缺
AttendanceService(coord 整改清单 #14,P3 补全,coord §6)
- 发布的领域事件(events.proto + outbox.publisher.ts TOPIC_MAP):
coord 已仲裁(coord §3.1):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)。当前 outbox payload 仅含业务字段,P3 必须补schema_version。 - 消费的事件:
- 错误码前缀:
CORE_EDU_*(见 application-error.ts CoreEduErrorCode),子域(exams/homework/grades/attendance)统一用CORE_EDU_*,不再细分EXAMS_/HOMEWORK_/GRADES_(coord §5.5 仲裁) - 响应信封:必须遵循 004 §11.5 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 + transactionalIdcore-edu-tx) - gRPC server:P3 启用(
@grpc/grpc-js+@bufbuild/protobuf,coord 已在 buf.gen.yaml 补 gRPC 插件,coord 整改 #16) - Temporal:P3 引入,仅试点 1 个工作流(考试发布编排:创建作业→通知,pending-features P3)
- 可观测: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):教师创建考试 → 发布 → 学生作答 → 教师批改 → 事件到 Kafka → 成绩统计更新 → 全链路可观测
- P3 关键功能:
- CoreEdu 服务考试/作业/成绩域 CRUD + 批改业务编排 + Outbox 事件发布
- MySQL schema:exams / exam_questions / homework_assignments / homework_submissions / homework_answers / grade_records / outbox_events(当前 schema 仅 4 表,缺 exam_questions / homework_submissions / homework_answers,P3 必须补全)
- Outbox 模式 + Outbox relay worker(当前 relay 在 NestJS 进程内 setInterval 5s,pending-features 提及"独立 Go 服务
services/outbox-relay/"是远期目标,P3 保留进程内模式) - Kafka topics:
edu.teaching.exam.published/edu.teaching.homework.graded/edu.teaching.grade.recorded(按 coord 仲裁后的命名) - Teacher BFF 扩展(考试/作业/成绩的查询与 mutation)
- teacher-portal 扩展(考试创建/作业批改/成绩查看页面)
- student-portal 微前端(学生作答作业页面)
- Temporal 试点 1 个工作流(考试发布编排)
- gRPC server 启用(端口 50053)
- 排课/考勤数据模型(course/lesson/schedule/attendance 四表)+ AttendanceService proto 补全
- 依赖上游:P1 classes 黄金模板、P2 iam(用户身份 + 权限 + DataScope)
- 下游依赖方:student-bff(P3)、parent-bff(P4,查看子女成绩/考勤)、data-ana(消费 CDC + 领域事件)、msg(消费事件触发通知)、content(接收教学内容变更事件)
6. 我需要对齐的黄金模板项(对照 classes 服务)
- 权限装饰器
@RequirePermission(exams.controller 全覆盖;需核对 homework/grades controller) - 错误码前缀
CORE_EDU_*(已用 CoreEduErrorCode 枚举) - logger / metrics / tracer 三支柱
/healthz健康检查/readyz(已实现 DB SELECT 1 探针,health.controller.ts Drizzledb.execute(sql\SELECT 1`)`,需补 Kafka 连接探针 + Redis ping 探针)- 优雅关闭 SIGTERM(main.ts 已处理 outboxPublisher.stop + disconnectKafka)
- 测试覆盖率 ≥ 80%(当前 0%,无测试文件,仅 vitest.config.ts 配置就绪)
- Dockerfile 多阶段构建(需核对)
- Zod 输入验证(当前 Controller 直接接收 body,未 Zod 校验;classes 用 zod schema)
- GlobalErrorFilter 统一兜底(响应信封 ActionState 对齐,coord §5.7)
- 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 L20/L22 违反 known-issues §1.4 "禁止 console.*" 规则)
服务审计表 — ai08(core-edu)
对照 黄金模板 classes 服务,审计已实现的 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 项
- ❌ 考试生命周期状态机缺失(当前仅
draft初值,无published → in_progress → grading → graded → archived转换与校验) - ❌ 作业状态机不完整(仅
assigned → submitted,缺graded;pending-features 要求HomeworkGraded事件) - ❌ 成绩录入无业务校验(不校验 exam/homework 是否存在、score 是否在 totalScore 范围内、是否重复录入)
- ❌ 作业提交高并发优化缺失(004 §9.2 要求 Redis 分布式锁 + 排队)
- ❌ 无
grade.updated/homework.graded事件触发点(proto 已定义,service 未实现) - ❌ 未消费 IAM
user.created/user.updated/user.deleted事件(初始化教师默认关联) - ⚠️ Drizzle
db直接导出 vs classes 的getDb()函数式 — 不一致,建议统一为getDb() - ⚠️ kafka.ts 用
console.log/console.warn,应改用结构化 logger - ⚠️ classes 模块在 core-edu 仅有
classes.module.ts占位,P3 待合并(classes 服务代码迁入 + 删除独立 services/classes) - ⚠️ 入口仍为 REST,proto gRPC 契约已定义但未接入
@grpc/grpc-js+ buf generate 代码 - ❌ 无 Zod 输入验证(Controller 直接接收
body: CreateExamInput,未走 zod schema) - ❌ 无测试
- ❌ TOPIC_MAP 命名违规(coord 已仲裁统一为
edu.teaching.<aggregate>.<action>,当前代码用edu.exam.events等表名分组风格,coord §3.1) - ❌ 排课/考勤数据模型缺失(pending-features P3 要求 course/lesson/schedule/attendance 四表,当前 core-edu 仅 exams/homework/grades/outbox 四表)
- ❌ core_edu.proto 缺 AttendanceService(coord 整改 #14,P3 必须补全)
- ❌ DataScope 下推未实现(004 §5.3 要求 Repository 层根据 dataScope 注入 WHERE,当前 PermissionGuard 仅做粗粒度角色判断,未做行级数据过滤)
- ❌ 事件 schema_version 字段缺失(known-issues §1.3 要求 Kafka 事件带
schema_version,当前 outbox payload 仅含业务字段) - ❌ Temporal 工作流未引入(pending-features P3 要求试点 1 个工作流:考试发布编排,当前未集成)
- ⚠️ outbox schema 缺少 event_id 字段(消费端幂等去重要求 event_id,当前 outbox 仅用 id 作主键,但 event_id 应独立于 outbox id 以支持重投递幂等)
- ❌ 成绩计算公式未配置化(ai-allocation §5 ai08 设计重点要求支持加权/平均/自定义公式,当前仅原始 score 存储)
跨模块契约对齐(ai08 接管后核对 coord 仲裁结果)
| 待确认项 | coord 仲裁结论 | 状态 |
|---|---|---|
iam user.created 等事件 topic |
edu.identity.user.created / .updated / .deleted(004 §7.2) |
✅ 已仲裁,core-edu P3 实现消费端 |
| core-edu 端口 3004 + gRPC 50053 | 不冲突,已纳入 coord 全局端口矩阵 | ✅ 已仲裁 |
| Kafka topic 命名 | 统一为 edu.teaching.<aggregate>.<action>(coord §3.1) |
✅ 已仲裁,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) |
✅ 已仲裁 |
| 错误码前缀 | CORE_EDU_* 统一(不再细分 EXAMS_/HOMEWORK_/GRADES_,coord §5.5) |
✅ 已仲裁 |
| core_edu.proto AttendanceService 缺失 | P3 补全(coord 整改 #14) | ❌ 待 ai08 P3 补全 proto + service 实现 |
下一步(阶段 2 入口)
ai08 进入阶段 2,按 ai-allocation.md §5 ai08 设计重点 产出模块架构设计文档 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)
- classes 黄金模板对齐(Drizzle
阶段 2 设计需先解决上述 20 项差距与跨模块契约对齐。
AI Agent: ai08 (core-edu) Coordinator: coord-ai Branch: 单仓库并行模式(直接 push main)