# 模块理解确认书 — classes > AI:ai07(TS / 教学组织 · 黄金模板) > 阶段:阶段 1 交付物 > 日期:2026-07-10 > 关联:[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[AI 分配方案](../../../docs/architecture/ai-allocation.md)、[pending-features P1](../../../docs/architecture/roadmap/pending-features.md)、[项目规则](../../../.trae/rules/project_rules.md) > > **本确认书定位**:classes 是 Edu 微服务的 **黄金模板**(ai-allocation.md §3.4),是其余 8 个 TS 服务的对照基准。本确认书既是对 classes 现状的审计,也是为其他 AI 提供的"读这一份即可理解黄金模板全貌"的入口。 --- ## 1. 我在架构中的位置 - **层级**:L5 业务微服务层(004 §3.1 六层架构) - **业务领域**:**D2 教学组织领域**(004 §1.1b),承载班级(Class)聚合根 - **上游(谁调用我)**: - `api-gateway`(Go/Gin):HTTP 反向代理 `/api/v1/classes/*` → `http://classes:3001/classes/*`(Gateway 已注册 classes 路由块,含精确路径 + 通配路径) - `teacher-bff`(NestJS):P2 起通过 GraphQL 聚合调用 classes 班级列表(教师仪表盘) - 未来:`student-bff`、`parent-bff`(同样走 BFF 聚合) - **下游(我调用谁)**: - `MySQL`(独占库 `classes_db`,连接串 `DATABASE_URL`,mysql2 连接池 + Drizzle ORM) - `Redis`(依赖已声明 `ioredis`,env 已留 `REDIS_URL` 占位;当前未启用,P3 合并入 core-edu 后启用班级列表缓存) - `Kafka`(依赖已声明 `kafkajs`,当前未启用;P3 起 Outbox 事件发布 `edu.org.class.created` 等) - **通信方式**: - 入口:**HTTP/REST**(当前实现,端口 3001) - 出口:MySQL(Drizzle ORM) - 演进:proto 已定义 `ClassService`(5 个 RPC),P3 起 core-edu 合并 classes 后启用 gRPC server 50053 - **不持有跨服务状态**:无本地内存缓存,无 WebSocket 连接 ### 1.1 黄金模板职责 classes 除了承载 D2 教学组织领域(班级 CRUD)业务外,**首要职责是作为黄金模板**: | 黄金模板职责 | 说明 | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | 横切关注点基准 | 错误处理 / 可观测性 / 安全 / 契约 / 测试 / 文档 / 配置 / Dockerfile 全部落地,其他 TS 服务对齐 | | 目录结构基准 | `src/{main,app.module,config,middleware,,shared}` 标准结构 | | 对照 checklist 源 | 产出黄金模板对照 checklist(见 [02 架构设计 §10](./02-architecture-design.md#10-黄金模板对照-checklist供其他-ts-服务自检))供 ai01-ai10 自检 | | 模式回写 | 各阶段新模式实现后回写本模板(Outbox 模式 P3 / 长连接 P5 / CDC P4) | --- ## 2. 我的限界上下文 ### 2.1 我负责的聚合 / 实体 | 聚合根 | 实体 / 值对象 | 当前表 | 职责 | | ----------------- | ----------------------------------------- | --------- | --------------------------------- | | **Class**(班级) | name、gradeId、headTeacherId、description | `classes` | 班级 CRUD、按年级过滤、班主任归属 | > **演进**:P3 合并入 core-edu 后,classes 表与 `subjects`(学科)、`enrollment`(选课)同属 D2 教学组织域,由 core-edu 统一承载(见 004 §1.3 CICD→Edu 映射)。 ### 2.2 业务领域归属 - **D2 教学组织领域**(004 §1.1b):classes 独占此领域(当前),P3 起与 subjects、enrollment 共同组成 D2 - 上游依赖:无业务领域依赖(教学组织是基础数据,不依赖其他业务领域) - 下游被依赖: - core-edu D3 教学核心域(考试/作业按班级归属) - data-ana D6 智能洞察域(班级维度统计聚合) - msg D5 沟通通知域(班级群组广播) ### 2.3 我不负责什么(边界外) - ❌ 学科(subjects)→ core-edu(P3 新增,D2 教学组织) - ❌ 选课 / 学生分班(enrollment)→ core-edu(P3 新增) - ❌ 考试 / 作业 / 成绩 → core-edu(D3 教学核心) - ❌ 教材 / 知识点 / 题库 → content(D4 内容资源) - ❌ 站内信 / 通知投递 → msg(D5 沟通通知) - ❌ 学情分析 / 掌握度计算 → data-ana(D6 智能洞察) - ❌ 用户 / 角色 / 权限管理 → iam(D1 身份认证) - ❌ JWT 校验 → api-gateway(classes 仅读 `x-user-id` / `x-user-roles` 头) - ❌ 年级(grade)数据 → 当前 classes 仅持有 `gradeId` 外键引用,年级实体归属待 core-edu P3 定义(当前由前端/管理端维护) --- ## 3. 我与外部的契约 ### 3.1 我消费的 proto message(从 shared-proto) - 当前**不消费**任何外部 proto(classes 是基础数据源,不反向依赖业务服务) - 未来 P3 起:core-edu 合并 classes 后,消费 `iam.proto` 的 `BatchGetUsers`(班主任信息批量查询) ### 3.2 我暴露的契约 #### 当前 REST API(已实现,见 [classes.controller.ts](../src/classes/classes.controller.ts)) | Method | Path | 权限 | 请求体 / 参数 | 响应 | 说明 | | ------ | -------------- | ---------------- | -------------------------- | ---------------------------------- | ---------------------------------- | | POST | `/classes` | `CLASSES_CREATE` | `{name, gradeId, ...}` | `{success, data: ClassResponse}` | 创建班级 | | GET | `/classes` | `CLASSES_READ` | `?gradeId=`(可选) | `{success, data: ClassResponse[]}` | 列表,支持按年级过滤 | | GET | `/classes/:id` | `CLASSES_READ` | path: `id` | `{success, data: ClassResponse}` | 单条查询 | | PUT | `/classes/:id` | `CLASSES_UPDATE` | path:`id` + `{name?, ...}` | `{success, data: ClassResponse}` | 更新(空 body 抛 ValidationError) | | DELETE | `/classes/:id` | `CLASSES_DELETE` | path: `id` | `{success: true}` | 删除(先校验存在) | > **响应信封**:遵循 ActionState(004 §11.5),成功 `{success:true, data:T}`,失败 `{success:false, error:{code,message,details?,traceId?}}`。 #### 健康检查(见 [health.controller.ts](../src/shared/health/health.controller.ts)) | Method | Path | 鉴权 | 用途 | | ------ | ---------- | ---- | ------------------------------------------------------------------- | | GET | `/healthz` | 无 | liveness,进程存活 | | GET | `/readyz` | 无 | readiness,校验 DB `SELECT 1`,失败 503 | | GET | `/metrics` | 无 | Prometheus 指标抓取(见 [main.ts](../src/main.ts) 第 23-26 行注册) | #### Proto 契约(见 [classes.proto](../../../packages/shared-proto/proto/classes.proto)) ```protobuf package next_edu_cloud.classes.v1; service ClassService { rpc CreateClass(CreateClassRequest) returns (Class); rpc GetClass(GetClassRequest) returns (Class); rpc ListClasses(ListClassesRequest) returns (ListClassesResponse); rpc UpdateClass(UpdateClassRequest) returns (Class); rpc DeleteClass(DeleteClassRequest) returns (Empty); } ``` > **proto 完备性**:5 个 RPC 与 REST 端点一一对应,已包含分页字段(`page_size` / `page_token`),REST 实现尚未落地分页。P3 合并后由 core-edu 启用 gRPC server 50053(004 §16.7.3 #1)。 #### 我发布的领域事件(当前未实现,P3 由 core-edu 接管) | 事件 | 触发时机 | Topic(遵循 004 §7.2) | 消费者 | | ------------------ | ------------ | --------------------------- | -------------------------------- | | `ClassCreated` | 班级创建成功 | `edu.org.class.created` | data-ana(建班级维度宽表行) | | `ClassUpdated` | 班级信息变更 | `edu.org.class.updated` | data-ana、msg(班主任变更通知) | | `ClassDeleted` | 班级删除 | `edu.org.class.deleted` | data-ana、core-edu(关联检查) | | `ClassTransferred` | 班主任变更 | `edu.org.class.transferred` | msg(通知新/旧班主任)、data-ana | > **事件 message 契约**:`events.proto` 已定义 `ClassEvent`(event_id / aggregate_id / event_type / occurred_at / class_id / name / action / metadata),见 [events.proto](../../../packages/shared-proto/proto/events.proto) 第 15-24 行。Topic 命名遵循 004 §7.2 `edu...` 强制格式。 --- ## 4. 我的技术栈 | 维度 | 选型 | 说明 | | -------- | --------------------------------- | --------------------------------------------------------------- | | 语言 | TypeScript 5.6+ | ESM 模式(`"type": "module"`),相对 import 用 `.js` 后缀 | | 框架 | NestJS 10 | 装饰器 + DI,适合 DDD 分层 | | ORM | Drizzle ORM 0.31 | 参数化查询,类型安全,替代 TypeORM | | 数据库 | MySQL 8(mysql2 驱动) | 连接池 `connectionLimit: 10` | | 校验 | Zod 3.23 | 输入 DTO 校验,controller 层 `schema.parse(body)` | | 日志 | pino 9 + pino-pretty(dev) | 结构化 JSON,`base:{service,version}` | | 指标 | prom-client 15 | 自定义 Counter/Histogram + 默认进程指标,`/metrics` 端点 | | 链路 | OpenTelemetry SDK + OTLP exporter | auto-instrumentations,serviceName=`classes` | | 测试 | Vitest 2.1 + @vitest/coverage-v8 | 单元测试(mock repository),阈值 60%(待提升至 80%) | | 容器 | node:20-alpine(多阶段构建) | builder + runtime 两阶段(项目规则要求 node:22-alpine,待对齐) | | ID 生成 | uuid v4 | 当前实现;project_rules 要求 cuid2(黄金模板待迁移) | | 预留依赖 | ioredis、kafkajs | 已声明但未使用,为 P3 Redis 缓存 + Kafka 事件预留 | --- ## 5. 我的阶段归属 - **阶段**:**P1 地基阶段**(已实现,黄金模板) - **当前阶段目标**(pending-features P1):classes 域 CRUD 端到端跑通 + 全横切关注点落地 + 30 分钟可复制新服务 - **依赖上游阶段产出**:无(P1 是地基,classes 是最早交付的服务) - **未来演进路径**: - P3:合并入 core-edu(ai08 负责),classes 表 + subjects + enrollment 共同组成 D2 教学组织域;启用 gRPC server 50053 - P3:补齐 Outbox 事件发布能力(`shared/outbox/` 目录) - P3:启用 Redis 班级列表缓存(TTL 5 分钟,事件驱动失效,见 004 §6.3) - P3:落地 DataScope 6 级 WHERE 注入(见 004 §5.3,当前 PermissionGuard 仅做角色级校验) --- ## 6. 我需要对齐的黄金模板项(对照 classes 自身——作为基准的自我审计) > classes 是黄金模板本身,本节为**自我审计**,确认各项已落地,供其他 TS 服务对照。 | 检查项 | 状态 | 证据 | 待整改 | | ------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------ | ---------- | | 权限装饰器 `@RequirePermission` | ✅ | [permission.guard.ts](../src/middleware/permission.guard.ts) + controller 全部方法装饰 | — | | 错误码前缀 `CLASSES_` | ✅ | [application-error.ts](../src/shared/errors/application-error.ts) 7 类错误码均带前缀 | — | | logger(pino) | ✅ | [logger.ts](../src/shared/observability/logger.ts) | — | | metrics(prom-client) | ✅ | [metrics.ts](../src/shared/observability/metrics.ts) + `/metrics` 端点 | — | | tracer(OTel) | ⚠️ | [tracer.ts](../src/shared/observability/tracer.ts) 第 20 行仍用 `console.log`,需改 `logger.info` | P1 整改 | | `/healthz` | ✅ | [health.controller.ts](../src/shared/health/health.controller.ts) liveness | — | | `/readyz` | ✅ | readiness 校验 DB `SELECT 1`,失败 503 | — | | 优雅关闭(SIGTERM) | ✅ | [main.ts](../src/main.ts) 第 31-34 行 + [lifecycle.service.ts](../src/shared/lifecycle/lifecycle.service.ts) | — | | Zod 输入验证 | ✅ | [classes.dto.ts](../src/classes/classes.dto.ts) + controller `schema.parse(body)` | — | | GlobalErrorFilter | ✅ | [global-error.filter.ts](../src/shared/errors/global-error.filter.ts) 捕获 ZodError + ApplicationError | — | | Dockerfile 多阶段 | ✅ | [Dockerfile](../Dockerfile) builder + runtime 两阶段 | node:20→22 | | 测试覆盖率 | ⚠️ | [vitest.config.ts](../vitest.config.ts) 阈值 60%,需提升至 80% | P1 整改 | | `shared/outbox/` 目录 | ❌ | 缺失,需补齐事件发布能力 | P3 补齐 | | `typeorm` 冗余依赖 | ❌ | [package.json](../package.json) 第 33 行仍声明 typeorm,已用 Drizzle,需移除 | P1 整改 | | `@Req()` → `@Query()` | ❌ | [classes.controller.ts](../src/classes/classes.controller.ts) 第 49-53 行 list 方法仍用 `@Req()` | P1 整改 | | ID 生成 cuid2 | ❌ | [classes.service.ts](../src/classes/classes.service.ts) 第 1 行用 uuid v4 | P3 迁移 | > **整改项汇总**:4 项 P1 立即整改(tracer console.log / 测试覆盖率 / typeorm 移除 / @Req 替换)+ 2 项 P3 演进(outbox / cuid2),详见 [02 架构设计 §11 整改清单](./02-architecture-design.md#11-整改清单与里程碑)。 --- ## 7. 待 coord 仲裁 / 提请事项 | # | 议题 | 我的倾向 | 影响 | | --- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------- | | 1 | classes 在 P3 合并入 core-edu 后,黄金模板源码是否保留独立目录 `services/classes/`? | 保留(作为只读对照基准,core-edu 复制其结构) | 影响 ai08 合并策略与黄金模板 checklist 归属 | | 2 | `node:20-alpine` → `node:22-alpine` 升级是否在 P1 立即做? | 立即做(project_rules §15.8 镜像预拉清单已是 node:22-alpine) | 影响所有 TS 服务 Dockerfile 基准 | | 3 | cuid2 迁移时机:P1 黄金模板立即迁移,还是 P3 合并时统一迁移? | P1 立即迁移(黄金模板应先行,避免 ai08 继承 uuid 遗留) | 影响 classes 表主键、proto message 字段类型 |