# parent-bff 工作排期

> 负责人：ai05
> 关联：[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-bff_contract.md](../contracts/parent-bff_contract.md)、[02-architecture-design.md](../../../services/parent-bff/docs/02-architecture-design.md)
> 模式：全并行（各 AI 一口气完成 P2-P6 全部代码，开发期间用 mock，最后统一集成测试）

---

## §1 总览

parent-bff 为家长端提供 GraphQL 聚合 API（端口 3010），覆盖 Dashboard、多子女切换、成绩趋势、学情诊断、通知偏好等场景。

**全阶段目标**：

| 阶段 | 交付核心 | 依赖 |
| --- | --- | --- |
| P4 MVP | GraphQL Yoga + DataLoader + 多子女切换 + ChildGuard + 并行 gRPC 聚合 + Redis 缓存 + /readyz 下游探针 | iam.GetChildrenByParent（I6 裁决）+ core-edu gRPC + data-ana gRPC |
| P5 通知接入 | msg gRPC + push-gateway HTTP + Kafka consumer 缓存失效 + 通知偏好过滤 | msg gRPC 50056 + push-gateway /internal/push |
| P6 硬化 | 熔断器 opossum + HPA + SLO 监控 + 灰度发布 | — |

**关键路径**：批次 0（coord 补 proto）→ 批次 1（iam gRPC + GetChildrenByParent）→ 批次 2（core-edu gRPC）→ **批次 3（parent-bff P4 MVP）** → 批次 4（msg P5）→ parent-bff P5 接入

**P0 阻塞项**（详见 [objections/parent-bff_issue.md](../objections/parent-bff_issue.md) ISSUE-008/007）：

- iam.GetChildrenByParent RPC + iam_student_guardians 表（I6 裁决，ai06 负责）
- core-edu ClassService.GetClass proto 缺失（ISSUE-008，待 coord 仲裁归属）
- msg.proto Notification 缺 child_id 字段（ISSUE-007，P5 阶段阻塞）

---

## §2 全阶段甘特图（P2-P6）

```mermaid
gantt
    title ai05 parent-bff 全阶段排期（全并行 + mock）
    dateFormat YYYY-MM-DD
    axisFormat %m-%d

    section P4 MVP 核心
    P4.1 骨架搭建(env+main+health) :crit, a5a, 2026-07-31, 2d
    P4.2 GraphQL Yoga+schema第一版 :crit, a5b, after a5a, 3d
    P4.3 DownstreamClient抽象+gRPC mock :crit, a5c, after a5a, 2d
    P4.4 ChildGuard+DataLoader实现 :crit, a5d, after a5b, 3d
    P4.5 Dashboard/children/grades Resolver :a5e, after a5d, 2d
    P4.6 Redis缓存层+ Orchestrator降级 :a5f, after a5e, 2d
    P4.7 /readyz下游探针+可观测三支柱 :a5g, after a5f, 1d
    P4.8 单元+集成测试(≥80%覆盖) :a5h, after a5g, 2d

    section P5 通知接入
    P5.1 msg gRPC client接入 :b5a, after a5h, 2d
    P5.2 Notification Resolver+偏好配置 :b5b, after b5a, 2d
    P5.3 push-gateway HTTP /internal/push :b5c, after b5a, 1d
    P5.4 Kafka consumer订阅+缓存失效 :b5d, after b5c, 3d
    P5.5 通知偏好过滤逻辑 :b5e, after b5d, 1d

    section P6 硬化
    P6.1 opossum熔断器per-service :c5a, after b5e, 2d
    P6.2 HPA+podAntiAffinity :c5b, after c5a, 1d
    P6.3 SLO告警规则+灰度发布 :c5c, after c5b, 2d
```

> **说明**：以上日期为 coord 总排期推算（批次 3 P4 在批次 2 P3 完成后启动）。全并行模式下，P4/P5/P6 代码一口气完成，上游未就绪时用 mock，最后统一集成测试。

---

## §3 详细任务

### P4.1：骨架搭建（env + main + health）

- **负责人**：ai05
- **依赖**：无（克隆 teacher-bff 骨架）
- **交付物**：
  - `services/parent-bff/package.json`（name=@edu/parent-bff）
  - `src/config/env.ts`（Zod 校验，见 02 §12.1 完整配置项）
  - `src/main.ts`（启动 + /metrics + SIGTERM 优雅关闭）
  - `src/app.module.ts`
  - `src/shared/health/health.controller.ts`（/healthz 直接 ok）
  - `src/shared/observability/{logger,metrics,tracer}.ts`（service=parent-bff）
  - `src/shared/errors/{application-error,global-error.filter}.ts`（BFF_PARENT_ 前缀）
  - `Dockerfile`（多阶段，EXPOSE 3010）
  - `tsconfig.json`（NodeNext + ESM .js 后缀）
- **验收标准**：`pnpm run typecheck` + `pnpm run lint` 零错误；`docker build` 通过；本地启动 /healthz 返回 200

### P4.2：GraphQL Yoga + schema 第一版

- **负责人**：ai05
- **依赖**：P4.1
- **交付物**：
  - `src/entry/graphql.controller.ts`（POST /graphql + GET /graphql playground 仅 dev）
  - `src/entry/context.middleware.ts`（解析 x-user-id/x-user-roles/x-request-id 注入 GraphQL context）
  - `src/graphql/schema.ts`（typeDefs + resolvers，见 02 §4.2 GraphQL schema）
  - `src/graphql/types/`（parent/child/grade/homework/exam/analytics/notification.type.ts）
  - GraphQL 复杂度限制（depth ≤ 7，cost ≤ 1000，02 §9 #5）
  - `packages/shared-ts/contracts/graphql/parent-bff.graphql`（SDL-first 集中管理，对齐 coord ARB-001 模式）
- **验收标准**：POST /graphql 可内省 schema；深度超 7 的查询被拒；cost 超 1000 被拒

### P4.3：DownstreamClient 抽象 + gRPC mock

- **负责人**：ai05
- **依赖**：P4.1 + ai03 DownstreamClient 抽象模式（teacher-bff 参考）
- **交付物**：
  - `src/clients/grpc/grpc.factory.ts`（gRPC client 创建 + interceptor：trace/metrics/retry）
  - `src/clients/iam.client.ts`（IamClient interface + gRPC impl + mock impl）
  - `src/clients/core-edu.client.ts`（CoreEduClient interface + gRPC impl + mock impl）
  - `src/clients/data-ana.client.ts`（DataAnaClient interface + gRPC impl + mock impl）
  - mock 数据：固定 2 个孩子（student-001 李同学 + student-002 李妹妹）+ 固定成绩/作业/考试/学情
- **验收标准**：DEV_MODE=true 时走 mock impl，返回固定数据；mock 数据 student_id 与 core-edu mock 一致（见 contract.md §4.2）

### P4.4：ChildGuard + DataLoader 实现

- **负责人**：ai05
- **依赖**：P4.2 + P4.3
- **交付物**：
  - `src/aggregation/child-guard.ts`（DataScope=CHILDREN 越权校验，见 02 §2.3）
    - 30s TTL Redis 缓存绑定列表（ISSUE-002 修正：§3.1.1 同步为 30s）
    - singleflight 模式防缓存击穿（02 §14 #5）
    - 越权时抛 BFF_PARENT_CHILD_NOT_BOUND(403)
  - `src/dataloader/dataloader.module.ts`（per-request 实例注册器）
  - `src/dataloader/{children,grade,homework,exam}.dataloader.ts`（批量去重 N+1 防御）
- **验收标准**：
  - childId ∉ 绑定列表时抛 403
  - 30s 内第二次查询不调 iam.GetChildrenByParent
  - 并发 100 请求只调 iam 1 次（singleflight）
  - DataLoader 同 parentId 多次调用合并为 1 次 gRPC

### P4.5：Dashboard / children / grades Resolver

- **负责人**：ai05
- **依赖**：P4.4
- **交付物**：
  - `src/graphql/resolvers/dashboard.resolver.ts`（聚合 iam.GetUserInfo + iam.GetChildrenByParent + core-edu.ListGradesByStudent 并行）
  - `src/graphql/resolvers/child.resolver.ts`（childQuery + ChildGuard 校验 + 延迟加载 grades/homework/exams/analytics）
  - `src/graphql/resolvers/select-child.resolver.ts`（mutation，仅审计日志，不持久化）
  - `src/graphql/resolvers/grade.resolver.ts`（childGrades query，含分页）
- **验收标准**：
  - dashboard Query 返回 parent + children + unreadNotifications
  - child(childId) 对未绑定 childId 返回 403
  - selectChild mutation 记录审计日志（traceId + parentId + childId + timestamp）

### P4.6：Redis 缓存层 + Orchestrator 降级

- **负责人**：ai05
- **依赖**：P4.5
- **交付物**：
  - `src/shared/cache/redis.client.ts`（ioredis 连接）
  - `src/shared/cache/cache-key.builder.ts`（bff:parent:* 前缀，见 02 §3.1.1）
  - `src/aggregation/orchestrator.ts`（Promise.allSettled 并行 + 降级标记 partial）
  - `src/aggregation/response-mapper.ts`（proto → GraphQL type，含 Grade.score string→Float 转换，ISSUE-008）
  - `src/aggregation/fallback-strategy.ts`（下游失败时返回缓存陈旧数据或 null 字段）
- **验收标准**：
  - dashboard 聚合结果缓存 15s，第二次命中不调下游
  - data-ana 失败时返回 dashboard.degraded=true，其他字段正常
  - Redis 不可用时降级为内存 LRU

### P4.7：/readyz 下游探针 + 可观测三支柱

- **负责人**：ai05
- **依赖**：P4.6
- **交付物**：
  - `src/shared/health/health.controller.ts` 补 /readyz 下游探针（02 §9 #7 ai05 调整）
    - 探针：iam gRPC + core-edu gRPC + data-ana gRPC + Redis，超时 1s/服务
    - 任一失败返回 503 + degraded=true
  - metrics 指标全量落地（02 §6.4 表格 11 项指标）
  - tracer auto-instrumentations（http/nestjs/express/ioredis/grpc-js）
  - logger 字段对齐（parentId/childId/operation/traceId）
- **验收标准**：
  - /readyz 返回 4 项依赖状态
  - /metrics 暴露 parent_bff_* 指标
  - Jaeger 可看到 dashboard 请求完整 span 链

### P4.8：单元 + 集成测试（≥80% 覆盖）

- **负责人**：ai05
- **依赖**：P4.7
- **交付物**：
  - `test/unit/child-guard.test.ts`（越权拦截 + 缓存命中 + singleflight）
  - `test/unit/orchestrator.test.ts`（并行编排 + 部分失败降级）
  - `test/unit/dataloader.test.ts`（批量去重）
  - `test/unit/graphql-complexity.test.ts`（depth/cost 限制）
  - `test/integration/dashboard.test.ts`（3 子女 × 3 下游并行，Redis Testcontainers）
  - `test/integration/readyz.test.ts`（iam 故障时 503）
  - `vitest.config.ts`（覆盖率阈值 80%）
- **验收标准**：覆盖率 ≥ 80%；10 项关键用例（02 §11.2）全部通过

### P5.1：msg gRPC client 接入

- **负责人**：ai05
- **依赖**：msg gRPC 50056 就绪（ai10）或 mock
- **交付物**：
  - `src/clients/msg.client.ts`（MsgClient interface + gRPC impl + mock impl）
  - env.ts 启用 MsgServiceUrl / MsgGrpcTarget
- **验收标准**：mock 模式下 NotificationService.ListNotifications / MarkAsRead 可调

### P5.2：Notification Resolver + 偏好配置

- **负责人**：ai05
- **依赖**：P5.1 + msg.proto 补 child_id 字段（ISSUE-007 仲裁结果）
- **交付物**：
  - `src/graphql/resolvers/notification.resolver.ts`（notifications query + markNotificationRead mutation）
  - `src/graphql/resolvers/notification-preference.resolver.ts`（notificationPreferences query + updateNotificationPreferences mutation）
  - `src/parent/dto/parent-inputs.dto.ts`（UpdateNotificationPreferencesSchema Zod 校验）
- **验收标准**：notifications Query 返回家长通知列表（含 childId）；偏好更新后缓存失效

### P5.3：push-gateway HTTP /internal/push 接入

- **负责人**：ai05
- **依赖**：push-gateway /internal/push 就绪（ai02）或 mock
- **交付物**：
  - `src/clients/http/push-http.client.ts`（HTTP POST /internal/push，U2 仲裁）
  - env.ts 启用 PushGatewayUrl
- **验收标准**：mock 模式下 pushViaHttp 返回 success

### P5.4：Kafka consumer 订阅 + 缓存失效

- **负责人**：ai05
- **依赖**：Kafka topic 已创建（C5 仲裁：edu.notification.sent/read/recalled/failed + edu.teaching.grade.recorded/homework.graded/exam.published）
- **交付物**：
  - `src/shared/kafka/kafka.consumer.ts`（consumer group: parent-bff-event-subscriber）
  - `src/shared/kafka/handlers/notification-push.handler.ts`（偏好过滤 + push-gateway 推送）
  - `src/shared/kafka/handlers/cache-invalidation.handler.ts`（成绩/作业/考试事件失效对应缓存）
  - 幂等性：Redis SETNX event_id 去重
  - DLQ：edu.parent-bff.dlq
- **验收标准**：
  - 收到 edu.teaching.grade.recorded 后 bff:parent:grades:{childId} 缓存失效
  - 家长关闭"成绩推送"偏好时，该家长不收到推送
  - 重复 event_id 不重复处理

### P5.5：通知偏好过滤逻辑

- **负责人**：ai05
- **依赖**：P5.4
- **交付物**：通知偏好过滤逻辑集成到 notification-push.handler（02 §5.4）
  - 拉取家长 NotificationPreferences（Redis 缓存 300s）
  - 按 eventTypeMap 映射事件类型 → 偏好开关
  - 取 prefs.channels 与 event.channels 交集
- **验收标准**：偏好开关为 false 时不推送；channels 无交集时不推送

### P6.1：opossum 熔断器 per-service

- **负责人**：ai05
- **依赖**：P5 完成
- **交付物**：
  - `src/clients/grpc/circuit-breaker.ts`（opossum，per-downstream-service 独立 circuit：iam/core-edu/data-ana/msg）
  - 熔断开启时抛 BFF_PARENT_SERVICE_UNAVAILABLE(503)
  - metrics: parent_bff_circuit_state Gauge
- **验收标准**：下游连续失败 5 次熔断开启；30s 后半开探测

### P6.2：HPA + podAntiAffinity

- **负责人**：ai05
- **依赖**：P6.1
- **交付物**：`infra/k8s/helm/parent-bff/` Chart（对齐 004 §1.2 端口）
  - HPA 2-10 副本（CPU 70% / 内存 80% 触发）
  - podAntiAffinity 跨节点分布
  - values-dev.yaml / values-staging.yaml / values-prod.yaml
- **验收标准**：helm template 通过；HPA 可根据负载扩缩

### P6.3：SLO 告警规则 + 灰度发布

- **负责人**：ai05
- **依赖**：P6.2
- **交付物**：
  - `infra/prometheus/rules.yml` 追加 parent-bff 告警规则（P95 > 200ms / 错误率 > 0.1% / 可用性 < 99.9%）
  - `infra/grafana/dashboards/parent-bff.json` 面板
  - 灰度发布：按 parentId hash 路由流量百分比（K8s Service + Istio/Envoy weight）
- **验收标准**：Prometheus 告警规则 lint 通过；Grafana 面板可展示 parent-bff 指标

---

## §4 依赖与就绪信号

### 4.1 我依赖的上游就绪标志

| 上游 | 就绪信号 | 责任方 | 阻塞阶段 | 状态 |
| --- | --- | --- | --- | --- |
| iam gRPC 50052 + GetChildrenByParent RPC | HealthService.Check = SERVING + GetChildrenByParent 可调 | ai06 | P4（P0 阻塞） | ⏳ |
| iam_student_guardians 表 | 表已建 + Repository 查询方法可用 | ai06 | P4（P0 阻塞） | ⏳ |
| core-edu gRPC 50053 | HealthService.Check = SERVING + Exam/Homework/Grade Service 可调 | ai08 | P4 | ⏳ |
| core-edu ClassService.GetClass | proto 补全 + RPC 实现（ISSUE-008 待仲裁） | ai08 或 classes | P4 | ⏳ |
| data-ana gRPC 50055 | HealthService.Check = SERVING + AnalyticsService 可调 | ai11 | P4 | ⏳ |
| msg gRPC 50056 | HealthService.Check = SERVING + NotificationService 可调 | ai10 | P5 | ⏳ |
| msg.proto Notification.child_id | proto 字段补全（ISSUE-007 待仲裁） | ai10 | P5 | ⏳ |
| push-gateway /internal/push | HTTP 端点可用 | ai02 | P5 | ⏳ |
| api-gateway /parent 路由 | `/api/v1/parent/*` → parent-bff:3010 代理生效 | ai01 | P4 | ⏳ |
| Kafka topic 已创建 | edu.notification.sent/read/recalled/failed + edu.teaching.* | coord/infra | P5 | ⏳ |
| Redis 已部署 | redis://edu-redis:6379 可达 | coord/infra | P4 | ⏳ |
| buf.gen.yaml gRPC 插件 | TS gRPC client 代码生成可用 | coord | P4 | ⏳ |
| ai03 DownstreamClient 抽象 | teacher-bff clients/ 抽象层可参考 | ai03 | P4 | ⏳ |

### 4.2 我的就绪信号（供下游消费）

| 信号 | 检查方式 | 消费方 |
| --- | --- | --- |
| parent-bff GraphQL :3010 启用 | GET /healthz 返回 200 | api-gateway / K8s |
| /readyz 返回 200（含 4 下游 gRPC 连通性） | GET /readyz 返回 200 | K8s readinessProbe |
| GraphQL schema 可内省 | POST /graphql 返回 schema | parent-portal（ai15） |
| 核心 Query 可执行 | dashboard / myChildren / childGrades / childAnalytics | parent-portal |
| 核心 Mutation 可执行 | selectChild / markNotificationRead（P5） | parent-portal |
| DataScope=CHILDREN 校验生效 | 家长查询未绑定 childId 返回 403 | 集成测试 |
| metrics 暴露 | GET /metrics 返回 parent_bff_* 指标 | Prometheus |

### 4.3 全并行 Mock 策略

> 开发期间上游未就绪时，parent-bff 使用 mock 完成全部 P4-P6 代码，最后统一集成测试。

| 下游 | Mock 方式 | 切换真实时机 |
| --- | --- | --- |
| iam gRPC | grpc-mock 拦截 + 固定 UserInfo（parent 角色）+ 固定 2 个 ChildInfo | iam 就绪信号 ✅ |
| core-edu gRPC | grpc-mock 拦截 + 固定成绩/作业/考试 | core-edu 就绪信号 ✅ |
| data-ana gRPC | grpc-mock 拦截 + 固定学情/趋势 | data-ana 就绪信号 ✅ |
| msg gRPC | grpc-mock 拦截 + 固定 10 条通知（含 childId） | msg 就绪信号 ✅ |
| push-gateway HTTP | fetch mock + 返回 success | push-gateway 就绪信号 ✅ |
| Redis | Testcontainers 真实 Redis 实例 | — |
| Kafka | kafkajs mock + jest.mock | Kafka topic 创建 ✅ |

**关键**：iam.GetChildrenByParent 的 mock 必须返回与 core-edu mock 数据一致的 student_id，否则 ChildGuard 越权校验会失败。

---

## §5 跨模块协作需求（需 coord 协调）

| # | 需求 | 涉及 AI | 阻塞阶段 | 协调内容 |
| --- | --- | --- | --- | --- |
| 1 | iam 补 GetChildrenByParent RPC + iam_student_guardians 表 | ai06 | P4（P0） | I6 裁决已定，ai06 P2.1 即补 |
| 2 | core-edu ClassService 归属仲裁 + proto 补全 | ai08 / classes | P4 | ISSUE-008 待 coord 仲裁 |
| 3 | msg.proto Notification 补 child_id 字段 | ai10 | P5 | ISSUE-007 待 coord 仲裁 |
| 4 | api-gateway 新增 /parent 路由 | ai01 | P4 | main.go + config.go 新增 ParentBffURL |
| 5 | 004 §4 依赖图同步 C6 仲裁（补 DataAna + Msg） | coord | P4 | ISSUE-004 |
| 6 | parent-portal 文档同步 GraphQL 决策 | ai15 | P4 | ISSUE-005 跨模块契约冲突 |
| 7 | buf.gen.yaml 补 gRPC TS 插件 | coord | P4 | 02 §7.3 #7 |
| 8 | docker-compose.deploy.yml 新增 parent-bff 服务 | coord | P4 | 端口 3010 + edu-net |
| 9 | full-stack-runbook 端口矩阵追加 3010 | coord | P4 | 02 §7.3 #5 |
| 10 | shared-ts/contracts/graphql/parent-bff.graphql 建库 | ai05 | P4 | SDL-first 集中管理 |
