Files
Edu/docs/architecture/issues/contracts/parent-bff_contract.md

233 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# parent-bff 对接契约
> 负责人ai05
> 关联:[matrix.md](../matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[02-architecture-design.md](../../../services/parent-bff/docs/02-architecture-design.md)
> 修订2026-07-10 ai05 复审修正仲裁引用、proto 包名、契约缺口标注)
---
## §0 契约基线说明
| 维度 | 内容 |
| --- | --- |
| proto 包名前缀 | 所有 proto 包名统一为 `next_edu_cloud.<domain>.v1`(如 `next_edu_cloud.iam.v1`),引用 proto message 时须带完整包名 |
| GraphQL schema 文件 | `packages/shared-ts/contracts/graphql/parent-bff.graphql`SDL-first 集中管理,对齐 coord ARB-001 模式) |
| 错误码前缀 | `BFF_PARENT_`C1 仲裁BFF 在前) |
| 端口 | HTTP 3010不暴露 gRPCC2 仲裁) |
| API 风格 | GraphQL YogaU3 仲裁P4 直接 GraphQL不走 REST 过渡) |
| 权限校验 | BFF 豁免 @RequirePermissionU4 仲裁),仅校验 x-user-id + ChildGuard 越权防御 |
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口
无对外 gRPC。parent-bff 是 GraphQL 聚合层,对上游仅暴露 HTTP/GraphQLC2 仲裁)。
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 | 阶段 |
| --- | --- | --- | --- | --- |
| POST | /graphql | 家长 BFF GraphQL 端点U3 仲裁) | JWT 必需 + parent 角色 | P4 |
| GET | /graphql | GraphQL Playground仅开发环境 | 开发环境公开 | P4 |
| GET | /healthz | 健康检查liveness直接返回 ok | 公开 | P4 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性02 §9 #7 | 公开 | P4 |
| GET | /metrics | Prometheus 指标parent_bff_* | 公开 | P4 |
> 网关路径:`/api/v1/parent/*` → api-gateway 剥离 `/api/v1` 后代理到 parent-bff:3010
> **不实现 REST 业务端点**02 §4.1,仅保留 /healthz /readyz /metrics 基础端点)
### 1.3 GraphQL schema
**schema 文件**`packages/shared-ts/contracts/graphql/parent-bff.graphql`SDL-first
**完整 schema 定义**:见 [02-architecture-design.md §4.2](../../../services/parent-bff/docs/02-architecture-design.md) GraphQL Schema 完整定义
核心 Query / Mutation 域(按阶段分级):
| 类型 | 字段 | 聚合下游 | ChildGuard | 阶段 |
| --- | --- | --- | --- | --- |
| Query | dashboard | iam + core-edu | 否(聚合所有孩子) | P4 |
| Query | viewports | iam | 否 | P4 |
| Query | me | iam | 否 | P4 |
| Query | children | iam | 否 | P4 |
| Query | child | iam + core-edu | 是 | P4 |
| Query | childGrades | core-edu | 是 | P4 |
| Query | childHomework | core-edu | 是 | P4 |
| Query | childExams | core-edu | 是 | P4 |
| Query | childAnalytics | data-ana | 是 | P4 |
| Query | notifications | msg | 否 | P5 |
| Query | notificationPreferences | msg | 否 | P5 |
| Mutation | selectChild | BFF 内部审计) | 是 | P4 |
| Mutation | markNotificationRead | msg | 否 | P5 |
| Mutation | updateNotificationPreferences | msg | 否 | P5 |
**统一响应信封**GraphQL 规范data/errors错误扩展字段携带 `extensions.code = "BFF_PARENT_*"`02 §4.5
### 1.4 Kafka 事件发布
无。parent-bff 不发布领域事件BFF 聚合层无业务状态变更02 §5.6)。
### 1.5 错误码前缀
`BFF_PARENT_`C1 仲裁BFF 在前;非 PARENT_BFF_
错误码清单(完整见 02 §6.2
| 错误码 | HTTP | 触发条件 |
| --- | --- | --- |
| BFF_PARENT_VALIDATION_ERROR | 400 | Zod / GraphQL input 校验失败 |
| BFF_PARENT_UNAUTHORIZED | 401 | 缺失 x-user-id 头 |
| BFF_PARENT_CHILD_NOT_BOUND | 403 | ChildGuard 拦截childId 不在家长绑定列表 |
| BFF_PARENT_NOT_FOUND | 404 | 资源不存在 |
| BFF_PARENT_BAD_GATEWAY | 502 | 下游服务返回非 ok 或 gRPC rejected |
| BFF_PARENT_GATEWAY_TIMEOUT | 504 | 下游调用超时 |
| BFF_PARENT_SERVICE_UNAVAILABLE | 503 | 熔断器开启P6 |
| BFF_PARENT_INTERNAL_ERROR | 500 | 未捕获异常 |
**下游错误透传**C3 仲裁core-edu 统一 CORE_EDU_*
| 下游服务 | 错误码前缀 | 示例 |
| --- | --- | --- |
| iam | IAM_ | IAM_USER_NOT_FOUND |
| core-edu | CORE_EDU_ | CORE_EDU_GRADE_NOT_FOUND |
| data-ana | DATA_ANA_ | DATA_ANA_ANALYTICS_NOT_READY |
| msg | MSG_ | MSG_NOTIFICATION_NOT_FOUND |
---
## §2 我消费什么(依赖上游)
### 2.1 gRPC 调用(同步)
> proto 包名均为 `next_edu_cloud.<domain>.v1`
> **状态标注**:✅ 已有 = proto 已定义;❌ 待补 = proto 缺失;⚠️ 待仲裁 = ai05 提请 coord 仲裁中
| 被调用方 | Service.RPC | proto message 包名 | 用途 | mock 策略 | 状态 |
| --- | --- | --- | --- | --- | --- |
| iam (ai06) | IamService.GetUserInfo | next_edu_cloud.iam.v1.UserInfo | 获取当前家长信息 | 返回固定 UserInfoparent 角色) | ✅ 已有 |
| iam (ai06) | IamService.GetViewports | next_edu_cloud.iam.v1.Viewport[] | 家长导航菜单 | 返回固定视口列表 | ❌ 待 ai06 补coord-cross-review #1 |
| iam (ai06) | IamService.GetEffectivePermissions | next_edu_cloud.iam.v1.EffectivePermissions | 权限校验 | 返回家长权限集 | ❌ 待 ai06 补 |
| iam (ai06) | **IamService.GetChildrenByParent** | next_edu_cloud.iam.v1.Child[] | 查询关联孩子列表(**P0 核心依赖** | 返回固定 2 个 ChildInfo | ❌ 待 ai06 补(**I6 裁决**P0 阻塞) |
| core-edu (ai08) | GradeService.ListGradesByStudent | next_edu_cloud.core_edu.v1.Grade[] | 孩子成绩 | 返回固定 5 个 Grade | ✅ 已有 |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | next_edu_cloud.core_edu.v1.Homework[] | 孩子作业 | 返回固定 3 个 Homework | ✅ 已有 |
| core-edu (ai08) | ExamService.ListExamsByClass | next_edu_cloud.core_edu.v1.Exam[] | 孩子考试 | 返回固定 3 个 Exam | ✅ 已有 |
| core-edu (ai08) | **ClassService.GetClass** | next_edu_cloud.core_edu.v1.Class | 孩子班级信息 | 返回固定 ClassInfo | ❌ 待补(**ISSUE-008**proto 缺 ClassService待 coord 仲裁归属) |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | next_edu_cloud.core_edu.v1.Attendance[] | 孩子考勤P5+ | 返回固定 10 条 | ❌ 待 ai08 补coord-cross-review #5P3 补全) |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | next_edu_cloud.analytics.v1.StudentWeakness | 孩子薄弱点 | 返回固定 3 个 weak_points | ✅ 已有 |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | next_edu_cloud.analytics.v1.LearningTrend | 孩子学习趋势 | 返回固定趋势数据 | ✅ 已有 |
| data-ana (ai11) | AnalyticsService.GetClassPerformance | next_edu_cloud.analytics.v1.ClassPerformance | 班级学情对比 | 返回固定班级数据 | ✅ 已有 |
| data-ana (ai11) | **AnalyticsService.GetParentDashboard** | — | 家长仪表盘聚合(多子女防 N+1 | 返回固定仪表盘 | ⚠️ 待仲裁02 §14 #3ai05 提请proto 未定义) |
| msg (ai10) | NotificationService.ListNotifications | next_edu_cloud.msg.v1.Notification[] | 家长通知 | 返回固定 10 条通知 | ✅ 已有 |
| msg (ai10) | NotificationService.MarkAsRead | next_edu_cloud.msg.v1.Empty | 标记已读 | 返回 success | ✅ 已有 |
| msg (ai10) | **NotificationPreferenceService.*** | — | 通知偏好配置 | 返回固定偏好 | ❌ 待 ai10 补proto + 实现均缺失P5 |
**类型映射注意**ISSUE-008
- `core_edu.v1.Grade.score``string` 类型GraphQL `Grade.score``Float!`BFF response-mapper 需做 `Number.parseFloat(score)` 转换,转换失败抛 BFF_PARENT_BAD_GATEWAY
- `msg.v1.Notification.is_read` 映射为 GraphQL `Notification.read`(字段名重命名)
- `msg.v1.Notification``child_id` 字段ISSUE-007GraphQL `Notification.childId` 暂从 notification.type+content 解析或置 null待 ai10 补 proto 字段
### 2.2 Kafka 事件订阅异步P5 可选)
> P4 阶段不订阅事件02 §5.1。P5 阶段可选订阅以下 topic 用于实时推送 + 缓存失效02 §5.2)。
| Topic | 事件 | 发布方 | 消费动作 | 幂等性 | 阶段 |
| --- | --- | --- | --- | --- | --- |
| edu.notification.sent | 通知发送 | msg | 推送给家长push-gateway HTTP | event_id SETNX | P5 |
| edu.notification.read | 通知已读 | msg | 失效 bff:parent:notifications:* | event_id SETNX | P5 |
| edu.notification.recalled | 通知撤回 | msg | 失效通知缓存 + 推送撤回 | event_id SETNX | P5 |
| edu.notification.failed | 通知失败 | msg | 记录日志 + 告警 | event_id SETNX | P5 |
| edu.teaching.grade.recorded | 成绩录入 | core-edu | 失效 bff:parent:grades:{childId} + 推送 | event_id SETNX | P5 |
| edu.teaching.homework.graded | 作业批改 | core-edu | 失效 bff:parent:homework:{childId} + 推送 | event_id SETNX | P5 |
| edu.teaching.exam.published | 考试发布 | core-edu | 失效 bff:parent:exams:{childId} + 推送 | event_id SETNX | P5 |
**消费者组**`parent-bff-event-subscriber`02 §5.5
**DLQ**`edu.parent-bff.dlq`
**提交策略**manual commit
### 2.3 HTTP 调用(非 gRPC
| 被调用方 | Method | Path | 用途 | 认证 | 阶段 |
| --- | --- | --- | --- | --- | --- |
| push-gateway (ai02) | POST | /internal/push | 推送给在线家长(**U2 仲裁**push-gateway 豁免 gRPC | X-Internal-Key | P5 |
> push-gateway HTTP 调用在 P5 阶段启用P4 不涉及。
---
## §3 就绪信号
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— **核心依赖 GetChildrenByParentI6 裁决P0 阻塞)**
- [ ] iam 补 GetViewports / GetEffectivePermissions RPCcoord-cross-review #1
- [ ] iam_student_guardians 表已建立I6 裁决)
- [ ] core-edu gRPC 50053 启用ai08
- [ ] core-edu ClassService.GetClass proto 补全ISSUE-008 待仲裁)
- [ ] data-ana gRPC 50055 启用ai11
- [ ] msg gRPC 50056 启用ai10—— P5
- [ ] msg.proto Notification 补 child_id 字段ISSUE-007 待仲裁)—— P5
- [ ] msg NotificationPreferenceService 补全 —— P5
- [ ] push-gateway /internal/push 启用ai02—— P5
- [ ] api-gateway /parent 路由注册ai01
- [ ] Kafka topic 已创建C5 仲裁)—— P5
- [ ] Redis 已部署且网络可达
- [ ] buf.gen.yaml 补 gRPC TS 插件coord
### 3.2 我的就绪标志(供下游消费)
- [ ] parent-bff GraphQL :3010 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 4 个下游 gRPC 连通性检查iam + core-edu + data-ana + Redis02 §9 #7
- [ ] GraphQL schema 可内省POST /graphql 返回 schema
- [ ] 核心 Query 可执行dashboard / children / childGrades / childAnalytics
- [ ] 核心 Mutation 可执行selectChildP4/ markNotificationReadP5
- [ ] DataScope=CHILDREN 校验生效家长只能查自己孩子的数据ChildGuard 基于 iam.GetChildrenByParent 返回的列表校验)
- [ ] /metrics 暴露 parent_bff_* 指标
---
## §4 Mock 策略
### 4.1 我提供的 mock供下游 parent-portal ai15
在 parent-bff 真实就绪前,为 parent-portal 提供以下 mock
- **GraphQL mock**:使用 MSW 拦截 POST /graphql 或 Apollo Server mockProviders
- `dashboard` 返回固定家长id="parent-001", name="王家长", roles=["parent"]+ 2 个孩子 + unreadNotifications=3
- `children` 返回固定 2 个孩子id="student-001" 李同学 + id="student-002" 李妹妹)
- `childGrades` 返回固定 5 个成绩(含 score 字段Float 类型)
- `childAnalytics` 返回固定学情child_avg_score=85.0, class_rank=5
- `childHomework` 返回固定 3 个作业
- `childExams` 返回固定 3 个考试
- `notifications`P5返回固定 10 条通知(含 childId 字段)
### 4.2 我消费的 mock上游未就绪时
在真实上游就绪前parent-bff 使用以下 mock详见 §2.1 mock 策略列):
- **iam mock**:固定 UserInfo + 家长权限 + 固定视口 + 固定 2 个 ChildInfo家长-学生关联核心数据)
- **core-edu mock**:固定孩子成绩/作业/考试/班级信息
- **data-ana mock**:固定孩子薄弱点/趋势/班级对比
- **msg mock**P5固定通知列表含 childId+ MarkAsRead success + 固定偏好配置
- **push-gateway mock**P5fetch mock 返回 success
- **Redis**Testcontainers 真实 Redis 实例(不用 mock
- **Kafka**P5kafkajs mock + jest.mock
> **关键约束**iam.GetChildrenByParent 的 mock 必须返回与 core-edu mock 数据一致的 student_idstudent-001 + student-002否则 ChildGuard 越权校验会失败。parent-bff 启动时校验 myChildren 返回的 user_id 与下游查询的 student_id 一致性。
---
## §5 跨模块契约冲突跟踪
> 以下为 ai05 复审发现的跨模块契约问题,详见 [objections/parent-bff_issue.md](../objections/parent-bff_issue.md)
| ISSUE | 问题 | 影响 | 状态 |
| --- | --- | --- | --- |
| ISSUE-003 | contract.md 仲裁引用编号错误I3 → I6 | 引用勘误,已修正本文档 | 待 coord 确认 |
| ISSUE-004 | 004 §4 + matrix.md §1 未同步 C6 仲裁(缺 DataAna + Msg | 新 AI 误判依赖 | 待 coord 仲裁 |
| ISSUE-005 | parent-portal 01 文档仍按 REST 消费 parent-bff | 跨模块契约冲突 | 待 coord 仲裁 |
| ISSUE-006 | proto 包名引用缺 next_edu_cloud 前缀 | gRPC 代码生成错误,已修正本文档 | 待 coord 确认 |
| ISSUE-007 | msg.proto Notification 缺 child_id 字段 | 按孩子过滤通知失效 | 待 coord 仲裁 |
| ISSUE-008 | core_edu.proto 缺 ClassService + Grade.score 类型不一致 | 班级信息查询 + 类型转换 | 待 coord 仲裁 |