# Coord 最终方案强制裁决 > 版本:1.0 > 日期:2026-07-09 > 决策者:coord(经用户确认) > 适用范围:Edu 项目全部 16 份 AI 设计文档(ai01-ai16) > 关联文档:[004 架构影响地图 §16](./004_architecture_impact_map.md)、[ai-allocation.md](./ai-allocation.md)、[project_rules.md](../../.trae/rules/project_rules.md) --- ## 0. 裁决原则 用户反馈:**"前面简略实现后面要重写,完全没必要,直接使用最好的方式工作"**。 各 AI 设计文档中存在大量"P2 简化 → P3 重构 → P4 再重构"的渐进式过渡方案,导致前期代码后期必废弃。coord 裁决如下: ### 0.1 强制覆盖声明 - **本文档 > 各 AI 设计文档**:凡本文档列出的裁决项,各 AI 设计文档中与之冲突的"中间过渡方案"一律作废,**直接采用最终方案** - **不分阶段**:最终方案在服务首次落地时即采用,不得以"阶段未到"为由先实现简化版 - **回写义务**:各 AI 收到本文档后,必须在 3 个工作日内回写自己的 02-architecture-design.md,删除所有"中间过渡方案"描述,对齐最终方案 ### 0.2 裁决依据 - 用户明确指示"直接使用最好的方式" - project_rules.md §3.1 已规定企业级规范(如 Dockerfile 多阶段、Zod 验证、GlobalErrorFilter),无理由分阶段 - 黄金模板 services/classes/ 已实现全部最终方案,其他服务对齐即可 --- ## 1. 通用裁决(适用全部服务) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | 影响服务 | | --- | --------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | G1 | **Dockerfile** | "P3 重构"、"P5 重构"、"待落地" | **首次实现即多阶段构建**(builder + runtime) | 全部 | | G2 | **/readyz 探针** | "P2 仅返回 ok"、"P3+ 补下游探针" | **首次实现即检查全部下游依赖**(DB SELECT 1 / Redis PING / Kafka 连接 / gRPC 下游可达) | 全部 | | G3 | **/healthz** | "P2 改造" | **首次实现即 liveness 探针** | 全部 | | G4 | **Logger** | "待引入 slog"、"console.log → P3 修复" | **首次实现即结构化日志**(TS pino / Go slog / Python structlog),禁止 console.log | 全部 | | G5 | **Metrics** | "自定义业务指标待实现" | **首次实现即 /metrics 端点 + 基础业务指标** | 全部 | | G6 | **Tracer** | "资源属性待补"、"span 待补" | **首次实现即 OTel SDK + OTLP exporter + 完整资源属性 + 全链路 span** | 全部 | | G7 | **Zod 验证** | "P2 补全"、"P3 实施中"、"部分 → P4 全 parse" | **Controller 层首次实现即全量 schema.parse(body/query/params)** | 全部 TS | | G8 | **GlobalErrorFilter** | "待落地"、"P4+ 对齐 ActionState" | **首次实现即注册 GlobalErrorFilter + 响应信封严格对齐 ActionState 结构** | 全部 | | G9 | **优雅关闭** | "P2 补全" | **首次实现即 SIGTERM → app.close() → shutdownTracer(),按序关闭 HTTP→DB→Redis→Kafka** | 全部 | | G10 | **DB 连接模式** | "const db vs getDb()" | **统一 getDb() 函数**(对齐 classes 黄金模板) | 全部 TS 持 DB 服务 | | G11 | **ID 策略** | "cuid2 vs randomUUID" | **统一 cuid2**(对齐 classes 黄金模板) | 全部 TS 持 DB 服务 | | G12 | **ESM import** | 缺 .js 后缀 | **所有相对 import 带 .js 后缀**(NestJS ESM 模式) | 全部 TS | | G13 | **import type** | 混用 | **仅用于类型的导入必须 import type** | 全部 TS | | G14 | **错误码前缀** | api-gateway 无前缀 | **每个服务错误码必须用服务名大写前缀**(GW_/IAM_/CORE_EDU_/CONTENT_/MSG_/DATA_ANA_/AI_/PUSH_/BFF_TEACHER_/BFF_STUDENT_/BFF_PARENT_/CLASSES_) | 全部 | | G15 | **错误码子前缀** | 保留 EXAMS_/HOMEWORK_/GRADES_ | **统一用聚合域前缀 CORE_EDU_***,禁止子前缀 | 前端 + core-edu | | G16 | **Kafka topic 命名** | edu.notification.events 抽象名 | **edu.\.\.\** 具体动作(如 edu.notification.sent) | 全部 | | G17 | **proto 包名** | 不一致 | **next_edu_cloud.\.v1** | 全部 | --- ## 2. BFF 层专项裁决(teacher-bff / student-bff / parent-bff) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | B1 | **API 风格** | "P2 REST → P4 GraphQL"、"P3 REST → P4+ GraphQL" | **P2 起直接 GraphQL**(GraphQL Yoga + DataLoader) | | B2 | **对下游通信** | "P2 HTTP fetch → P3+ gRPC"、"P3 HTTP → P3+ gRPC" | **首次实现即 gRPC 调用下游**(iam/core-edu/content/data-ana/msg/ai 全部 gRPC) | | B3 | **权限装饰器** | "BFF 加 @RequirePermission"、"待仲裁" | **BFF 豁免 @RequirePermission**(权限由 Gateway JWT + 下游服务负责),BFF 仅校验 x-user-id 存在 | | B4 | **越权防御** | "仅 teacher-bff 透传"、"student-bff 待仲裁" | **全部 BFF 强制自我越权防御**:student-bff 比对 userId、parent-bff 校验 childId 绑定列表、teacher-bff 校验 teacherId 与班级关系 | | B5 | **错误码前缀** | "STUDENT_BFF_ vs BFF_STUDENT_" | **BFF_TEACHER\_ / BFF_STUDENT\_ / BFF_PARENT_**(统一 BFF_ 前缀) | | B6 | **缓存策略** | "不缓存 vs Redis 短缓存" | **Redis 5-30s 短缓存**(对齐 004 §6.2 BFF 混合读策略) | | B7 | **Kafka 订阅** | "P3 评估"、"P5 评估" | **P2-P4 不订阅 Kafka**(仅同步聚合),P5 push-gateway 落地后再订阅 | | B8 | **DownstreamClient 抽象** | "是否回写 teacher-bff" | **回写 teacher-bff**,作为 BFF 模式 v2 标准抽象,3 个 BFF 统一使用 | --- ## 3. 业务服务专项裁决 ### 3.1 iam(ai06) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | I1 | **gRPC server** | "P2 仅 REST → P3 启用 gRPC" | **P2 即启用 gRPC server 50052**,同时暴露 REST + gRPC | | I2 | **Outbox** | "iam 自建 → core-edu 落地后回写 shared-ts" | **直接建 shared-ts Outbox 工具包**,iam 首次实现即用 | | I3 | **PermissionGuard** | "本地 ROLE_PERMISSIONS map → DB 驱动" | **首次实现即 DB 驱动 + Redis 缓存**,废弃本地 map | | I4 | **AuthMiddleware** | "P2 不注册 → Controller 直读 header" | **首次实现即注册 AuthMiddleware**,Controller 通过 @Req() 获取用户上下文 | | I5 | **JWT 密钥** | "P2 本地文件 → P6 Vault" | **P2 本地文件**(IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATH),P6 迁 Vault 单列后续 | | I6 | **家长-学生关联** | "接口缺失 → P3 补" | **P2 即补全**:iam_student_guardians 表 + GetChildrenByParent RPC + GET /iam/children REST(P0 阻塞 parent-bff) | | I7 | **API 版本化** | "待 coord 审查" | _*采用 /iam/v1/* 前缀_*,Gateway 同步调整 | | I8 | **iam 端点路径** | /iam/permissions/effective vs /iam/effective-permissions | **统一 GET /iam/permissions/effective**(RESTful 资源路径风格) | ### 3.2 core-edu(ai08) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ----------------------------------------- | -------------------------------------- | -------------------------------------------------------------------- | | C1 | **classes 合并** | "P3 初期 vs P3 末期" | **P3 开始前直接合并**:classes 代码迁移到 core-edu,classes 服务下线 | | C2 | **gRPC server** | "P3 启用" | **P3 首次实现即启用 gRPC server 50053** | | C3 | **Kafka console.log** | "P3 修复" | **首次实现即用 logger.info**,禁止 console.log | | C4 | **AttendanceService** | "待 coord 补 proto" | **P3 首次实现即补全 proto + 实现** | | C5 | **GetClassesByTeacher / BatchGetClasses** | "待新增" | **P3 首次实现即补全** | | C6 | **排课 room_id** | "P3 实现 vs 预留 vs P6+" | **P3 预留字段**(不实现逻辑,但 schema 含字段) | | C7 | **成绩计算公式** | "scope 优先级待定" | **class > subject > school**(越细粒度优先) | | C8 | **作业 grace_period** | "0 秒 vs 300 秒" | **默认 300 秒**(5 分钟宽限) | | C9 | **schema 强制校验** | "P3 JSON Schema vs 文档约束" | **P3 仅文档约束**,P6+ 引入 schema registry | | C10 | **exam.submitted 事件** | "包含完整 answers vs 仅 submission_id" | **仅含 submission_id**(避免事件过大) | | C11 | **archived 考试** | "物理迁移 vs 软删除 vs P6+" | **仅软删除**(is_archived 字段) | ### 3.3 content(ai09) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ------------------- | -------------------- | ------------------------------------------------ | | N1 | **gRPC server** | "gRPC 待实现" | **P4 首次实现即启用 gRPC server 50054** | | N2 | **/readyz** | "仅 DB → P4 多依赖" | **首次实现即检查 DB/Neo4j/Kafka** | | N3 | **QuestionService** | "P5 阻塞待补 proto" | **P4 即补全 QuestionService proto**(不等到 P5) | | N4 | **ErrorFilter** | "错误响应结构待对齐" | **首次实现即对齐 ActionState** | | N5 | **ChapterService** | "缺 GetChapter" | **P4 首次实现即补全** | ### 3.4 msg(ai10) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | --------------------- | --------------------------- | ----------------------------------------------------- | | M1 | **gRPC server** | "gRPC 待实现" | **P5 首次实现即启用 gRPC server 50056** | | M2 | **/readyz** | "仅 DB → P5 多依赖" | **首次实现即检查 DB/ES/Redis/Kafka/PushGateway** | | M3 | **ZodError** | "未处理 → P5 处理" | **首次实现即处理 ZodError 分支** | | M4 | **调用 push-gateway** | "gRPC PushService.Push" | **HTTP POST /internal/push**(coord 仲裁豁免 gRPC) | | M5 | **事件 topic** | "edu.teaching.assignment.*" | **edu.teaching.homework.***(与 core-edu 发布方一致) | ### 3.5 data-ana(ai11) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | --------------------------- | --------------------- | ------------------------------------------------------------------------------------------------- | | D1 | **ErrorFilter** | "v2 对齐 ActionState" | **首次实现即对齐 ActionState** | | D2 | **Dockerfile** | "v2 设计多阶段" | **首次实现即多阶段** | | D3 | **ClickHouse DDL** | "待 coord 建目录" | **直接建 infra/clickhouse/ddl/** | | D4 | **analytics.proto 扩展** | "待 coord 审议" | **P4 即补全**:4 端 Dashboard + Warning + MasteryDistribution + SubscribeMasteryUpdate Stream RPC | | D5 | **warning.triggered topic** | "待 coord 登记" | **已登记**,004 §7.2 + events.proto MasteryEvent | ### 3.6 ai(ai12) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ---------------------- | ------------------------- | ------------------------------------------------------------------------------ | | A1 | **ErrorFilter** | "待对齐" | **首次实现即对齐 ActionState** | | A2 | **Dockerfile** | "实现阶段需新增" | **首次实现即多阶段** | | A3 | **gRPC 端口** | "待 coord 补登" | **已补登 50058**(见 infra/port-allocation.md) | | A4 | **ai.proto 补全** | "待 coord" | **P5 首次实现即补全** GenerateLessonPlan / StreamGenerateQuestion | | A5 | **AIUsageEvent proto** | "待 coord" | **已补全** events.proto | | A6 | **Prompt 模板存储** | "P5 YAML / P6+ DB" | **P5 直接用 DB 存储**(避免后期迁移) | | A7 | **备课工作流** | "P5 Redis / P6+ Temporal" | **P5 用 FastAPI BackgroundTasks + Redis**(Temporal 引入成本高,P6+ 单独评估) | | A8 | **多模态** | "P5 文本 / P6+ 多模态" | **P5 仅文本**,proto v2 预留字段 | ### 3.7 push-gateway(ai02) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ------------------ | ---------------------------- | ----------------------------------------------------------------- | | P1 | **gRPC** | "备选 gRPC PushService" | **彻底删除 gRPC 设计**,纯 HTTP + WebSocket(coord 仲裁豁免) | | P2 | **X-Internal-Key** | "→ X-Internal-Token P5 立即" | **首次实现即 X-Internal-Token**(环境变量 INTERNAL_API_TOKEN) | | P3 | **Dockerfile** | "P5 重构" | **首次实现即多阶段** | | P4 | **slog** | "待引入" | **首次实现即用 slog** | | P5 | **SSE 降级** | "P7+ 考虑" | **本期仅 HTTP 长轮询预留**,SSE 不实现 | | P6 | **断线重连** | "本期仅协议预留" | **本期仅协议预留**(session_id + last_seq 字段),P6 实现补推逻辑 | ### 3.8 api-gateway(ai01) | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | -------------------- | ------------------------------------------- | ----------------------------------------------------- | | W1 | **错误码前缀** | "无前缀" | **GW_***(GW_UNAUTHORIZED 等) | | W2 | **错误信封** | "recovery/ratelimit/circuit-breaker 待整改" | **首次实现即对齐 ActionState** | | W3 | **slog** | "待引入" | **首次实现即用 slog** | | W4 | **/readyz** | "待重构" | **首次实现即真实健康检查**(轮询下游 /healthz) | | W5 | **业务 metrics** | "待实现" | **首次实现即基础业务指标**(请求量/延迟/错误率/熔断) | | W6 | **tracer 资源属性** | "待补" | **首次实现即完整**(version/env/host) | | W7 | **DevMode 防护** | "P1 待办" | **首次实现即生产防护**(DEV_MODE 仅非生产环境生效) | | W8 | **per-服务实例熔断** | "P6 待办" | **保持共享 downstream 熔断**,per-实例 P6 单独评估 | --- ## 4. 前端 4 portal 专项裁决 | # | 过渡项 | 禁止的中间方案 | 强制最终方案 | | --- | ------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | F1 | **admin-portal 端口** | 3003 / 3000 / 4003 三处矛盾 | **统一 4003** | | F2 | **admin-portal MF 配置** | teacher_app@localhost:3000 | **teacher_app@localhost:4000** | | F3 | **Web Vitals** | FID vs INP 混用 | **统一 INP**(CWV 2024 标准) | | F4 | **i18n key 命名** | bff.error / bffTeacher.error / bffStudent.error 混用 | **统一 error.\.\**(如 error.iam.invalid_credentials、error.bffTeacher.validation_error) | | F5 | **错误码子前缀** | parent-portal 保留 GRADES_/HOMEWORK_ | **统一删除子前缀**,用 CORE_EDU_* | | F6 | **iam 权限端点** | /iam/permissions/effective vs /iam/effective-permissions | **统一 /iam/permissions/effective** | | F7 | **权限点命名风格** | TEACHER_DASHBOARD_VIEW / STUDENT_DASHBOARD_READ / GRADES_READ_CHILD 混用 | **统一 \\_\**(如 DASHBOARD_VIEW、EXAM_READ、GRADE_READ),数据范围用后缀 _OWN/_CHILD(如 GRADE_READ_CHILD) | | F8 | **packages 归属** | "ai07/ai13 维护 vs coord 维护" | **ai13 维护 ui-tokens/ui-components/hooks,coord 仅维护 shared-ts/contracts** | | F9 | **GraphQL vs REST** | "P2-P6 REST,未来切 GraphQL" | **P2 起 BFF 用 GraphQL,前端用 urql/apollo** | | F10 | **MF 暴露粒度** | "AppShell 整体 vs 细粒度" | **暴露 AppShell 整体**,各 Remote 自行决定内部布局 | | F11 | **teacher-portal 归属** | "004 §15.1 写 ai07,ai-allocation §3.2 写 ai13" | **ai13 负责 teacher-portal**(ai07 负责 classes 黄金模板) | | F12 | **localStorage token** | "P6 评估迁移 httpOnly Cookie" | **P2 用 localStorage**(httpOnly Cookie + CSRF Token P6 单独评估) | --- ## 5. 跨服务契约裁决(一次性补全,不分阶段) ### 5.1 proto 契约补全(coord 负责,P2 启动前全部就位) | proto 文件 | 待补内容 | 阻塞阶段 | 完成期限 | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------- | | events.proto | ✅ 已补全(UserEvent/RoleEvent/NotificationEvent/MasteryEvent/AIUsageEvent/KnowledgePointEvent/QuestionEvent) | — | 已完成 | | iam.proto | GetViewports / GetEffectivePermissions / GetEffectiveAccess / Logout / GetPublicKey / BatchGetUsers / GetEffectiveDataScope / GetChildrenByParent | P2 | P2 启动前 | | core_edu.proto | AttendanceService.* / GetClassesByTeacher / BatchGetClasses | P3 | P3 启动前 | | content.proto | ChapterService.GetChapter / QuestionService.* / TextbookService.ListTextbooks 补全 | P4 | P4 启动前 | | analytics.proto | GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard / GetWarnings / GetMasteryDistribution / SubscribeMasteryUpdate (stream) | P4 | P4 启动前 | | ai.proto | GenerateLessonPlan / StreamGenerateQuestion / 字段扩展 | P5 | P5 启动前 | | msg.proto | BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / RecallNotification + NotificationPreferenceService + NotificationTemplateService + 3 个 message | P5 | P5 启动前 | ### 5.2 接口命名统一 | 调用方 | 被调方 | 统一命名 | 说明 | | ----------------- | ------------ | ------------------------------------------------------------ | ---------------------------- | | core-edu | content | **KnowledgeGraphService.GetPrerequisites / GetLearningPath** | 禁用 ContentService 命名 | | teacher-bff | data-ana | **GetTeacherDashboard**(无 Stats 后缀) | 统一命名 | | msg / student-bff | push-gateway | **HTTP POST /internal/push**(body 含 user_id) | 豁免 gRPC | | ai | core-edu | **ExamService.GetExam** | 方向:ai 调 core-edu,非反向 | ### 5.3 gRPC server 启用时机 | 服务 | gRPC 端口 | 启用时机 | 说明 | | ------------ | --------- | --------------- | ---------------------------------- | | iam | 50052 | **P2** | 首次实现即启用(覆盖"P2 仅 REST") | | core-edu | 50053 | **P3** | 首次实现即启用 | | content | 50054 | **P4** | 首次实现即启用 | | data-ana | 50055 | **P4** | 首次实现即启用 | | msg | 50056 | **P5** | 首次实现即启用 | | ai | 50058 | **P5** | 首次实现即启用 | | push-gateway | — | **不启用** | 豁免 gRPC | | BFF(3 个) | — | **不暴露 gRPC** | 仅对下游走 gRPC | --- ## 6. 待 P6+ 单独评估事项(不阻塞当前阶段) 以下事项不属于"中间过渡方案",属于"未来扩展",保持原状: | # | 事项 | 评估时机 | | --- | ------------------------------------------------------------------- | --------- | | X1 | ai 发布 edu.insight.ai.generated / feedback / workflow 3 个新 topic | P6+ | | X2 | admin-portal 8 个新 Kafka topic + events.proto message | P6 实施时 | | X3 | admin-portal 审计日志/安全事件归属(iam vs audit-service) | P6 | | X4 | ai 备课工作流迁移 Temporal | P6+ | | X5 | ai Prompt 模板 DB 存储迁移(已在 A6 裁决 P5 直接用 DB) | — | | X6 | admin-portal Grafana/Jaeger iframe 嵌入 | P6 | | X7 | teacher-portal localStorage → httpOnly Cookie 迁移 | P6 | | X8 | iam JWT 密钥迁移 Vault | P6 | | X9 | api-gateway per-服务实例熔断 | P6 | | X10 | schema registry 引入(events.proto 强制校验) | P6+ | --- ## 7. 各 AI 回写义务 各 AI 收到本文档后,必须执行以下回写: ### 7.1 回写内容 1. 删除自己 02-architecture-design.md 中所有"中间过渡方案"描述 2. 对齐本文档的最终方案 3. 更新 §8 风险与假设(移除已裁决项) ### 7.2 回写期限 | AI | 服务 | 回写期限 | 阻塞阶段 | | ---- | -------------- | ---------------------- | -------- | | ai01 | api-gateway | P2 启动前 | — | | ai02 | push-gateway | P5 启动前 | — | | ai03 | teacher-bff | **P2 启动前** | P2 | | ai04 | student-bff | P3 启动前 | — | | ai05 | parent-bff | P4 启动前 | — | | ai06 | iam | **P2 启动前** | P2 | | ai07 | classes | 已是黄金模板,无需回写 | — | | ai08 | core-edu | P3 启动前 | — | | ai09 | content | P4 启动前 | — | | ai10 | msg | P5 启动前 | — | | ai11 | data-ana | P4 启动前 | — | | ai12 | ai | P5 启动前 | — | | ai13 | teacher-portal | **P2 启动前** | P2 | | ai14 | student-portal | P3 启动前 | — | | ai15 | parent-portal | P4 启动前 | — | | ai16 | admin-portal | P6 启动前 | — | ### 7.3 coord 验收 各 AI 回写完成后,coord 将重新运行 §8 交叉审查,确认无"中间过渡方案"残留。 --- ## 8. 变更记录 | 日期 | 变更 | 决策者 | | ---------- | --------------------------------------------------------------------------------------------------------------------- | ----------------- | | 2026-07-09 | 初始创建,覆盖 G1-G17 / B1-B8 / I1-I8 / C1-C11 / N1-N5 / M1-M5 / D1-D5 / A1-A8 / P1-P6 / W1-W8 / F1-F12 共 80+ 项裁决 | coord(用户确认) |