feat(core-edu): 完整实现 core-edu 教学核心服务
包含 classes/exams/homework/grades/attendance/scheduling 域、outbox、iam-consumer、redis 配置等完整实现
This commit is contained in:
@@ -256,22 +256,34 @@
|
||||
|
||||
### 2.4 core-edu(TS/NestJS,P3)
|
||||
|
||||
| 场景 | 技术/规则 |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 考试全生命周期 | 教师创建 → 发布 → 学生作答 → 教师批改 → 成绩统计 |
|
||||
| Outbox 模式 | 业务事务同写 `outbox_events` 表,后台 relay worker 投递 Kafka |
|
||||
| Outbox relay | Go 写独立服务 `services/outbox-relay/`,轻量高吞吐 |
|
||||
| Kafka topic | `exam.published` / `homework.graded` / `grade.recorded` |
|
||||
| 不引入 Saga | 跨服务一致性用 Outbox + 最终一致性 |
|
||||
| 批改后联动 | 批改完成 → 发 `homework.graded` 事件 → 下游消费(DataAna/Msg) |
|
||||
| Temporal 试点 | 仅 1 个工作流(考试发布编排:创建作业→通知) |
|
||||
| schema 表 | exams / exam_questions / homework_assignments / homework_submissions / homework_answers / grade_records / outbox_events |
|
||||
| datetime 列 | Drizzle `datetime` 列需 Date 对象,HTTP 请求体里是 ISO 字符串,service 层必须 `new Date(input)` 转换否则报 `toISOString is not a function` |
|
||||
| Kafka 非阻塞 | 开发环境 Kafka 未启动时 `connectKafka()` 必须 try/catch 且 main 中用 `void` 调用,否则阻塞服务启动 |
|
||||
| 相对 import | `src/<domain>/` 下文件回溯一级用 `../`,不要用 `../../`(NestJS ESM 模块) |
|
||||
| 身份头读取 | Controller 用 `@Req() req` 从 `req.headers['x-user-id']` 读 createdBy/gradedBy,不依赖未注册的 AuthMiddleware |
|
||||
| 健康检查 | health.controller 用 Drizzle `db.execute(sql\`SELECT 1\`)`,不用 typeorm DataSource |
|
||||
| Outbox 验证 | 业务事务同写 `core_edu_outbox` 表,Kafka 未启动时事件 status=failed,启动后会重试 |
|
||||
| 场景 | 技术/规则 |
|
||||
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 考试全生命周期 | 教师创建 → 发布 → 学生作答 → 教师批改 → 成绩统计 |
|
||||
| Outbox 模式 | 业务事务同写 `outbox_events` 表,后台 relay worker 投递 Kafka |
|
||||
| Outbox relay | Go 写独立服务 `services/outbox-relay/`,轻量高吞吐 |
|
||||
| Kafka topic | `edu.teaching.exam.created` / `edu.teaching.homework.graded` / `edu.teaching.grade.recorded`(ISSUE-002/004 仲裁统一前缀) |
|
||||
| 不引入 Saga | 跨服务一致性用 Outbox + 最终一致性 |
|
||||
| 批改后联动 | 批改完成 → 发 `homework.graded` 事件 → 下游消费(DataAna/Msg) |
|
||||
| Temporal 试点 | 仅 1 个工作流(考试发布编排:创建作业→通知) |
|
||||
| schema 表 | exams / exam_questions / exam_submissions / homework / homework_submissions / homework_answers / grades / grade_formulas / attendance / courses / lessons / schedules / classes / teacher_associations / outbox |
|
||||
| datetime 列 | Drizzle `datetime` 列需 Date 对象,HTTP 请求体里是 ISO 字符串,service 层必须 `new Date(input)` 转换否则报 `toISOString is not a function` |
|
||||
| Kafka 非阻塞 | 开发环境 Kafka 未启动时 `connectKafka()` 必须 try/catch 且 main 中用 `void` 调用,否则阻塞服务启动 |
|
||||
| 相对 import | `src/<domain>/` 下文件回溯一级用 `../`,不要用 `../../`(NestJS ESM 模块) |
|
||||
| 身份头读取 | Controller 用 `@Req() req` 从 `req.headers['x-user-id']` 读 createdBy/gradedBy,不依赖未注册的 AuthMiddleware |
|
||||
| 健康检查 | health.controller 用 Drizzle `db.execute(sql\`SELECT 1\`)`,不用 typeorm DataSource |
|
||||
| Outbox 验证 | 业务事务同写 `core_edu_outbox` 表,Kafka 未启动时事件 status=failed,启动后会重试 |
|
||||
| Drizzle 索引语法 | drizzle-orm 0.31+ 的 `mysqlTable` 第三参数回调必须返回对象(`{ idx: index(...).on(...) }`),不能返回数组 |
|
||||
| Drizzle datetime defaultNow | `datetime` 列类型不支持 `defaultNow()`,需改用 `timestamp` 列类型或在 service 层显式传入 `new Date()` |
|
||||
| KafkaJS 连接状态 | KafkaJS Producer 类型无 `isConnected()` 方法,需监听 `producer.connect`/`producer.disconnect` 事件自行维护布尔标志 |
|
||||
| 状态机类型转换 | DB 查询返回的 `status` 字段为 `string`,传入状态机函数前需 `as ExamStatus` 转换(drizzle 不支持 enum 字面量类型推导) |
|
||||
| Controller createdBy | Controller 构造 `CreateExamInput`/`AssignHomeworkInput` 时需显式从 `req.userId` 赋值 `createdBy` 字段,Zod schema 不含此字段 |
|
||||
| Redis 分布式锁 | 作业提交幂等用 Redis `SET NX EX`,Redis 不可用时降级到 DB 唯一索引兜底 |
|
||||
| 课表冲突检测 | `findOverlappingSchedules` 查 teacher + class 两个维度的时间重叠,ISSUE-006 #2 裁决 room_id 仅 P3 预留不校验冲突 |
|
||||
| 成绩公式优先级 | scope 优先级 class(3) > subject(2) > school(1),ISSUE-006 #3 裁决;`gradeFormulas` 表按 effectiveFrom/effectiveTo 时间窗口匹配 |
|
||||
| 作业宽限期 | 默认 grace_period=300 秒(ISSUE-006 #4 裁决),提交时 `dueDate + gracePeriod` 判断是否迟到 |
|
||||
| 考试归档 | ISSUE-006 #7 裁决 archived 仅软删除(设 status + archivedAt),不物理删除 |
|
||||
| 事件 payload | `buildEvent` 统一封装 event_id/occurred_at/metadata.schema_version,ISSUE-006 #6 裁决 exam.submitted 事件只带 submission_id+studentId |
|
||||
| IAM 事件消费 | `iam-consumer` 订阅 `edu.iam.teacher.assigned`/`edu.iam.class.created`,幂等 upsert 到 `teacher_associations`/`classes` 表 |
|
||||
|
||||
### 2.5 content(TS/NestJS,P4)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user