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

> AI 标识：ai05（coord 代笔推断 → ai05 复审修订正式接手）
> 阶段：阶段 2（模块架构设计）· ai05 复审修订
> coord 代笔日期：2026-07-09
> ai05 复审日期：2026-07-09 深夜
> 状态：ai05 已复审，补充 ai05 视角的长远架构演进设计，进入 coord 交叉审查
> 关联文档：
>
> - [阶段 1 理解确认书](./01-understanding.md)（ai04 撰写 + ai05 复审修订）
> - [004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)
> - [coord 交叉审查报告](../../../docs/architecture/coord-cross-review.md)
> - [ai-allocation §3.2/§5/§7 模板](../../../docs/architecture/ai-allocation.md)
> - [pending-features P4/P5](../../../docs/architecture/roadmap/pending-features.md)
> - [project_rules](../../../.trae/rules/project_rules.md)
> - [多 AI 协作指南](../../../docs/standards/multi-ai-collaboration.md)
> - 参考实现：[teacher-bff 02-architecture-design.md](../../teacher-bff/docs/02-architecture-design.md)、[student-bff 02-architecture-design.md](../../student-bff/docs/02-architecture-design.md)、[classes 黄金模板](../../classes/)
>
> **代笔与复审说明**：
>
> - **coord 代笔阶段**（2026-07-09）：ai05 未交付 02 文档，coord 基于 01-understanding.md（ai04 初稿）+ 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 黄金模板对齐 checklist（16 项自检）；(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 豁免 gRPC，msg/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.4，BFF 在前；不是 PARENT_BFF_）                                                           | coord 仲裁 |
| C2  | 端口分配            | HTTP 3010，**不暴露 gRPC**（BFF 对下游走 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   | 数据模型（无 DB，Redis 仅缓存聚合结果 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）

```mermaid
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 场景）

```mermaid
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_BOUND`（403）。详见 §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/                        # DataLoader（per-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.ts（trace/metrics/retry）
│  └─ http/push-http.client.ts        # push-gateway 走 HTTP（U2 仲裁）
├─ shared/
│  ├─ errors/                         # application-error.ts（BFF_PARENT_*）+ global-error.filter.ts
│  ├─ health/health.controller.ts     # /healthz + /readyz（BFF 直接 ok）
│  ├─ cache/                          # redis.client.ts + cache-key.builder.ts（bff:parent:*）
│  ├─ observability/                  # logger.ts（pino）+ metrics.ts（parent_bff_*）+ tracer.ts（OTel）
│  └─ 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 → Downstream；Cache 横向被 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（前端管理 childId，BFF 无状态）。ParentSession 仅作为 GraphQL context 内的**请求级值对象**存在，不持久化到 Redis 会话。

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

**设计要点**：

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

### 2.3 DataScope=CHILDREN 越权校验（BFF 层强制）

BFF 不做权限决策（U4 豁免 `@RequirePermission`），但做**越权防御**（与 student-bff 的 SELF 防御同理，CHILDREN 更复杂）：

```typescript
// 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 聚合视图间关系

```mermaid
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（方案 A：BFF 无状态，不存会话）。本节定义 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 缓存失效策略

```mermaid
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 字段对齐。下游响应包装 `DownstreamEnvelopeSchema`（success/data/error）复用 teacher-bff 实现，不再赘述。

**家长端核心 DTO**（`parent/dto/parent-dashboard.dto.ts`）：`ParentDashboardSchema` 含 `parent`（id/name/avatar）、`children`（id/name/grade/class/lastGrade）、`unreadNotifications`，字段与 GraphQL `DashboardData` type 对齐。

**输入 Schema**（`parent/dto/parent-inputs.dto.ts`）：

- `UpdateNotificationPreferencesSchema`：channels（app/sms/email/wechat 枚举数组，min 1）+ eventTypes（5 个布尔开关：gradeReleased/homeworkGraded/examPublished/attendanceAlert/schoolAnnouncement）
- `ListGradesQuerySchema`：childId（min 1）、page（≥1，默认 1）、pageSize（1-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）。
>
> **用户仲裁 U3**：**GraphQL P2 立即引入**（覆盖 ai04 建议，恢复 pending-features P2 原计划）→ parent-bff 在 P4 落地时**直接用 GraphQL**，不走 REST 过渡期。
>
> **本设计文档执行 U3**：parent-bff P4 落地即 GraphQL Yoga + DataLoader，不实现 REST 业务端点（仅保留 /healthz /readyz /metrics 基础端点）。

### 4.2 GraphQL Schema

```graphql
# ============ 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_*"`：

```json
{
  "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）

```mermaid
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 豁免 gRPC，parent-bff 调用 push-gateway 走 **HTTP `/internal/push`**（不是 gRPC）。

### 5.4 通知偏好过滤逻辑

P5 订阅事件时，根据家长 NotificationPreferences 过滤（`kafka/handlers/notification-push.handler.ts`）：

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

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

```yaml
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_FOUND`、`CORE_EDU_EXAM_NOT_FOUND` |
| data-ana | `DATA_ANA_*` | `DATA_ANA_ANALYTICS_NOT_READY`                        |
| msg      | `MSG_*`      | `MSG_NOTIFICATION_NOT_FOUND`                          |

### 6.3 Logger（pino）

| 项           | 值                                                                               |
| ------------ | -------------------------------------------------------------------------------- |
| 文件位置     | `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 Metrics（prom-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`        | Counter（P5） | `topic, event_type`        | 事件消费数              |
| `parent_bff_event_pushed_total`          | Counter（P5） | `topic, push_status`       | 推送数                  |

### 6.5 Tracer（OpenTelemetry）

| 项                    | 值                                                                                |
| --------------------- | --------------------------------------------------------------------------------- |
| 文件位置              | `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 Context（traceparent 头 → gRPC metadata）                               |

### 6.6 /healthz 与 /readyz

| 端点       | 检查逻辑                                                                     | 响应                                                        |
| ---------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `/healthz` | 进程存活（直接返回 ok）                                                      | `200 { status: 'ok', service: 'parent-bff', timestamp }`    |
| `/readyz`  | **直接返回 ok**（对齐 teacher-bff，BFF 无 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. 停止消费 Kafka（commit 最后 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 二次校验（双重保障），自定义 scalar（DateTime、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. 注入 traceId（从 `x-request-id` 头或新生成）

### 6.10 Dockerfile（多阶段构建）

复制 teacher-bff Dockerfile，仅改 `EXPOSE 3010`（对齐 C2 端口仲裁）。多阶段构建：builder 阶段 `pnpm install --frozen-lockfile` + `pnpm run build`；runtime 阶段 `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 50052（P4+）   | 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   | **HTTP**（U2 仲裁） | `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 服务定义 | coord（infra） | P4       | 端口 3010，加入 edu-net + edu-shared 网络                                                               |
| 5   | full-stack-runbook 端口矩阵更新                    | coord（docs）  | P4       | 追加 3010 行                                                                                            |
| 6   | 004 §4 依赖图更新                                  | coord（docs）  | P4       | parent-bff 依赖扩展为 iam + core-edu + data-ana + msg（C6 仲裁），状态从"📐 需设计"改为"✅ 已实现"      |
| 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/push`（U2 仲裁） | ai01           | P5       | parent-bff P5 推送依赖                                                                                  |

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

| 维度       | teacher-bff                                    | student-bff                                    | parent-bff（本模块）                           |
| ---------- | ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- |
| API 风格   | REST（P2）→ GraphQL（P4）                      | REST（P3）→ GraphQL（P6+ 可选）                | **GraphQL（P4 直接，U3 仲裁）**                |
| DataScope  | CLASS / GRADE / SCHOOL                         | SELF                                           | **CHILDREN（自定义级）**                       |
| 越权防御   | 不做（透传 x-user-id）                         | 做（强制 userId 比对）                         | **做（ChildGuard：childId ∈ 绑定列表）**       |
| 端口       | 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 + msg**（C6 仲裁） |
| 特有功能   | 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_guardians` 表；2) Repository 查询方法；3) `GET /iam/children` REST 端点；4) proto `GetChildrenByParent` RPC |
| pending-features P2 提到 `parent_student_relations` 表，但 iam.schema.ts 未实现 | 表缺失                                                                              | 高   | 同上，推动 ai02 补表                                                                                                                                                                |
| data-ana 服务未实现查询 API（analytics.proto 3 个 method 无 REST/gRPC 端点）    | P4 学情诊断端点无法实现                                                             | 中   | coord 协调 ai06 在 P4 实现 data-ana 查询 API                                                                                                                                        |
| msg 通知偏好配置接口缺失                                                        | P5 家长通知偏好无法配置                                                             | 中   | coord 协调 ai05 在 P5 补 NotificationPreferenceService                                                                                                                              |

### 8.2 技术风险

| 风险                                                                | 影响 | 概率 | 缓解措施                                                                             |
| ------------------------------------------------------------------- | ---- | ---- | ------------------------------------------------------------------------------------ |
| GraphQL Schema 演进破坏前端                                         | 高   | 中   | Schema 版本化 + deprecation 先行 + 协调前端 ai07（parent-portal）                    |
| ChildGuard 越权校验性能损耗（每次查询都调 iam.GetChildrenByParent） | 中   | 中   | Redis 缓存绑定列表 60s（`bff: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 analysis（cost ≤ 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 已创建（C5：edu.notification.sent/read/recalled/failed） | P5 事件订阅无法实现        | P5 阻塞；P4 不依赖 Kafka                     |
| 前端 parent-portal（ai07）P4 配合使用 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 ≤ 7，cost ≤ 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](./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 调用 NotificationPreferenceService；P5 msg 服务补全时实现                                                                                                                                          |
| 5   | GraphQL 查询复杂度限制                            | depth ≤ 7，cost ≤ 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/服务，任一失败返回 degraded；K8s 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/push`（U2 仲裁），不走 GraphQL Subscription（WebSocket 长连接与 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[]` RPC**（childId→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+）

```mermaid
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-service（iam/core-edu/data-ana/msg 独立 circuit）
- 🔜 HPA 2-10 副本 + podAntiAffinity
- 🔜 mTLS（Service Mesh 接入后由 Envoy 负责，parent-bff 代码无需变更）
- 🔜 SLO 监控 + 告警规则（P95 延迟 < 200ms / 错误率 < 0.1% / 可用性 > 99.9%）
- 🔜 灰度发布（按 parentId hash 路由流量百分比）

### 10.5 长远预留点（10 项，为未来可能发生的做好铺垫）

| #   | 预留点                | 当前铺垫                                                                                    | 未来触发条件                              | 演进路径                                                                                                                   |
| --- | --------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 1   | **多租户隔离**        | Redis 缓存 key 含 `parentId`，无 `tenantId`；GraphQL Schema 无 tenant 字段                  | SaaS 化 / 多校独立部署                    | 缓存 key 加 `tenantId`；GraphQL 加 `tenantId` 字段；BFF 无状态不需重构                                                     |
| 2   | **合规审计**          | selectChild mutation 已记录审计日志；未来全量审计                                           | COPPA/FERPA/PIPL/GDPR 合规要求            | 引入审计日志 service + 写 Kafka `edu.audit.parent.action` topic                                                            |
| 3   | **国际化（i18n）**    | 错误码已用英文 + traceId；message 待前端 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:50052`（Envoy 服务发现）                                         |
| 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 fallback（P6+）；④全部不可用→维护页面 |
| 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 本分**：只聚合、裁剪、协议转换，不持业务状态、不做权限决策、不直访业务 DB（004 §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 server（proto 一致）         | `@grpc/grpc-js` mock + `jest.mock` |
| core-edu gRPC | Mock server                       | 同上                               |
| data-ana gRPC | Mock server                       | 同上                               |
| msg gRPC      | Mock server（P5）                 | 同上                               |
| Redis         | Testcontainers（真实 Redis 实例） | `testcontainers/redis`             |
| Kafka         | Mock consumer（P5）               | `kafkajs` mock + `jest.mock`       |

---

## 12. 完整配置项清单（ai05 补全）

> 所有环境变量统一 Zod 校验（`src/config/env.ts`），符合黄金模板"环境校验"要求。

### 12.1 配置项分组

```typescript
// 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"),

  // === 下游服务 URL（P4 HTTP，P5+ 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 target（P5+ 启用，覆盖 URL） ===
  IamGrpcTarget: z.string().optional(), // 默认 localhost:50052
  CoreEduGrpcTarget: z.string().optional(), // 默认 localhost:50053
  DataAnaGrpcTarget: z.string().optional(), // 默认 localhost:50055
  MsgGrpcTarget: z.string().optional(), // 默认 localhost:50056（P5）

  // === 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(),

  // === Kafka（P5+ 启用） ===
  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-gateway（HTTP 模式）    |
| 下游 gRPC target | 4        | iam / core-edu / data-ana / msg（gRPC 模式，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 / topics（P5+ 启用）                  |
| CORS             | 1        | 允许的 origin（parent-portal 默认 4002）                       |

### 12.3 环境变量优先级

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

---

## 13. 黄金模板对齐 checklist（ai05 补全，16 项）

> 对照 [project_rules §3](../../../.trae/rules/project_rules.md) + [known-issues §2.2 classes 黄金模板](../../../docs/troubleshooting/known-issues.md) + [coord §16.4 黄金模板对齐表](../../../docs/architecture/coord-cross-review.md)

| #   | 对齐项                                                              | 状态                           | 说明                                                                                              |
| --- | ------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------- |
| 1   | 权限装饰器 `@RequirePermission`                                     | ⚠️ 豁免（coord §16.5 #4 仲裁） | BFF 不做权限决策，仅校验 `x-user-id` 存在                                                         |
| 2   | 错误码前缀统一 `BFF_PARENT_*`                                       | ✅                             | coord §5.2 仲裁，从 `PARENT_BFF_*` 迁移（ai04 初稿未迁移）                                        |
| 3   | logger（pino）                                                      | ✅                             | 复制 teacher-bff `shared/observability/logger.ts`，service 改 `parent-bff`                        |
| 4   | metrics（prom-client `/metrics` 端点）                              | ✅                             | 复制 teacher-bff main.ts 注册方式，指标名前缀 `parent_bff_`                                       |
| 5   | tracer（OpenTelemetry SDK + OTLP exporter + auto-instrumentations） | ✅                             | 复制 teacher-bff tracer.ts，serviceName 改 `parent-bff`                                           |
| 6   | `/healthz` 健康检查（liveness）                                     | ✅                             | 直接返回 ok，BFF 无 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 落地补                | 测试策略见 §11，4 层金字塔                                                                        |
| 10  | Dockerfile 多阶段构建                                               | ✅                             | 复制 teacher-bff Dockerfile，EXPOSE 改 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}                                                                         | {success: false, error: {code, message, details?, traceId?}}`（004 §11.5） |

---

## 14. 待 coord 仲裁的新决策清单（ai05 提请，8 项）

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

| #   | 决策点                                                                                       | ai05 建议                                                                                                                                                             | 阻塞阶段 | 影响范围                                        |
| --- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------- |
| 1   | iam 补 `GetEffectiveAccess(parentUserId) → {perms, viewports, dataScope, children}` 聚合 RPC | 强烈建议（减少 2 次 RTT：GetChildrenByParent + GetViewports + GetEffectivePermissions 合一）                                                                          | P4       | iam（ai06 补全）+ parent-bff（调用方）          |
| 2   | core-edu 补 `GetClassesByStudent(student_id) → Class[]` RPC                                  | 强烈建议（parent-bff 拉子女考试/作业需先 childId→classId 转换，当前缺此 RPC）                                                                                         | P4       | core-edu（ai08 补全）+ parent-bff + student-bff |
| 3   | data-ana 补 `GetParentDashboard(parent_id) → ParentDashboard` 聚合 RPC                       | 建议（多子女场景防 N+1：N 次 GetStudentWeakness 调用 → 1 次聚合 RPC）                                                                                                 | P4       | data-ana（ai11 补全）+ 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       | iam（ai06）+ parent-bff                         |
| 5   | ChildGuard 缓存击穿保护（singleflight 模式）                                                 | 启用（防止缓存过期瞬间 N 个请求同时调 iam.GetChildrenByParent）                                                                                                       | P4       | parent-bff 独立决策，无需 coord 仲裁，记录在此  |
| 6   | 多子女切换跨标签同步                                                                         | 前端方案（BroadcastChannel + LWW），BFF 仅提供 selectChild 审计 mutation                                                                                              | P4       | parent-portal（ai15）+ parent-bff               |
| 7   | GraphQL Schema 版本化策略                                                                    | `@deprecated` + Schema Registry + Breaking Change 检测（CI 强制）                                                                                                     | P5+      | parent-bff + parent-portal（ai15）              |
| 8   | msg 服务 P5 落地后 parent-bff 接入方式                                                       | 推荐 gRPC 50056（性能优于 HTTP，proto 契约一致）；兼容方案 HTTP REST（msg gRPC server 未就绪时降级）                                                                  | P5       | msg（ai10）+ parent-bff                         |

### 14.1 ai05 提请的 P0 阻塞项（已在 §8.1 列出，此处重申）

| #    | 阻塞项                                                           | 阻塞阶段 | 责任方             | 缓解措施                                               |
| ---- | ---------------------------------------------------------------- | -------- | ------------------ | ------------------------------------------------------ |
| P0-1 | iam 缺失 `GetChildrenByParent` RPC + `iam_student_guardians` 表  | P4       | ai06（iam 现归属） | 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                               | 无 DB，Redis 缓存 + 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   | 状态机预留     | ✅   | §2（BFF 无状态机，仅审计日志）                           |
| 2   | 版本化         | ✅   | §14 #7（GraphQL Schema 版本化） + §10.5 #7（API 版本化） |
| 3   | 多租户         | ✅   | §10.5 #1（缓存 key + Schema 字段预留）                   |
| 4   | 国际化（i18n） | ✅   | §10.5 #3（错误码英文 + message i18n key）                |
| 5   | 合规审计       | ✅   | §10.5 #2（selectChild 审计 + 未来全量审计）              |
| 6   | AI 集成        | ✅   | §10.5 #5（AI 家长助手，P5+ 评估）                        |
| 7   | 演进路径       | ✅   | §10.1（P4-P7+ 路线图 mermaid）                           |
| 8   | 可扩展抽象     | ✅   | §10.5 #10（Client 抽象层）                               |
| 9   | 容灾与高可用   | ✅   | §10.5 #8（无状态 + 跨可用区部署）                        |
| 10  | 降级模式       | ✅   | §10.5 #9（4 类降级矩阵）                                 |

### 15.3 自评估结论

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

---

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

**本文件由 coord 基于 ai04 阶段 1 交付（[01-understanding.md](./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 Schema（coord 基于家长场景推断，可能未覆盖 ai05 全部场景）、ChildGuard 实现细节（同步阻塞 vs 预加载绑定列表）、通知偏好过滤位置（P5 EventSubscriber 内 vs msg 服务侧）。

### 后续动作

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

---

**AI Agent**: ai05（coord 代笔）
**Coordinator**: coord-ai
**Branch**: 单仓库并行模式（直接 push main）
**关联阶段 1 文档**: [01-understanding.md](./01-understanding.md)（ai04 撰写）
**代笔日期**: 2026-07-09
