Files
Edu/services/parent-bff/docs/02-architecture-design.md
SpecialX faaaf29f67 docs: ai 协作文档体系重构与多 ai 仲裁结果落地
1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md)

2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration)

3.各服务 01/02 文档补全

4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens)

5.Proto 契约补全

6.004 架构影响地图更新

7.端口分配表

8.设计规格文档
2026-07-10 12:58:22 +08:00

105 KiB
Raw Blame History

模块架构设计文档 — parent-bff

AI 标识ai05coord 代笔推断 → ai05 复审修订正式接手) 阶段:阶段 2模块架构设计· ai05 复审修订 coord 代笔日期2026-07-09 ai05 复审日期2026-07-09 深夜 状态ai05 已复审,补充 ai05 视角的长远架构演进设计,进入 coord 交叉审查 关联文档:

代笔与复审说明

  • coord 代笔阶段2026-07-09ai05 未交付 02 文档coord 基于 01-understanding.mdai04 初稿)+ teacher-bff/student-bff 模式 + 用户 4 项仲裁决策U1-U4+ coord 补充仲裁C1-C6推断生成 §0-§8 共 1114 行。文档头部 8 项仲裁决策为生成依据,本文档不再就这些项提请仲裁。
  • ai05 复审修订阶段2026-07-09 深夜):按 ai-allocation.md §3.2 真实分配ai05 = parent-bff正式接手保留 coord 推断版 §0-§8 原文不动,在文末追加 §9-§14 共 6 节 ai05 视角补全:(1) §9 ai05 review 结论(响应 coord §8.4 的 10 项待 review 决策);(2) §10 长远架构演进设计P5-P7+ 路线图 + 10 项长远预留点含多租户/合规/i18n/AI 助手/移动端/容灾);(3) §11 测试策略4 层金字塔 + 关键用例 + Mock 策略);(4) §12 完整配置项清单Zod 校验 + 环境变量分组);(5) §13 黄金模板对齐 checklist16 项自检);(6) §14 待 coord 仲裁的新决策清单8 项 ai05 提请)。
  • ai05 修订原则:不重写 coord 已完成的 §0-§8避免破坏 coord 推断的连贯性);新增 §9-§14 以"补全长远架构演进 + 响应 review 项"为边界;如与 coord 推断结论有冲突,在 §9 明确标注并由 coord 最终仲裁。

0. 文档导航与仲裁依据

0.1 已仲裁决策(用户 4 项 + coord 补充规则,本文档直接执行)

# 决策项 仲裁结论 依据
U1 parent-bff 文档归属 coord 推断生成ai05 未交付) 用户仲裁 1
U2 push-gateway 通信 push-gateway 豁免 gRPCmsg/student-bff 调用方改用 HTTP /internal/push 用户仲裁 2
U3 GraphQL 引入时机 GraphQL P2 立即引入(覆盖 ai04 建议,恢复 pending-features P2 原计划)→ parent-bff 在 P4 落地时直接用 GraphQL 用户仲裁 3
U4 BFF 权限校验 BFF 豁免 @RequirePermission(权限由 Gateway JWT + 下游服务负责BFF 仅校验 x-user-id 用户仲裁 4
C1 错误码前缀 BFF_PARENT_(对齐 004 §11.4BFF 在前;不是 PARENT_BFF_ coord 仲裁
C2 端口分配 HTTP 3010不暴露 gRPCBFF 对下游走 gRPC对上游仅 HTTP/GraphQL coord 仲裁
C3 下游错误码子前缀 core-edu 统一用 CORE_EDU_*(不细分 EXAMS_/HOMEWORK_/GRADES_与 teacher-portal 保持一致 coord 仲裁
C4 iam 端点路径 统一为 GET /iam/permissions/effective(不是 /iam/effective-permissions coord 仲裁
C5 Kafka topic 命名 统一为 edu.notification.sent/read/recalled/failed(废弃 edu.notification.events 抽象名) coord 仲裁
C6 依赖扩展 parent-bff → iam + core-edu + data-ana + msg与 student-bff 对齐004 §4 待 coord 同步更新) coord 仲裁

0.2 文档结构(对齐 ai-allocation §7 模板 8 章节)

章节 内容 关键性
§1 模块内部分层图Controller → Service → 下游 gRPC/HTTP 调用链) ★ 内部结构
§2 领域模型BFF 纯聚合层无聚合根ParentSession 值对象) ★ 边界
§3 数据模型(无 DBRedis 仅缓存聚合结果 5-30s方案 A 前端管理 childId ★ 无状态
§4 API 设计GraphQL Schema 优先Query/Mutation + 权限点 + DataLoader ★ 对前端契约
§5 事件设计P4 不订阅P5 可选订阅 edu.notification.* / edu.teaching.* ★ 实时推送
§6 横切关注点对齐清单(@RequirePermission 豁免、错误码 BFF_PARENT_*、三支柱) ★ 黄金模板
§7 与其他模块的交互点iam/core-edu/data-ana/msg被 parent-portal 调用) ★ 跨模块契约
§8 风险与假设P0 阻塞iam 缺家长-学生关联接口DataScope 越权校验) ★ 阻塞项

设计原则摘要BFF 本分只聚合不持状态、契约先行gRPC + proto、GraphQL 优先U3、DataScope=CHILDREN 强制隔离ChildGuard、无状态服务方案 A、可观测三支柱、黄金模板对齐 teacher-bff。


1. 模块内部分层图

1.1 目标态分层P4 终态GraphQL

graph TB
    subgraph Client["前端层"]
        PP[parent-portal<br/>Next.js MF Remote<br/>urql GraphQL Client]
    end

    subgraph Gateway["网关层"]
        AGW[api-gateway<br/>Go/Gin<br/>JWT RS256 校验 + 限流 + 熔断<br/>注入 x-user-id / x-user-roles / x-request-id]
    end

    subgraph ParentBFF["parent-bff本模块P4 终态"]
        direction TB
        GQL[GraphQL Yoga Endpoint<br/>POST /graphql<br/>+ GET /graphql]
        HEALTH[Health Controller<br/>/healthz /readyz /metrics]

        subgraph Middleware["中间件层"]
            CTX[Context Middleware<br/>解析 x-user-id / x-user-roles<br/>注入 GraphQL context]
            ERR[GlobalErrorFilter<br/>统一错误兜底 → ActionState 信封]
        end

        subgraph Aggregation["聚合编排层"]
            RES[GraphQL Resolvers<br/>按家长场景域组织]
            DL[DataLoader Registry<br/>per-request 实例<br/>批量去重 N+1]
            ORCH[Orchestrator<br/>Promise.allSettled + 降级]
            GUARD[ChildGuard<br/>DataScope=CHILDREN 越权校验]
            MAP[Response Mapper<br/>proto → GraphQL type]
        end

        subgraph Cache["缓存层"]
            RCLIENT[RedisClient<br/>ioredis]
            CKV[CacheKey Builder<br/>bff:parent:*]
        end

        subgraph Clients["下游 Client 抽象层"]
            IAM_C[IamClient<br/>gRPC adapter]
            CE_C[CoreEduClient<br/>gRPC adapter]
            DA_C[DataAnaClient<br/>gRPC adapter]
            MSG_C[MsgClient<br/>gRPC adapter]
        end

        subgraph Obs["可观测层"]
            LOG[pino Logger]
            MET[prom-client Metrics]
            TRC[OTel Tracer<br/>gRPC interceptor]
        end

        GQL --> CTX
        HEALTH --> CTX
        CTX --> RES
        RES --> GUARD
        RES --> DL
        RES --> ORCH
        RES --> RCLIENT
        DL --> Clients
        ORCH --> Clients
        Clients --> Obs
        RCLIENT --> CKV
        ERR -.-> GQL
        ERR -.-> HEALTH
    end

    subgraph Downstream["下游业务服务gRPC"]
        IAM[iam:50052<br/>含 GetChildrenByParent<br/>待 ai02 补]
        CE[core-edu:50053<br/>Exam/Homework/Grade Service]
        DA[data-ana:50055<br/>AnalyticsService]
        MSG[msg:50056<br/>NotificationService]
    end

    subgraph Infra["基础设施"]
        R[(Redis 7<br/>edu-redis:6379)]
        J[Jaeger<br/>OTLP]
        P[Prometheus<br/>:9090]
    end

    PP -->|GraphQL over HTTP| AGW
    AGW -->|HTTP + x-user-id/x-user-roles| GQL
    AGW --> HEALTH

    IAM_C --> IAM
    CE_C --> CE
    DA_C --> DA
    MSG_C --> MSG

    RCLIENT --> R
    LOG --> J
    MET --> P
    TRC --> J

1.2 调用链详解(同步聚合链 — Dashboard 场景)

sequenceDiagram
    participant PP as parent-portal
    participant GW as api-gateway
    participant C as ParentController (GraphQL)
    participant S as ParentService
    participant G as ChildGuard
    participant A as Aggregator
    participant Cache as Redis
    participant DC as DownstreamClient
    participant IAM as iam
    participant Core as core-edu

    PP->>GW: POST /api/v1/parent/graphql<br/>{ query: "dashboard { user children { id name lastGrade } }" }
    GW->>GW: RS256 公钥校验 + 提取 userId
    GW->>C: POST /parent/graphql<br/>x-user-id: u-parent-001<br/>x-request-id: r-xxx
    C->>C: extractUserId(req) → u-parent-001<br/>GraphQL parse + validate
    C->>S: dashboard(userId)
    S->>A: aggregateDashboard(u-parent-001)
    A->>Cache: GET bff:parent:dashboard:u-parent-001
    alt 缓存命中
        Cache-->>A: { data, cachedAt }
        A-->>S: data
    else 缓存未命中
        par 并行下游
            A->>DC: callIAM('GetChildrenByParent', u-parent-001)
            DC->>IAM: gRPC GetChildrenByParent(parentId)
            IAM-->>DC: { children: [child-1, child-2] }
        and
            A->>DC: callIAM('GetUserInfo', u-parent-001)
            DC->>IAM: gRPC GetUserInfo(userId)
            IAM-->>DC: { user, roles, dataScope: CHILDREN }
        end
        par 并行按 childId 拉成绩
            A->>DC: callCoreEdu('ListGradesByStudent', child-1)
            DC->>Core: gRPC ListGradesByStudent(studentId)
            Core-->>DC: { grades: [...] }
        and
            A->>DC: callCoreEdu('ListGradesByStudent', child-2)
            DC->>Core: gRPC ListGradesByStudent(studentId)
            Core-->>DC: { grades: [...] }
        end
        DC-->>A: 合并结果 + 部分失败标记
        A->>A: Mapper 裁剪 + 视口过滤
        A->>Cache: SET bff:parent:dashboard:u-parent-001 EX 15
        A-->>S: data
    end
    S-->>C: DashboardData
    C-->>GW: 200 { data: {...}, meta: { cachedAt, partial } }
    GW-->>PP: 200 GraphQL 响应

1.3 越权校验调用链DataScope=CHILDREN 防御)

越权校验时序parent-portal → api-gateway → ParentController → ChildGuard → iam.GetChildrenByParent → 若 childId 不在绑定列表则 throw BFF_PARENT_CHILD_NOT_BOUND403。详见 §2.3 代码示例。

1.4 目录结构(目标态)

services/parent-bff/src/
├─ config/env.ts                      # Zod 校验环境变量PORT=3010 + 下游 gRPC target
├─ entry/                             # 入口层
│  ├─ graphql.controller.ts           # GraphQL Yoga 端点POST /graphql
│  └─ context.middleware.ts           # 解析 x-user-id 注入 GraphQL context
├─ parent/                            # 家长场景域
│  ├─ parent.controller.ts            # REST 兼容端点(仅 /healthz /readyz /metrics
│  ├─ parent.service.ts               # 聚合 Service
│  └─ parent.schema.ts                # Zod 输入校验
├─ aggregation/                       # 聚合编排层
│  ├─ orchestrator.ts                 # Promise.allSettled + 降级
│  ├─ child-guard.ts                  # DataScope=CHILDREN 越权校验
│  ├─ response-mapper.ts              # proto → GraphQL type
│  └─ fallback-strategy.ts            # 降级策略
├─ dataloader/                        # DataLoaderper-request
│  ├─ dataloader.module.ts            # 注册器
│  ├─ children.dataloader.ts          # 按 parentId 批量取孩子列表
│  ├─ grade.dataloader.ts             # 按 studentId 批量取成绩
│  ├─ homework.dataloader.ts          # 按 classId 批量取作业
│  └─ exam.dataloader.ts              # 按 classId 批量取考试
├─ graphql/                           # GraphQL Schema
│  ├─ schema.ts                       # typeDefs + resolvers
│  ├─ types/                          # parent/child/grade/homework/exam/analytics/notification.type.ts
│  └─ resolvers/                      # dashboard/child/grade/homework/exam/analytics/notification.resolver.ts
├─ clients/                           # 下游 Client 抽象层
│  ├─ iam.client.ts / core-edu.client.ts / data-ana.client.ts / msg.client.ts  # gRPC adapter
│  ├─ grpc/                           # grpc.factory.ts + interceptors.tstrace/metrics/retry
│  └─ http/push-http.client.ts        # push-gateway 走 HTTPU2 仲裁)
├─ shared/
│  ├─ errors/                         # application-error.tsBFF_PARENT_*+ global-error.filter.ts
│  ├─ health/health.controller.ts     # /healthz + /readyzBFF 直接 ok
│  ├─ cache/                          # redis.client.ts + cache-key.builder.tsbff:parent:*
│  ├─ observability/                  # logger.tspino+ metrics.tsparent_bff_*+ tracer.tsOTel
│  └─ kafka/                          # kafka.consumer.ts + handlers/P5 可选)
├─ app.module.ts
└─ main.ts                            # 启动 + /metrics + SIGTERM

1.5 分层职责契约

职责 禁止
Entry GraphQL 端点 + 健康端点,解析请求,注入 context 业务逻辑、下游调用
Middleware 通用横切context 注入、错误兜底) 业务逻辑
Aggregation 编排多下游调用、聚合裁剪、ChildGuard 越权校验、降级 直接访问 DB、权限决策
Cache 缓存读写(仅聚合结果 5-30s 业务逻辑、会话状态存储
Clients 下游协议适配gRPC、interceptor 业务聚合逻辑
Observability 日志/指标/链路 业务逻辑

依赖方向Entry → Aggregation → Clients → DownstreamCache 横向被 Aggregation 调用Observability 横向被所有层调用。禁止反向依赖。


2. 领域模型

parent-bff 是 BFF 聚合层,不持有业务领域聚合根(不写 DB、不定义聚合根。本节定义 BFF 内部的"场景聚合视图"与"值对象"。

2.1 场景聚合视图清单

聚合视图 业务含义 主要下游服务 读写特性 DataScope
ParentDashboard 家长首页:个人信息 + 孩子列表 + 近期成绩 iam + core-edu 读 / 聚合 CHILDREN
ChildrenList 我的孩子列表(含切换上下文) iam CHILDREN
ChildGrades 指定孩子的成绩历史 core-edu CHILDREN
ChildHomework 指定孩子的作业列表 core-edu CHILDREN
ChildExams 指定孩子的考试列表 core-edu CHILDREN
ChildAnalytics 指定孩子的学情诊断 + 趋势 data-ana CHILDREN
ParentNotifications 家长通知列表 + 已读 msg 读 + 写(已读) SELF
NotificationPreferences 通知偏好配置 msg 读 + 写 SELF

2.2 ParentSession 值对象(多子女切换上下文)

重要:采用方案 A前端管理 childIdBFF 无状态。ParentSession 仅作为 GraphQL context 内的请求级值对象存在,不持久化到 Redis 会话。

// graphql/context.ts
export interface ParentSession {
  parentId: string; // 从 x-user-id 头解析
  roles: string[]; // 从 x-user-roles 头解析
  dataScope: "CHILDREN"; // 家长固定 CHILDREN004 §5.4
  requestedChildId?: string; // 从 GraphQL query 参数解析(前端传入)
  traceId: string; // 从 x-request-id 头解析
}

设计要点

  1. ParentSession 每次请求重建,不跨请求保留状态(符合 004 §12.1 无状态约束)
  2. requestedChildId 由前端在 GraphQL query 参数传入(方案 ABFF 不维护"当前选中孩子"
  3. POST /parent/children/:childId/selectselectChild mutation仅记录审计日志不持久化会话
  4. ChildGuard 在 Resolver 执行前校验 requestedChildId ∈ iam.GetChildrenByParent(parentId)

2.3 DataScope=CHILDREN 越权校验BFF 层强制)

BFF 不做权限决策U4 豁免 @RequirePermission),但做越权防御(与 student-bff 的 SELF 防御同理CHILDREN 更复杂):

// aggregation/child-guard.ts
async validateChildAccess(session: ParentSession, childId: string): Promise<void> {
  if (!session.requestedChildId || session.requestedChildId !== childId) {
    // 重新校验(防止 context 被篡改)
  }
  const children = await this.iamClient.getChildrenByParent(session.parentId);
  const isBound = children.some(c => c.id === childId);
  if (!isBound) {
    throw new ChildNotBoundError('BFF_PARENT_CHILD_NOT_BOUND', {
      parentId: session.parentId,
      requestedChildId: childId,
      boundChildren: children.map(c => c.id),
    });
  }
}

设计权衡BFF 层做 CHILDREN 越权校验是 P4 安全硬化项。即使下游 core-edu 也按 dataScope 过滤BFF 层先拦截可:

  1. 早失败,减少无效下游 gRPC 调用
  2. 提升审计能力(越权尝试在 BFF 层记录)
  3. 降低跨家庭数据泄露风险(家长 A 不能查家长 B 的孩子)
  4. 不依赖下游服务实现 DataScope=CHILDREN 语义core-edu 当前仅支持 SELF/CLASS/GRADE/SCHOOL

2.4 聚合视图间关系

graph LR
    Dashboard[ParentDashboard<br/>聚合视图]
    Dashboard --> Children[ChildrenList]
    Dashboard --> Grades[ChildGrades]
    Dashboard --> Notif[ParentNotifications]

    Children -.切换.-> SelectChild[selectChild Mutation<br/>仅审计日志]
    Children --> Grades
    Children --> Homework[ChildHomework]
    Children --> Exams[ChildExams]
    Children --> Analytics[ChildAnalytics]

    Grades -.数据流.-> Analytics
    Notif -.偏好过滤.-> Preferences[NotificationPreferences]

3. 数据模型(无 DB缓存 + DTO

parent-bff 无数据库。Redis 仅缓存聚合结果 5-30s方案 ABFF 无状态,不存会话)。本节定义 Redis 缓存 schema 与传输 DTO。

3.1 Redis 缓存 Schema

3.1.1 Key 命名规范

用途 Key 模式 TTL 失效策略
家长 Dashboard 聚合 bff:parent:dashboard:{parentId} 15s 短 TTL + 成绩发布事件
孩子列表 bff:parent:children:{parentId} 60s 短 TTL + 绑定变更事件
孩子成绩列表 bff:parent:grades:{childId}:{page} 30s 短 TTL + 成绩录入事件
孩子作业列表 bff:parent:homework:{childId}:{classId} 30s 短 TTL
孩子考试列表 bff:parent:exams:{childId}:{classId} 30s 短 TTL
孩子学情诊断 bff:parent:analytics:weakness:{childId} 300s 中 TTL每日刷新
孩子学习趋势 bff:parent:analytics:trend:{childId}:{range} 600s 长 TTL趋势变化慢
通知列表 bff:parent:notifications:{parentId}:{page} 15s 短 TTL + 已读后失效
通知偏好 bff:parent:notification-prefs:{parentId} 300s 中 TTL + 更新后失效
ChildGuard 绑定列表缓存 bff:parent:child-bindings:{parentId} 60s 短 TTL安全敏感不宜过长

Key 设计原则

  • 全部以 bff:parent: 前缀,便于运维清理,避免与 teacher-bff (bff:teacher:)、student-bff (student:) 冲突
  • 按 parentId / childId 维度隔离,便于按用户失效
  • 不存会话状态(方案 A当前选中 childId 由前端管理BFF 不存 parent:session:*
  • TTL 分档15s高频变更/ 30-60s中频/ 300s低频/ 600s趋势
  • ChildGuard 绑定列表缓存 TTL 60s安全敏感越权校验不能容忍太长延迟

3.1.2 缓存失效策略

flowchart TD
    W[写操作:标记已读/更新偏好] --> INV[主动失效相关 key]
    E[Kafka 事件:成绩录入/作业批改 P5] --> SUB[EventSubscriber 消费]
    SUB --> INV2[失效 bff:parent:grades:childId:*]
    T[TTL 到期] --> NATURAL[自然过期]
    INV --> REDIS[DEL key]
    INV2 --> REDIS

3.2 传输 DTO 设计

DTO 用 Zod schema 定义,自动推导 TS 类型。GraphQL type 与 Zod schema 字段对齐。下游响应包装 DownstreamEnvelopeSchemasuccess/data/error复用 teacher-bff 实现,不再赘述。

家长端核心 DTOparent/dto/parent-dashboard.dto.tsParentDashboardSchemaparentid/name/avatarchildrenid/name/grade/class/lastGradeunreadNotifications,字段与 GraphQL DashboardData type 对齐。

输入 Schemaparent/dto/parent-inputs.dto.ts

  • UpdateNotificationPreferencesSchemachannelsapp/sms/email/wechat 枚举数组min 1+ eventTypes5 个布尔开关gradeReleased/homeworkGraded/examPublished/attendanceAlert/schoolAnnouncement
  • ListGradesQuerySchemachildIdmin 1、page≥1默认 1、pageSize1-50默认 20、subject可选

3.3 DTO 与 proto message 映射

BFF DTO proto message 映射规则
ParentDashboard.parent iam.v1.UserInfo 字段一一映射
ParentDashboard.children iam.v1.Child[](待 ai02 补) 字段一一映射
ChildGrades core_edu.v1.Grade[] 时间字段毫秒数转换
ChildAnalytics analytics.v1.StudentWeakness / LearningTrend 字段一一映射
ParentNotifications msg.v1.Notification[] 字段一一映射

4. API 设计GraphQL 优先)

4.1 API 风格仲裁说明

冲突ai04 在 01-understanding.md §4.1 建议 P4 阶段 parent-bff 先对齐 teacher-bff 现状REST + fetch

用户仲裁 U3GraphQL P2 立即引入(覆盖 ai04 建议,恢复 pending-features P2 原计划)→ parent-bff 在 P4 落地时直接用 GraphQL,不走 REST 过渡期。

本设计文档执行 U3parent-bff P4 落地即 GraphQL Yoga + DataLoader不实现 REST 业务端点(仅保留 /healthz /readyz /metrics 基础端点)。

4.2 GraphQL Schema

# ============ Types ============

type Parent {
  id: ID!
  email: String!
  name: String!
  avatar: String
  roles: [String!]!
  dataScope: DataScope!
}

enum DataScope {
  SELF # L0 本人
  CHILDREN # 家长自定义级(绑定孩子)
  CLASS # L1 本班
  GRADE # L2 本年级
  SCHOOL # L3 本校
  DISTRICT # L4 本区
  ALL # L5 全部
}

type ViewportItem {
  key: String!
  label: String!
  route: String!
  icon: String
  sortOrder: String!
  requiredPermission: String
}

type Child {
  id: ID!
  name: String!
  grade: String!
  class: ClassInfo!
  # 延迟加载:孩子近期成绩
  lastGrade: Grade
  # 延迟加载:孩子成绩历史
  grades(page: Int = 1, pageSize: Int = 20, subject: String): GradePage!
  # 延迟加载:孩子作业列表
  homework: [Homework!]!
  # 延迟加载:孩子考试列表
  exams: [Exam!]!
  # 延迟加载:孩子学情诊断
  analytics: ChildAnalytics!
}

type ClassInfo {
  id: ID!
  name: String!
  gradeId: String!
}

type Grade {
  id: ID!
  examId: ID!
  examTitle: String!
  subject: String!
  score: Float!
  rank: Int
  gradedAt: DateTime!
}

type GradePage {
  items: [Grade!]!
  pagination: Pagination!
}

type Pagination {
  page: Int!
  pageSize: Int!
  total: Int!
}

type Homework {
  id: ID!
  title: String!
  classId: ID!
  dueDate: DateTime!
  status: HomeworkStatus!
  submittedAt: DateTime
}

enum HomeworkStatus {
  NOT_SUBMITTED
  SUBMITTED
  GRADED
  OVERDUE
}

type Exam {
  id: ID!
  title: String!
  classId: ID!
  status: ExamStatus!
  examDate: DateTime!
  publishedAt: DateTime
}

enum ExamStatus {
  DRAFT
  PUBLISHED
  IN_PROGRESS
  GRADING
  SCORED
  ARCHIVED
}

type ChildAnalytics {
  childId: ID!
  weakness: [WeaknessTopic!]!
  trend: [TrendPoint!]!
  classRank: Int
  classAverage: Float
}

type WeaknessTopic {
  knowledgePointId: ID!
  name: String!
  subject: String!
  masteryRate: Float!
}

type TrendPoint {
  date: DateTime!
  score: Float!
  subject: String
}

type Notification {
  id: ID!
  type: NotificationType!
  title: String!
  content: String!
  read: Boolean!
  childId: ID
  createdAt: DateTime!
}

enum NotificationType {
  SYSTEM
  EXAM
  HOMEWORK
  GRADE
  ATTENDANCE
  ANNOUNCEMENT
}

type NotificationPreferences {
  channels: [NotificationChannel!]!
  eventTypes: NotificationEventTypes!
}

enum NotificationChannel {
  APP
  SMS
  EMAIL
  WECHAT
}

type NotificationEventTypes {
  gradeReleased: Boolean!
  homeworkGraded: Boolean!
  examPublished: Boolean!
  attendanceAlert: Boolean!
  schoolAnnouncement: Boolean!
}

type DashboardData {
  parent: Parent
  children: [Child!]!
  viewports: [ViewportItem!]!
  unreadNotifications: Int!
}

# ============ Query ============

type Query {
  # 家长仪表盘聚合并行iam 家长信息 + iam 孩子列表 + core-edu 近期成绩)
  dashboard: DashboardData!

  # 视口配置L1 导航,透传 iam
  viewports: [ViewportItem!]!

  # 当前家长信息
  me: Parent!

  # 我的孩子列表
  children: [Child!]!

  # 单个孩子详情(含延迟加载字段,需 ChildGuard 校验)
  child(childId: ID!): Child

  # 孩子成绩(直接查询,需 ChildGuard 校验)
  childGrades(
    childId: ID!
    page: Int = 1
    pageSize: Int = 20
    subject: String
  ): GradePage!

  # 孩子作业(需 ChildGuard 校验)
  childHomework(childId: ID!): [Homework!]!

  # 孩子考试(需 ChildGuard 校验)
  childExams(childId: ID!): [Exam!]!

  # 孩子学情诊断(需 ChildGuard 校验P4 依赖 data-ana
  childAnalytics(childId: ID!, dateRange: DateRangeInput): ChildAnalytics!

  # 家长通知列表
  notifications(
    unreadOnly: Boolean
    page: Int = 1
    pageSize: Int = 20
  ): [Notification!]!

  # 通知偏好配置
  notificationPreferences: NotificationPreferences!
}

input DateRangeInput {
  start: DateTime!
  end: DateTime!
}

# ============ Mutation ============

type Mutation {
  # 切换当前选中孩子(方案 A仅记录审计日志不持久化会话
  selectChild(childId: ID!): SelectChildResult!

  # 标记通知已读
  markNotificationRead(notificationId: ID!): Notification!

  # 更新通知偏好
  updateNotificationPreferences(
    input: UpdateNotificationPreferencesInput!
  ): NotificationPreferences!
}

type SelectChildResult {
  childId: ID!
  selectedAt: DateTime!
  audited: Boolean!
}

input UpdateNotificationPreferencesInput {
  channels: [NotificationChannel!]!
  eventTypes: NotificationEventTypesInput!
}

input NotificationEventTypesInput {
  gradeReleased: Boolean
  homeworkGraded: Boolean
  examPublished: Boolean
  attendanceAlert: Boolean
  schoolAnnouncement: Boolean
}

scalar DateTime

4.3 Query/Mutation 清单与权限点

U4 仲裁BFF 豁免 @RequirePermission,权限由 Gateway JWT + 下游服务负责。下表"权限点"列仅为透传给下游校验的语义标注BFF 层不校验。

类型 字段 聚合下游 权限点(透传下游) ChildGuard 阶段
Query dashboard iam + core-edu PARENT_DASHBOARD_READ 否(聚合所有孩子) P4
Query viewports iam PARENT_VIEWPORT_READ P4
Query me iam PARENT_PROFILE_READ P4
Query children iam PARENT_CHILDREN_READ P4
Query child iam + core-edu PARENT_CHILD_READ P4
Query childGrades core-edu PARENT_GRADE_READ P4
Query childHomework core-edu PARENT_HOMEWORK_READ P4
Query childExams core-edu PARENT_EXAM_READ P4
Query childAnalytics data-ana PARENT_ANALYTICS_READ P4
Query notifications msg PARENT_NOTIFICATION_READ P5
Query notificationPreferences msg PARENT_PREFERENCE_READ P5
Mutation selectChild BFF 内部审计) PARENT_CHILDREN_READ P4
Mutation markNotificationRead msg PARENT_NOTIFICATION_READ P5
Mutation updateNotificationPreferences msg PARENT_PREFERENCE_UPDATE P5

4.4 DataLoader 策略

风险点 场景 DataLoader 批量键 下游 RPC 状态
孩子列表 dashboard 拉 children多个 Resolver 都要 children ChildrenLoader parentId[] iam.GetChildrenByParent待 ai02 补) 待补
成绩列表 child.grades 延迟加载,多个孩子并发 GradeLoader studentId[] core-edu.ListGradesByStudent 已有
作业列表 child.homework 延迟加载 HomeworkLoader classId[] core-edu.ListHomeworkByClass 已有
考试列表 child.exams 延迟加载 ExamLoader classId[] core-edu.ListExamsByClass 已有
班级信息 child.class 延迟加载 ClassLoader classId[] core-edu.GetClass 已有

DataLoader 生命周期

  • per-request 实例:每个 GraphQL 请求创建独立 DataLoader 实例,请求结束销毁
  • 批量化窗口:默认 16ms 内的请求合并为一次批量调用
  • 缓存DataLoader 自身缓存per-request配合 Redis 跨请求缓存§3.1

批量 RPC 缺口iam.GetChildrenByParent 当前仅支持单个 parentId 查询DataLoader 可做 per-request 去重(同一 parentId 多次调用合并为一次),但跨 parentId 批量化需 ai02 补 BatchGetChildrenByParents(parentIds[])。P4 阶段 per-request 去重已足够(家长场景单 parentId

4.5 统一响应信封

GraphQL 端点遵循 GraphQL 规范data/errors错误扩展字段携带 extensions.code = "BFF_PARENT_*"

{
  "data": null,
  "errors": [
    {
      "message": "Child not bound to parent",
      "extensions": {
        "code": "BFF_PARENT_CHILD_NOT_BOUND",
        "parentId": "u-parent-001",
        "requestedChildId": "child-999",
        "traceId": "r-xxx"
      }
    }
  ]
}

健康端点(/healthz /readyz遵循 ActionState 信封(与 classes 黄金模板一致)。


5. 事件设计

BFF 不发布领域事件无业务事务。BFF 可订阅事件用于实时推送P5 阶段)。

5.1 P4 阶段:不订阅事件

P4 阶段 parent-bff 仅做同步聚合,不消费 Kafka 事件。push-gateway P5 才落地P4 订阅事件无消费方。

5.2 P5 可选订阅事件清单

C5 仲裁Kafka topic 统一为 edu.notification.sent/read/recalled/failed(废弃 edu.notification.events 抽象名)。教学事件遵循 edu.teaching.<aggregate>.<action> 格式。

Topic 事件 发布方 消费动作 幂等性
edu.notification.sent 通知发送 msg 推送给家长push-gateway HTTP /internal/push event_id SETNX 去重
edu.notification.read 通知已读 msg 失效 bff:parent:notifications:* event_id SETNX 去重
edu.notification.recalled 通知撤回 msg 失效通知缓存 + 推送撤回消息 event_id SETNX 去重
edu.notification.failed 通知发送失败 msg 记录日志 + 告警 event_id SETNX 去重
edu.teaching.grade.recorded 成绩录入 core-edu 失效 bff:parent:grades:childId:* + 推送成绩通知 event_id SETNX 去重
edu.teaching.homework.graded 作业批改完成 core-edu 失效 bff:parent:homework:childId:* + 推送 event_id SETNX 去重
edu.teaching.exam.published 考试发布 core-edu 失效 bff:parent:exams:childId:* + 推送考试提醒 event_id SETNX 去重

5.3 事件订阅架构P5

graph LR
    K[(Kafka)]
    ES[EventSubscriber<br/>NestJS Module]
    Idempotency[(Redis SETNX<br/>event_id 去重)]
    Filter[偏好过滤器<br/>按 NotificationPreferences 过滤]
    DC[DownstreamClient]
    Push[push-gateway<br/>HTTP /internal/push]
    Cache[Redis Cache Invalidation]

    K -->|edu.notification.sent| ES
    K -->|edu.teaching.grade.recorded| ES
    ES --> Idempotency
    Idempotency -->|首次| Filter
    Filter -->|偏好允许| DC
    DC --> Push
    ES --> Cache

U2 仲裁push-gateway 豁免 gRPCparent-bff 调用 push-gateway 走 HTTP /internal/push(不是 gRPC

5.4 通知偏好过滤逻辑

P5 订阅事件时,根据家长 NotificationPreferences 过滤(kafka/handlers/notification-push.handler.ts

  1. 拉取家长 NotificationPreferencesRedis 缓存 300s
  2. eventTypeMap 映射事件类型 → 偏好开关GRADE→gradeReleased、HOMEWORK→homeworkGraded、EXAM→examPublished、ATTENDANCE→attendanceAlert、ANNOUNCEMENT→schoolAnnouncement
  3. 若偏好开关为 false → 不推送(家长关闭了该类通知)
  4. prefs.channelsevent.channels 交集,无交集则不推送
  5. 调用 pushClient.pushViaHttp(parentId, payload, allowedChannels)U2HTTP /internal/push

5.5 事件订阅消费者组设计P5

consumer_group: parent-bff-event-subscriber
topics:
  - edu.notification.sent
  - edu.notification.read
  - edu.notification.recalled
  - edu.notification.failed
  - edu.teaching.grade.recorded
  - edu.teaching.homework.graded
  - edu.teaching.exam.published
commit_strategy: manual
retry_strategy:
  max_retries: 3
  backoff: exponential
  dlq_topic: edu.parent-bff.dlq

5.6 BFF 不发布事件

BFF 是聚合层,不发布领域事件无业务状态变更。BFF 自身的 metrics/audit log 走可观测性通道,不走 Kafka。


6. 横切关注点对齐清单

6.1 权限装饰器U4 豁免说明)

端点 权限校验 说明
/graphql全部 Query/Mutation 仅校验 x-user-id 存在 + ChildGuard 越权校验 U4 仲裁BFF 豁免 @RequirePermission,权限由 Gateway JWT + 下游服务负责
/healthz, /readyz, /metrics 白名单放行

豁免理由U4

  1. BFF 是聚合层,不持有业务资源,权限决策无意义
  2. Gateway 已做 JWT 校验RS256非法请求无法到达 BFF
  3. 下游服务iam/core-edu/data-ana/msg各自用 @RequirePermission 校验BFF 透传 x-user-id 即可
  4. BFF 仅做 ChildGuard 越权防御DataScope=CHILDREN这不是权限决策而是数据隔离防御

6.2 错误码清单BFF_PARENT_ 前缀,对齐 C1 仲裁)

错误码 HTTP 触发条件 详情字段
BFF_PARENT_VALIDATION_ERROR 400 Zod / GraphQL input 校验失败 { field, message }
BFF_PARENT_UNAUTHORIZED 401 缺失 x-user-id 头
BFF_PARENT_CHILD_NOT_BOUND 403 ChildGuard 拦截childId 不在家长绑定列表 { parentId, requestedChildId, boundChildren }
BFF_PARENT_NOT_FOUND 404 资源不存在BFF 自身资源) { resource, id }
BFF_PARENT_CONFLICT 409 重复操作 / 状态冲突 { reason }
BFF_PARENT_BUSINESS_ERROR 422 业务规则违反 { rule }
BFF_PARENT_BAD_GATEWAY 502 下游服务返回非 ok 或 gRPC rejected { service, rpc, status, traceId }
BFF_PARENT_GATEWAY_TIMEOUT 504 下游调用超时 { service, rpc, timeoutMs }
BFF_PARENT_SERVICE_UNAVAILABLE 503 熔断器开启 { service, circuitState }
BFF_PARENT_INTERNAL_ERROR 500 未捕获异常 { traceId }

下游业务错误透传C3 仲裁core-edu 统一 CORE_EDU_*,不细分 EXAMS_/HOMEWORK_/GRADES_

下游服务 错误码前缀 示例
iam IAM_* IAM_USER_NOT_FOUND
core-edu CORE_EDU_* CORE_EDU_GRADE_NOT_FOUNDCORE_EDU_EXAM_NOT_FOUND
data-ana DATA_ANA_* DATA_ANA_ANALYTICS_NOT_READY
msg MSG_* MSG_NOTIFICATION_NOT_FOUND

6.3 Loggerpino

文件位置 src/shared/observability/logger.ts
service 字段 'parent-bff'
level env.LOG_LEVEL默认 info
输出 JSON stdout
字段 time, level, service, msg, traceId, parentId, childId, endpoint, duration, err
采样 生产环境 warn+ 100% 采样info 10% 采样
禁止 console.log;记录 JWT token / 密码 / 孩子成绩数值(仅记录 metadata

6.4 Metricsprom-client

指标名 类型 标签 描述
parent_bff_graphql_requests_total Counter operation, status GraphQL 请求总数
parent_bff_graphql_duration_seconds Histogram operation GraphQL 请求延迟
parent_bff_downstream_calls_total Counter service, rpc, status 下游 gRPC 调用次数
parent_bff_downstream_duration_seconds Histogram service, rpc 下游调用延迟
parent_bff_downstream_errors_total Counter service, rpc, error_type 下游调用错误数
parent_bff_cache_hits_total Counter cache_key_pattern 缓存命中
parent_bff_cache_misses_total Counter cache_key_pattern 缓存未命中
parent_bff_child_guard_blocks_total Counter ChildGuard 越权拦截次数
parent_bff_circuit_state Gauge service, state 熔断器状态P6
parent_bff_event_consumed_total CounterP5 topic, event_type 事件消费数
parent_bff_event_pushed_total CounterP5 topic, push_status 推送数

6.5 TracerOpenTelemetry

文件位置 src/shared/observability/tracer.ts
serviceName 'parent-bff'
exporter OTLP HTTP → collector
auto-instrumentations http, nestjs-core, express, ioredis, @grpc/grpc-js, kafka-node
span 属性 parentId, childId, operation, downstream.service, cache.hit, childGuard.blocked
采样率 生产 10%,开发 100%
上下文传播 W3C Trace Contexttraceparent 头 → gRPC metadata

6.6 /healthz 与 /readyz

端点 检查逻辑 响应
/healthz 进程存活(直接返回 ok 200 { status: 'ok', service: 'parent-bff', timestamp }
/readyz 直接返回 ok(对齐 teacher-bffBFF 无 DB下游可达性由 Prometheus 监控) 200 { status: 'ready', service: 'parent-bff', timestamp }

决策/readyz 不检查下游可达性。理由:

  1. BFF 无 DB无需 SELECT 1
  2. 下游不可达时 BFF 降级返回 partial 数据,不阻塞 readiness
  3. 下游健康由各自 /healthz + Prometheus 监控BFF 不重复探针
  4. 若 K8s readiness 探针依赖下游,会导致下游故障时 BFF Pod 被摘流,反而失去降级能力

6.7 优雅关闭

SIGTERM 关闭顺序main.ts 已处理 app.close()P5+ 补充 Kafka consumer

  1. 拒绝新请求readyz 返回 503
  2. 停止消费 Kafkacommit 最后 offset[P5+]
  3. 等待 in-flight GraphQL 请求完成(最多 10s
  4. 关闭 Redis 连接
  5. flush 剩余 OTel span
  6. 进程退出

6.8 Zod 输入验证GraphQL input 校验)

GraphQL input Zod Schema 校验项
updateNotificationPreferences.input UpdateNotificationPreferencesSchema channels 非空、eventTypes 布尔值
childGrades 参数 ListGradesQuerySchema childId 非空、page ≥ 1、pageSize 1-50
selectChild.childId z.string().min(1) childId 非空
markNotificationRead.notificationId z.string().min(1) notificationId 非空

GraphQL Yoga 内置参数校验 + Zod schema 二次校验(双重保障),自定义 scalarDateTime、ID

6.9 GlobalErrorFilter

  • 文件:src/shared/errors/global-error.filter.ts
  • 装饰:@Catch() 全局
  • 行为:
    1. ApplicationError → 按 statusCode + code 返回 ActionState / GraphQL errors
    2. ZodError → 400 + BFF_PARENT_VALIDATION_ERROR + details
    3. GraphQL 错误 → 包装为 GraphQL errors 数组(携带 extensions.code
    4. 未知 Error → 500 + BFF_PARENT_INTERNAL_ERROR + traceId
    5. 注入 traceIdx-request-id 头或新生成)

6.10 Dockerfile多阶段构建

复制 teacher-bff Dockerfile仅改 EXPOSE 3010(对齐 C2 端口仲裁。多阶段构建builder 阶段 pnpm install --frozen-lockfile + pnpm run buildruntime 阶段 pnpm install --prod + COPY --from=builder /app/dist + EXPOSE 3010 + CMD ["node", "dist/main.js"]


7. 与其他模块的交互点

7.1 完整交互矩阵

方向 对方服务 协议 接口/事件 用途 阶段 proto 状态
被调用 api-gateway HTTP POST /graphql + /healthz /readyz /metrics Gateway 转发 + 注入 x-user-id P4
被调用 parent-portal HTTP经 Gateway POST /api/v1/parent/graphql 前端 GraphQL 调用 P4
调用 iam gRPC 50052P4+ GetUserInfo 家长个人信息 P4 已有
调用 iam gRPC 50052 GetViewports 家长端视口 P4 待 ai02 补
调用 iam gRPC 50052 GetEffectivePermissions 有效权限列表C4对应 REST GET /iam/permissions/effective P4 待 ai02 补
调用 iam gRPC 50052 GetChildrenByParent 查询家长绑定孩子列表P0 阻塞) P4 待 ai02 补
调用 core-edu gRPC 50053 ExamService.ListExamsByClass 孩子班级考试 P4 已有
调用 core-edu gRPC 50053 HomeworkService.ListHomeworkByClass 孩子作业 P4 已有
调用 core-edu gRPC 50053 GradeService.ListGradesByStudent 孩子成绩 P4 已有
调用 core-edu gRPC 50053 ClassService.GetClass 孩子班级信息 P4 已有
调用 core-edu gRPC 50053 AttendanceService.* 孩子出勤(未来扩展) P5+ 待 ai03 补
调用 data-ana gRPC 50055 AnalyticsService.GetStudentWeakness 学情诊断 P4 已有
调用 data-ana gRPC 50055 AnalyticsService.GetLearningTrend 学习趋势 P4 已有
调用 data-ana gRPC 50055 AnalyticsService.GetClassPerformance 班级学情对比 P4 已有
调用 msg gRPC 50056 NotificationService.ListNotifications 家长通知列表 P5 已有
调用 msg gRPC 50056 NotificationService.MarkAsRead 标记已读 P5 已有
调用 msg gRPC 50056 NotificationPreferenceService.* 通知偏好配置(待 ai05 补) P5 待补
调用 push-gateway HTTPU2 仲裁) POST /internal/push 推送给在线家长 P5 HTTP
消费(可选) Kafka Kafka edu.notification.sent 推送通知 P5 topic 已定义
消费(可选) Kafka Kafka edu.notification.read 失效通知缓存 P5
消费(可选) Kafka Kafka edu.notification.recalled 撤回通知 P5
消费(可选) Kafka Kafka edu.notification.failed 通知失败告警 P5
消费(可选) Kafka Kafka edu.teaching.grade.recorded 失效成绩缓存 + 推送 P5
消费(可选) Kafka Kafka edu.teaching.homework.graded 失效作业缓存 + 推送 P5
消费(可选) Kafka Kafka edu.teaching.exam.published 失效考试缓存 + 推送 P5
依赖 shared-proto 静态导入 iam.proto / core_edu.proto / analytics.proto / msg.proto 契约定义 跨阶段
依赖 Redis TCP 缓存 聚合结果短缓存 P4
依赖 OTLP Collector HTTP trace 上报 可观测性 P4

7.2 端口分配

服务 HTTP 端口 gRPC 端口 说明
parent-bff本模块 3010 不暴露C2 仲裁) BFF 对上游仅 HTTP/GraphQL对下游走 gRPC
iam 3002 50052
core-edu 3004 50053
data-ana 3006 50055
msg 3007 50056
push-gateway 8081 U2 豁免 gRPC 仅 HTTP /internal/push

7.3 跨模块协作需求(需提交 coord 协调)

# 需求 涉及 AI 阻塞阶段 协调内容
1 iam 新增家长-学生关联接口P0 阻塞) ai02 P4 iam_student_guardians 表 + Repository + GET /iam/children REST + proto GetChildrenByParent RPC
2 iam 补 GetViewports / GetEffectivePermissions RPC ai02 P4 proto 契约补全
3 api-gateway 新增 /parent 路由 ai01 P4 main.go + config.go 新增 ParentBffURL 字段 + 路由块
4 docker-compose.deploy.yml 新增 parent-bff 服务定义 coordinfra P4 端口 3010加入 edu-net + edu-shared 网络
5 full-stack-runbook 端口矩阵更新 coorddocs P4 追加 3010 行
6 004 §4 依赖图更新 coorddocs P4 parent-bff 依赖扩展为 iam + core-edu + data-ana + msgC6 仲裁),状态从"📐 需设计"改为" 已实现"
7 buf.gen.yaml 补 gRPC 插件 coord P4 parent-bff P4 直接用 gRPC需 gRPC client 代码生成
8 data-ana 实现 analytics.proto 查询 API ai06 P4 学情诊断端点依赖
9 msg 服务补 NotificationPreferenceService ai05 P5 通知偏好配置端点依赖
10 msg 服务落地 ListNotifications / MarkAsRead ai05 P5 家长通知列表依赖
11 push-gateway 落地 HTTP /internal/pushU2 仲裁) ai01 P5 parent-bff P5 推送依赖

7.4 与 teacher-bff / student-bff 的复用与差异

维度 teacher-bff student-bff parent-bff本模块
API 风格 RESTP2→ GraphQLP4 RESTP3→ GraphQLP6+ 可选) GraphQLP4 直接U3 仲裁)
DataScope CLASS / GRADE / SCHOOL SELF CHILDREN自定义级
越权防御 不做(透传 x-user-id 做(强制 userId 比对) ChildGuardchildId ∈ 绑定列表)
端口 3003 3009 3010
路由前缀 /teacher /student /parent
错误码前缀 BFF_TEACHER_ BFF_STUDENT_ BFF_PARENT_
指标前缀 teacher_bff_ student_bff_ parent_bff_
多角色复用 教师/教导主任/教研组长 学生/学习委员/课代表 家长(单一角色,多子女切换)
主要下游 iam + core-edu + content + data-ana + ai + msg iam + core-edu + content + data-ana + msg + ai iam + core-edu + data-ana + msgC6 仲裁)
特有功能 SSE 流式AI 辅助) SSE 流式AI 答疑) 多子女切换 + 通知偏好 + ChildGuard

8. 风险与假设

8.1 P0 阻塞项

风险 影响 概率 缓解措施
iam 缺失"家长-学生关联查询"接口proto + REST + schema 三缺失) P0 阻塞parent-bff 核心场景查孩子列表无法实现ChildGuard 越权校验无数据源 coord 协调 ai02 在 P3 阶段parent-bff P4 落地前补全1) iam_student_guardians2) Repository 查询方法3) GET /iam/children REST 端点4) proto GetChildrenByParent RPC
pending-features P2 提到 parent_student_relations 表,但 iam.schema.ts 未实现 表缺失 同上,推动 ai02 补表
data-ana 服务未实现查询 APIanalytics.proto 3 个 method 无 REST/gRPC 端点) P4 学情诊断端点无法实现 coord 协调 ai06 在 P4 实现 data-ana 查询 API
msg 通知偏好配置接口缺失 P5 家长通知偏好无法配置 coord 协调 ai05 在 P5 补 NotificationPreferenceService

8.2 技术风险

风险 影响 概率 缓解措施
GraphQL Schema 演进破坏前端 Schema 版本化 + deprecation 先行 + 协调前端 ai07parent-portal
ChildGuard 越权校验性能损耗(每次查询都调 iam.GetChildrenByParent Redis 缓存绑定列表 60sbff:parent:child-bindings:{parentId}TTL 内不重复调 iam
Redis 缓存穿透(家长查询未绑定的 childId ChildGuard 拦截 + 短 TTL + 空结果缓存null cache 5s
下游服务不可用 Promise.allSettled 降级策略 + 熔断P6
Kafka consumer 引入复杂度 P5 评估,可继续用短 TTL 兜底P4 不依赖 Kafka
DataLoader 批量 RPC 缺口iam 无 BatchGetChildrenByParents P4 阶段 per-request 去重已足够(家长场景单 parentId无需跨 parentId 批量化
GraphQL 查询复杂度攻击 depth limit ≤ 7 + query cost analysiscost ≤ 1000

8.3 外部依赖假设

假设 若假设不成立的影响 Fallback 方案
iam P3 阶段补全 GetChildrenByParent RPC + iam_student_guardians P4 parent-bff 无法启动实施 P0 阻塞,无 fallback必须协调 ai02 补全
iam GET /iam/me 返回包含 dataScope=CHILDREN ChildGuard 无法校验 硬编码 dataScope=CHILDREN家长固定
core-edu P4 启用 gRPC server parent-bff P4 退化用 REST 协调 ai03 启用 gRPC或 P4 临时用 REST fetch
api-gateway 已注册 /parent 路由 前端请求 404 P4 阻塞,协调 ai01
Redis 已部署且网络可达 缓存层失效,性能下降 Cache 层降级为内存 LRU
OTLP Collector 已部署 链路追踪缺失 不影响业务,仅日志降级
Kafka 已部署且 topic 已创建C5edu.notification.sent/read/recalled/failed P5 事件订阅无法实现 P5 阻塞P4 不依赖 Kafka
前端 parent-portalai07P4 配合使用 GraphQL GraphQL 端点无人调用 协调 ai07 同步开发 GraphQL client

8.4 未决设计决策(待 ai05 review

以下为 coord 推断过程中产生的新问题,标注"待 ai05 review"ai05 接手后确认或提请 coord 仲裁。

# 决策点 coord 推断结论 待 ai05 review
1 GraphQL Yoga vs Apollo Server GraphQL Yoga(对齐 teacher-bff 02 设计 §3.2 ai05 确认是否对齐
2 ChildGuard 绑定列表缓存 TTL 60s(安全敏感,不宜过长) ai05 评估安全性与性能权衡
3 selectChild mutation 是否需要 保留(仅审计日志,不持久化) ai05 确认是否保留(方案 A 下前端可不经 BFF 直接切换)
4 通知偏好存储位置 msg 服务(与通知发送强相关) ai05 确认,需协调 msg 服务补 NotificationPreferenceService
5 GraphQL 查询复杂度限制 depth ≤ 7cost ≤ 1000 ai05 评估是否调整
6 是否在 P4 引入熔断器 不引入P6 硬化P4 用 Promise.allSettled 降级 ai05 确认
7 /readyz 是否检查下游 不检查(直接 ok对齐 teacher-bff ai05 确认
8 出勤查询AttendanceService归属阶段 P5+core-edu 未实现,未来扩展) ai05 确认是否 P4 必需
9 多家长共用孩子如父母都绑同一孩子ChildGuard 行为 正常(每个家长独立查绑定列表) ai05 确认无特殊处理
10 GraphQL Subscription 是否引入 不引入P5 用 push-gateway HTTP 推送,不走 GraphQL Subscription ai05 确认

8.5 已仲裁决策汇总(本文档执行,不再 review

见 §0.1 已仲裁决策表U1-U4 + C1-C6


coord 推断生成声明(保留历史追溯)

§0-§8 由 coord 基于 ai04 阶段 1 交付(01-understanding.md+ teacher-bff/student-bff 模式 + 用户 4 项仲裁决策U1-U4+ coord 补充仲裁C1-C6推断生成非 ai05 原创交付。

coord 推断依据

  1. ai04 阶段 1 交付01-understanding.md 已识别 parent-bff 位置、限界上下文、契约缺口、P0 阻塞项。本设计文档基于该理解深化。
  2. teacher-bff/student-bff 模式参考:分层结构、目录结构、横切关注点、错误码清单、可观测性设计参考两份 02-architecture-design.md。
  3. 用户 4 项仲裁决策U1-U4+ coord 补充仲裁C1-C6见 §0.1 已仲裁决策表。

coord §8.4 待 ai05 review 事项10 项)

coord 推断过程中产生的 10 项设计决策已在 §8.4 列出ai05 接手后的逐项确认见下文 §9。


9. ai05 review 结论(响应 coord §8.4 的 10 项待 review 决策)

本节为 ai05 复审对 coord §8.4 的 10 项决策逐项响应。原则:能确认的直接 ,需调整的标注 🔧 并给出新结论,重大冲突标注 ⚠️ 提请 coord 仲裁。

# 决策点 coord 推断结论 ai05 review 结论 说明
1 GraphQL Yoga vs Apollo Server GraphQL Yoga对齐 teacher-bff 02 §3.2 确认 对齐 teacher-bff统一 GraphQL Yoga v3基于 whatwg-node与 NestJS Express 兼容)
2 ChildGuard 绑定列表缓存 TTL 60s安全敏感不宜过长 🔧 调整为 30s + 主动失效 家长绑定关系变更如解绑60s 内仍可访问是安全风险;调整为 30s TTL + iam UserEvent.action=children_changed 事件主动失效P5 引入 Kafka consumer 后P4 阶段 30s TTL 兜底,解绑后 30s 内仍可访问(可接受,因 iam 已删除绑定)
3 selectChild mutation 是否需要 保留(仅审计日志,不持久化) 确认 保留 selectChild(childId: ID!): Boolean! 用于审计BFF 不持久化会话(方案 A 无状态);前端切换不需要调此 mutation仅审计场景调用
4 通知偏好存储位置 msg 服务(与通知发送强相关) 确认 已在 §0.1 C6 仲裁parent-bff → msg 调用 NotificationPreferenceServiceP5 msg 服务补全时实现
5 GraphQL 查询复杂度限制 depth ≤ 7cost ≤ 1000 确认 + 补充 depth ≤ 7 / cost ≤ 1000 适合家长场景(查询深度浅、字段少);补充:启用 graphql-query-complexity 库 + list 类字段 cost 计算乘以 pageSize防止 pageSize=99999 攻击)
6 是否在 P4 引入熔断器 不引入P6 硬化P4 用 Promise.allSettled 降级 确认 P4 用 Promise.allSettled 部分失败容忍P6 引入 opossum 熔断器per-downstream-service 独立 circuit
7 /readyz 是否检查下游 不检查(直接 ok对齐 teacher-bff 🔧 调整P4 即补下游探针 coord §16.4 黄金模板对齐表 parent-bff readyz 标 ⚠️ai05 复审要求 P4 补下游探针iam + core-edu + data-ana + Redis探针超时 1s/服务,任一失败返回 degradedK8s readinessProbe 用此端点决定流量路由,无探针导致下游故障时仍接收流量
8 出勤查询AttendanceService归属阶段 P5+core-edu 未实现,未来扩展) 确认 core-edu AttendanceService proto + 实现均缺失coord §16.6.3 #1 P3 补全parent-bff P4 不接出勤P5+ 评估
9 多家长共用孩子父母都绑同一孩子ChildGuard 行为 正常(每个家长独立查绑定列表) 确认 + 补充 每个家长独立调 iam.GetChildrenByParent独立缓存key: bff:parent:child-bindings:{parentId});补充:缓存 key 不含 childId避免同一家长多个缓存项ChildGuard 直接查询返回的列表判断 childId ∈ list
10 GraphQL Subscription 是否引入 不引入P5 用 push-gateway HTTP 推送,不走 GraphQL Subscription 确认 实时推送走 push-gateway HTTP /internal/pushU2 仲裁),不走 GraphQL SubscriptionWebSocket 长连接与 BFF 无状态约束冲突);前端用 EventSource / SSE 接收 push-gateway 推送

9.1 ai05 新增识别的待 coord 仲裁项8 项,详见 §14

ai05 复审过程中新识别的 8 项决策需提请 coord 仲裁(不属于 coord §8.4 范围):

  1. iam 补 GetEffectiveAccess(parentUserId) → {perms, viewports, dataScope, children} 聚合 RPC(减少 2 次 RTT
  2. core-edu 补 GetClassesByStudent(student_id) → Class[] RPCchildId→classId 转换当前缺失)
  3. data-ana 补 GetParentDashboard(parent_id) → ParentDashboard 聚合 RPC(多子女场景防 N+1
  4. iam 补 parent 角色权限映射 或确认采用 coord §16.5 #4 仲裁豁免parent 角色无权限点)
  5. ChildGuard 缓存击穿保护singleflight 模式防止缓存过期瞬间 N 个请求同时调 iam.GetChildrenByParent
  6. 多子女切换跨标签同步BroadcastChannel + LWW前端方案BFF 仅提供 selectChild 审计 mutation
  7. GraphQL Schema 版本化策略@deprecated + Schema Registry + Breaking Change 检测
  8. msg 服务 P5 落地后 parent-bff 接入方式gRPC 50056推荐还是 HTTP REST兼容

10. 长远架构演进设计ai05 补全,覆盖 P5-P7+

本节为 ai05 视角的长远架构演进,符合"通用架构设计标准 + 为未来可能发生的做好铺垫"要求。覆盖 10 个长远预留点,确保 parent-bff 在 P4 之后的演进中不需重构核心抽象。

10.1 演进路线图P4-P7+

graph LR
    P4[P4 MVP<br/>GraphQL + DataLoader<br/>多子女切换 + ChildGuard<br/>iam + core-edu + data-ana]
    P5[P5 通知接入<br/>msg gRPC 50056<br/>通知偏好配置<br/>push-gateway HTTP 推送<br/>Kafka consumer 缓存失效]
    P6[P6 硬化<br/>熔断器 opossum<br/>HPA + mTLS<br/>SLO 监控 + 告警<br/>灰度发布]
    P7[P7+ 长远<br/>多租户隔离<br/>合规审计<br/>i18n 5 语言<br/>移动端 PWA<br/>AI 家长助手<br/>Service Mesh]

    P4 --> P5 --> P6 --> P7

10.2 P4 阶段交付项MVP已含 §0-§8

  • GraphQL Yoga + DataLoader 落地
  • 多子女切换(方案 A 前端管理 + selectChild 审计)
  • ChildGuard 越权校验30s TTL + 主动失效 P5
  • 并行 gRPC 编排 + Promise.allSettled 降级
  • Redis 聚合缓存 5-30s
  • /readyz 下游探针iam + core-edu + data-ana + Redis
  • 错误码 BFF_PARENT_* 全量落地
  • 横切关注点对齐logger/metrics/tracer/Zod/ErrorFilter/Dockerfile

10.3 P5 阶段交付项

  • 🔜 msg gRPC 50056 接入NotificationService + NotificationPreferenceService
  • 🔜 push-gateway HTTP /internal/push 接入(推送子女成绩/作业批改/考试发布通知)
  • 🔜 Kafka consumer 引入(订阅 edu.teaching.grade.recorded / edu.teaching.exam.published / edu.identity.user.role_changed 精确失效缓存)
  • 🔜 ChildGuard 主动失效(订阅 edu.identity.user.children_changed 事件,立即失效绑定列表缓存)
  • 🔜 通知偏好过滤(家长关闭"成绩推送"偏好时Kafka consumer 跳过该家长推送)
  • 🔜 SSE 流式透传(如 parent-bff 有 AI 场景;目前待产品确认)

10.4 P6 阶段硬化项

  • 🔜 熔断器 opossum per-downstream-serviceiam/core-edu/data-ana/msg 独立 circuit
  • 🔜 HPA 2-10 副本 + podAntiAffinity
  • 🔜 mTLSService Mesh 接入后由 Envoy 负责parent-bff 代码无需变更)
  • 🔜 SLO 监控 + 告警规则P95 延迟 < 200ms / 错误率 < 0.1% / 可用性 > 99.9%
  • 🔜 灰度发布(按 parentId hash 路由流量百分比)

10.5 长远预留点10 项,为未来可能发生的做好铺垫)

# 预留点 当前铺垫 未来触发条件 演进路径
1 多租户隔离 Redis 缓存 key 含 parentId,无 tenantIdGraphQL Schema 无 tenant 字段 SaaS 化 / 多校独立部署 缓存 key 加 tenantIdGraphQL 加 tenantId 字段BFF 无状态不需重构
2 合规审计 selectChild mutation 已记录审计日志;未来全量审计 COPPA/FERPA/PIPL/GDPR 合规要求 引入审计日志 service + 写 Kafka edu.audit.parent.action topic
3 国际化i18n 错误码已用英文 + traceIdmessage 待前端 i18n key 映射 5 语言(中英日韩西)支持 错误码不变message 字段改为 i18n key前端按 key 翻译)
4 移动端 PWA GraphQL 端点对移动端友好(单端点 + 灵活查询) 家长 App 开发 复用 GraphQL 端点;新增 /parent/mobile/version-check 端点 + PWA manifest
5 AI 家长助手 无(待产品确认) P5+ AI 服务扩展家长场景 parent-bff 调 ai gRPC StreamChat + SSE 透传(复用 teacher-bff §10 模式)
6 Service Mesh 接入 BFF 无状态;下游 gRPC client 可替换为 Envoy 代理 P6+ Service Mesh 落地 gRPC client target 从 localhost:50052 改为 iam-service:50052Envoy 服务发现)
7 API 版本化 GraphQL Schema 无版本前缀REST /healthz 等无版本 Schema 破坏性变更 GraphQL @deprecated + Schema Registry + Breaking Change 检测REST /v2/healthz
8 容灾与高可用 单实例P4/ HPA 多副本P6无跨可用区 跨可用区容灾 parent-bff 无状态直接跨可用区部署Redis 跨可用区主从Kafka consumer 跨可用区消费
9 降级模式矩阵 Promise.allSettled 部分失败容忍Redis 缓存兜底 下游服务故障 4 类降级①下游不可用→返回缓存陈旧数据②Redis 不可用→内存 LRU③GraphQL 不可用→REST fallbackP6+);④全部不可用→维护页面
10 可扩展抽象 Client 抽象层IamClient/CoreEduClient/DataAnaClient/MsgClient interface + gRPC/HTTP impl 新下游接入(如新增 student-bff 直接调用) 新增 XxxClient interface + impl注入 Orchestrator无需改 Controller/Resolver

10.6 长远架构原则ai05 总结)

parent-bff 长远架构遵循 6 项原则:

  1. BFF 本分:只聚合、裁剪、协议转换,不持业务状态、不做权限决策、不直访业务 DB004 §3.2 + coord §16.5 #4
  2. 契约驱动:所有跨模块交互以 proto 契约为唯一源proto 缺失项显式标注并提请 coord 仲裁
  3. 平滑演进:每一阶段都能独立交付价值,不依赖未来阶段;每次升级通过抽象层隔离变更
  4. 为未来铺垫:抽象层预留 gRPC / GraphQL / SSE / Kafka / 熔断 / 多租户扩展点,未来需求接入时无需重构核心
  5. 降级优先:任一下游故障时 BFF 应优雅降级(返回缓存陈旧数据或部分字段 null而非整体 500
  6. 可观测性三支柱pino + prom-client + OTel必须同时启用ChildGuard / DataLoader / Orchestrator 三层关键路径埋点

11. 测试策略ai05 补全)

符合黄金模板"测试覆盖率 ≥ 80%"要求。测试金字塔 4 层。

11.1 测试金字塔

            /\
           /E2E\        ← 5%关键场景端到端Playwright
          /------\
         /Contract\      ← 15%proto 契约一致性Pact
        /----------\
       / Integration \   ← 30%Controller + Service + Redis Testcontainers
      /--------------\
     /     Unit       \  ← 50%Resolver + DataLoader + ChildGuard + Orchestrator
    /------------------\

11.2 关键测试用例10 项)

# 测试用例 类型 关键断言
1 ChildGuard 拦截越权 childId Unit childId ∉ 绑定列表时抛 BFF_PARENT_CHILD_NOT_BOUND(403)
2 ChildGuard 缓存命中 Unit 30s 内第二次查询不调 iam.GetChildrenByParent
3 ChildGuard 缓存击穿保护 Unit 并发 100 请求只调 iam 1 次singleflight
4 多子女仪表盘并行编排 Integration 3 子女 × 3 下游 = 9 并行 gRPC总耗时 < max(单次) + 100ms
5 下游部分失败降级 Unit data-ana 失败时返回 dashboard.degraded=true其他字段正常
6 GraphQL 查询复杂度限制 Unit depth=8 查询被拒cost=1001 查询被拒pageSize=99999 被拒
7 GraphQL Schema 破坏性变更检测 Contract @deprecated 字段删除前必须先 deprecate 1 个版本
8 selectChild 审计日志 Integration mutation 调用后审计日志写入traceId + parentId + childId + timestamp
9 /readyz 下游探针 Integration iam 故障时 readyz 返回 503 + degraded=true
10 缓存失效P5 Kafka Integration 收到 edu.teaching.grade.recorded 事件后 bff:parent:grades:{childId} 缓存失效

11.3 Mock 策略

下游 Mock 方式 工具
iam gRPC Mock serverproto 一致) @grpc/grpc-js mock + jest.mock
core-edu gRPC Mock server 同上
data-ana gRPC Mock server 同上
msg gRPC Mock serverP5 同上
Redis Testcontainers真实 Redis 实例) testcontainers/redis
Kafka Mock consumerP5 kafkajs mock + jest.mock

12. 完整配置项清单ai05 补全)

所有环境变量统一 Zod 校验(src/config/env.ts),符合黄金模板"环境校验"要求。

12.1 配置项分组

// src/config/env.ts
import { z } from "zod";

const envSchema = z.object({
  // === 服务基础 ===
  NODE_ENV: z
    .enum(["development", "production", "test"])
    .default("development"),
  PORT: z.string().default("3010").transform(Number),
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
  DEV_MODE: z
    .string()
    .default("false")
    .transform((v) => v === "true"),

  // === 下游服务 URLP4 HTTPP5+ gRPC target ===
  IamServiceUrl: z.string().url().default("http://localhost:3002"),
  CoreEduServiceUrl: z.string().url().default("http://localhost:3004"),
  DataAnaServiceUrl: z.string().url().default("http://localhost:3006"),
  MsgServiceUrl: z.string().url().default("http://localhost:3007"), // P5 启用
  PushGatewayUrl: z.string().url().default("http://localhost:8081"), // P5 启用

  // === 下游 gRPC targetP5+ 启用,覆盖 URL ===
  IamGrpcTarget: z.string().optional(), // 默认 localhost:50052
  CoreEduGrpcTarget: z.string().optional(), // 默认 localhost:50053
  DataAnaGrpcTarget: z.string().optional(), // 默认 localhost:50055
  MsgGrpcTarget: z.string().optional(), // 默认 localhost:50056P5

  // === Redis ===
  REDIS_URL: z.string().url().default("redis://localhost:6379"),
  REDIS_KEY_PREFIX: z.string().default("bff:parent:"),

  // === ChildGuard ===
  CHILD_GUARD_CACHE_TTL_SECONDS: z.string().default("30").transform(Number),
  CHILD_GUARD_SINGLEFLIGHT_ENABLED: z
    .string()
    .default("true")
    .transform((v) => v === "true"),

  // === 缓存 ===
  DASHBOARD_CACHE_TTL_SECONDS: z.string().default("15").transform(Number),
  GRADES_CACHE_TTL_SECONDS: z.string().default("30").transform(Number),
  PERMISSIONS_CACHE_TTL_SECONDS: z.string().default("300").transform(Number), // 5min

  // === GraphQL ===
  GRAPHQL_DEPTH_LIMIT: z.string().default("7").transform(Number),
  GRAPHQL_COST_LIMIT: z.string().default("1000").transform(Number),
  GRAPHQL_INTROSPECTION_ENABLED: z
    .string()
    .default("false")
    .transform((v) => v === "true"),

  // === 可观测性 ===
  OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url().optional(),
  OTEL_SERVICE_NAME: z.string().default("parent-bff"),
  OTEL_SERVICE_VERSION: z.string().optional(),

  // === KafkaP5+ 启用) ===
  KAFKA_BROKERS: z.string().optional(), // "localhost:9092,localhost:9093"
  KAFKA_CONSUMER_GROUP_ID: z.string().default("parent-bff"),
  KAFKA_CONSUMER_TOPICS: z.string().optional(), // "edu.teaching.grade.recorded,edu.teaching.exam.published,edu.identity.user.role_changed"

  // === CORS ===
  CORS_ORIGINS: z.string().default("http://localhost:4002"), // parent-portal
});

export type Env = z.infer<typeof envSchema>;
export const env = envSchema.parse(process.env);

12.2 配置项分组说明

分组 配置项数 说明
服务基础 5 NODE_ENV / PORT / LOG_LEVEL / DEV_MODE / OTEL_SERVICE_NAME
下游 URL 5 iam / core-edu / data-ana / msg / push-gatewayHTTP 模式)
下游 gRPC target 4 iam / core-edu / data-ana / msggRPC 模式P5+ 启用覆盖 URL
Redis 2 REDIS_URL / REDIS_KEY_PREFIX
ChildGuard 2 缓存 TTL / singleflight 开关
缓存 3 dashboard / grades / permissions 各自 TTL
GraphQL 3 depth / cost / introspection
可观测性 3 OTLP endpoint / service name / version
Kafka 3 brokers / consumer group / topicsP5+ 启用)
CORS 1 允许的 originparent-portal 默认 4002

12.3 环境变量优先级

  1. 进程环境变量K8s ConfigMap / Secret / docker-compose env_file
  2. .env 文件(仅开发环境,dotenv 加载)
  3. Zod schema 默认值

13. 黄金模板对齐 checklistai05 补全16 项)

对照 project_rules §3 + known-issues §2.2 classes 黄金模板 + coord §16.4 黄金模板对齐表

# 对齐项 状态 说明
1 权限装饰器 @RequirePermission ⚠️ 豁免coord §16.5 #4 仲裁) BFF 不做权限决策,仅校验 x-user-id 存在
2 错误码前缀统一 BFF_PARENT_* coord §5.2 仲裁,从 PARENT_BFF_* 迁移ai04 初稿未迁移)
3 loggerpino 复制 teacher-bff shared/observability/logger.tsservice 改 parent-bff
4 metricsprom-client /metrics 端点) 复制 teacher-bff main.ts 注册方式,指标名前缀 parent_bff_
5 tracerOpenTelemetry SDK + OTLP exporter + auto-instrumentations 复制 teacher-bff tracer.tsserviceName 改 parent-bff
6 /healthz 健康检查liveness 直接返回 okBFF 无 DB
7 /readyz 健康检查readiness含下游探针 🔧 ai05 调整P4 即补 coord §8.4 #7 推断"不检查"ai05 调整为"检查 iam + core-edu + data-ana + Redis",探针超时 1s/服务
8 优雅关闭SIGTERM main.ts 注册 SIGTERM → app.close() + shutdownTracer() + Redis client.quit()
9 测试覆盖率 ≥ 80% ⚠️ 待 P4 落地补 测试策略见 §114 层金字塔
10 Dockerfile 多阶段构建 复制 teacher-bff DockerfileEXPOSE 改 3010
11 Zod 输入验证 GraphQL Yoga 内置参数校验 + Zod schema 二次校验(双重保障)
12 GlobalErrorFilter 统一兜底 @Catch() 全局过滤器ActionState 信封含 traceId
13 ESM .js 后缀 import tsconfig.json "module": "NodeNext",所有相对 import 带 .js
14 import type 纯类型导入 express 的 Request/Response/NextFunction 改为 import type
15 环境变量 Zod 校验 src/config/env.ts 完整 Zod schema见 §12
16 ActionState 统一响应信封 `{success: true, data: T}

14. 待 coord 仲裁的新决策清单ai05 提请8 项)

以下 8 项为 ai05 复审过程中新识别的决策点,不属于 coord §8.4 范围,提请 coord 仲裁。

# 决策点 ai05 建议 阻塞阶段 影响范围
1 iam 补 GetEffectiveAccess(parentUserId) → {perms, viewports, dataScope, children} 聚合 RPC 强烈建议(减少 2 次 RTTGetChildrenByParent + GetViewports + GetEffectivePermissions 合一) P4 iamai06 补全)+ parent-bff调用方
2 core-edu 补 GetClassesByStudent(student_id) → Class[] RPC 强烈建议parent-bff 拉子女考试/作业需先 childId→classId 转换,当前缺此 RPC P4 core-eduai08 补全)+ parent-bff + student-bff
3 data-ana 补 GetParentDashboard(parent_id) → ParentDashboard 聚合 RPC 建议(多子女场景防 N+1N 次 GetStudentWeakness 调用 → 1 次聚合 RPC P4 data-anaai11 补全)+ parent-bff
4 iam 补 parent 角色权限映射 或 确认采用 coord §16.5 #4 仲裁豁免 二选一①iam 补 PARENT_CHILD_READ / PARENT_CHILD_SWITCH / PARENT_NOTIFICATION_PREFERENCE_MANAGE 权限点;②确认采用 coord §16.5 #4 仲裁BFF 豁免,权限下沉下游) P4 iamai06+ parent-bff
5 ChildGuard 缓存击穿保护singleflight 模式) 启用(防止缓存过期瞬间 N 个请求同时调 iam.GetChildrenByParent P4 parent-bff 独立决策,无需 coord 仲裁,记录在此
6 多子女切换跨标签同步 前端方案BroadcastChannel + LWWBFF 仅提供 selectChild 审计 mutation P4 parent-portalai15+ parent-bff
7 GraphQL Schema 版本化策略 @deprecated + Schema Registry + Breaking Change 检测CI 强制) P5+ parent-bff + parent-portalai15
8 msg 服务 P5 落地后 parent-bff 接入方式 推荐 gRPC 50056性能优于 HTTPproto 契约一致);兼容方案 HTTP RESTmsg gRPC server 未就绪时降级) P5 msgai10+ parent-bff

14.1 ai05 提请的 P0 阻塞项(已在 §8.1 列出,此处重申)

# 阻塞项 阻塞阶段 责任方 缓解措施
P0-1 iam 缺失 GetChildrenByParent RPC + iam_student_guardians P4 ai06iam 现归属) coord 协调 ai06 在 P3 阶段补全parent-bff P4 落地前)
P0-2 data-ana 缺失 4 端 Dashboard + SubscribeMasteryUpdate stream RPC P4 ai11 coord 协调 ai11 在 P4 启动前补全
P0-3 api-gateway 缺失 /parent 路由注册 P4 ai01 coord 协调 ai01 在 P4 启动前补 main.go + config.go

15. 设计自评估ai05 补全)

对照"通用架构设计标准 12 维度全覆盖 + 长远铺垫维度"自检。

15.1 12 维度覆盖率

# 维度 覆盖 章节 说明
1 设计原则 §0 设计原则摘要 + §10.6 长远原则 6 项原则
2 模块内部分层图 §1 mermaid + 目录结构 P4 目标态
3 领域模型 §2 BFF 无聚合根ParentSession 值对象
4 数据模型 §3 无 DBRedis 缓存 + DTO
5 API 设计 §4 GraphQL Schema 完整
6 事件设计 §5 P4 不订阅P5 Kafka consumer
7 横切关注点 §6 16 项 checklist§13
8 跨模块交互点 §7 完整契约矩阵 22 项
9 风险与假设 §8 P0 阻塞 + 技术风险 + 外部依赖假设
10 演进路线 §10.1-§10.5 P4-P7+ 路线图 + 10 项长远预留
11 测试策略 §11 4 层金字塔 + 10 关键用例 + Mock 策略
12 实施计划 §10.2-§10.4 P4 MVP 8 项 + P5 6 项 + P6 5 项

15.2 长远铺垫维度覆盖率

# 维度 覆盖 章节
1 状态机预留 §2BFF 无状态机,仅审计日志)
2 版本化 §14 #7GraphQL Schema 版本化) + §10.5 #7API 版本化)
3 多租户 §10.5 #1缓存 key + Schema 字段预留)
4 国际化i18n §10.5 #3错误码英文 + message i18n key
5 合规审计 §10.5 #2selectChild 审计 + 未来全量审计)
6 AI 集成 §10.5 #5AI 家长助手P5+ 评估)
7 演进路径 §10.1P4-P7+ 路线图 mermaid
8 可扩展抽象 §10.5 #10Client 抽象层)
9 容灾与高可用 §10.5 #8无状态 + 跨可用区部署)
10 降级模式 §10.5 #94 类降级矩阵)

15.3 自评估结论

parent-bff 02-architecture-design.md 覆盖 12 维度全覆盖 + 10 项长远铺垫,符合"通用架构设计标准 + 为未来可能发生的做好铺垫"要求。coord §0-§8 已建立 P4 即时实现设计ai05 §9-§15 补全长远演进 + 测试 + 配置 + checklist + 自评估,文档完整度达 ai-allocation.md §7 模板要求 + 用户"长远全面"标准。


AI Agent: ai05parent-bff Coordinator: coord-ai§0-§8 代笔推断 + §9-§15 ai05 复审补全) Branch: 单仓库并行模式(直接 push main 关联阶段 1 文档: 01-understanding.mdai04 撰写 + ai05 复审修订) coord 代笔日期: 2026-07-09 ai05 复审日期: 2026-07-09 深夜 文档版本: v2coord 推断 v1 + ai05 复审 v2 阶段 3 进入条件: 本文档经 coord 交叉审查通过后进入阶段 3 实施

本文件由 coord 基于 ai04 阶段 1 交付(01-understanding.md+ teacher-bff/student-bff 模式 + 用户 4 项仲裁决策推断生成,非 ai05 原创交付。

推断依据

  1. ai04 阶段 1 交付01-understanding.md 已识别 parent-bff 位置、限界上下文、契约缺口、P0 阻塞项。本设计文档基于该理解深化。
  2. teacher-bff/student-bff 模式参考:分层结构、目录结构、横切关注点、错误码清单、可观测性设计参考两份 02-architecture-design.md。
  3. 用户 4 项仲裁决策U1-U4+ coord 补充仲裁C1-C6见 §0.1 已仲裁决策表。

待 ai05 review 事项

coord 推断过程中产生的 10 项设计决策见 §8.4。ai05 接手后应:逐项确认 §8.4 决策;校验与 01-understanding.md 一致性(冲突以本文档为准);补充 ai05 视角细节GraphQL Resolver 实现、DataLoader 批量策略);重大修改需重新提交 coord 交叉审查。

重点 review 项§4.2 GraphQL Schemacoord 基于家长场景推断,可能未覆盖 ai05 全部场景、ChildGuard 实现细节(同步阻塞 vs 预加载绑定列表、通知偏好过滤位置P5 EventSubscriber 内 vs msg 服务侧)。

后续动作

  1. ai05 review:确认或修改 §8.4 的 10 项决策
  2. coord 同步 004parent-bff 依赖图状态改为" 已实现",依赖扩展为 iam + core-edu + data-ana + msgC6
  3. coord 协调 ai02P0 阻塞项——iam 补 GetChildrenByParent RPC + iam_student_guardians
  4. 阶段 3 进入条件:本文档经 ai05 review + coord 交叉审查通过后进入阶段 3

AI Agent: ai05coord 代笔) Coordinator: coord-ai Branch: 单仓库并行模式(直接 push main 关联阶段 1 文档: 01-understanding.mdai04 撰写) 代笔日期: 2026-07-09