# parent-bff 模块上下游依赖与工作清单(Next Steps) > 版本:v3 > 日期:2026-07-13 > 负责人:ai04 > 关联: > > - [parent-portal nextstep.md](../../../apps/parent-portal/docs/nextstep.md) > - [api-gateway nextstep.md](../../api-gateway/docs/nextstep.md) > - [ARB-022 §24.4 ISSUE-003 方案 A](../../docs/architecture/issues/coord.md) --- ## 1. 概述 parent-bff 是家长端聚合层 BFF(Backend For Frontend),端口 3010,基于 NestJS + GraphQL Yoga。负责聚合 iam / core-edu / data-ana / msg 四个下游微服务的 gRPC 接口,为 parent-portal 前端提供场景化 GraphQL API。 **路由路径(ARB-022 §24.4 ISSUE-003 方案 A 双 /v1 前缀):** ``` parent-portal → POST /api/v1/parent/v1/graphql (前端调用路径) → api-gateway registerBffProxy("parent") (剥离 /api/v1/parent) → parent-bff:3010 /v1/graphql (BFF 接收路径) → GraphqlController @Controller("v1/graphql") ``` **本地 Docker 测试结果(2026-07-13):** - ✅ TypeScript 编译零错误(`tsc --noEmit -p tsconfig.test.json`) - ✅ ESLint 零错误(`eslint src test`) - ✅ 单元测试 + 集成测试全通过(128 tests, 13 test files) - ✅ Docker 镜像构建成功(`edu/parent-bff:test`) - ✅ 容器启动正常(端口 3010,DEV_MODE=false 生产模式) - ✅ `/healthz` 返回 200(liveness 通过) - ✅ `/readyz` 返回 200(status=degraded,下游 gRPC 不可达但 Redis up) - ✅ `/v1/graphql` GraphQL 查询正常响应(`{ __typename }` → `{ data: { __typename: "Query" } }`) - ✅ `/metrics` 返回 Prometheus 指标(graphql_requests_total + downstream_calls_total) - ✅ 优雅降级:下游 gRPC 不可达时 resolver 返回空数据而非崩溃(`{ children { id } }` → `{ data: { children: [] } }`) --- ## 2. 已完成工作 ### 2.1 P0 阻塞项(已解决) | # | 工作项 | 状态 | 实现详情 | | --- | ------------------------------------------------------------------ | ---- | ---------------------------------------------------------------------------------------------------- | | 1 | GraphQL 端点路径修复(`/graphql` → `/v1/graphql`) | ✅ | controller + yoga + module 三处路径改为 `v1/graphql`,对齐 api-gateway `registerBffProxy` 剥离策略 | | 2 | Dockerfile 构建修复(pnpm-lock.yaml + husky + better-sqlite3) | ✅ | `--no-frozen-lockfile --ignore-scripts`,runtime stage 用 `--ignore-workspace --ignore-scripts` | | 3 | Proto 文件路径修复(4 级 → 5 级 `../`) | ✅ | `grpc.factory.ts` 改用 `process.cwd()` 解析,兼容开发模式和生产模式 | | 4 | GraphQL Schema 扩展至 32 Query + 6 Mutation | ✅ | `packages/shared-ts/contracts/graphql/parent-bff.graphql` 已扩展,对齐 parent-portal 全部 operations | | 5 | 全部新 Query/Mutation resolver 实现 | ✅ | `src/graphql/resolvers/extended-resolvers.ts` 37 个 resolver builder | | 6 | Legacy resolver 修复(children/child/selectChild/Child.analytics) | ✅ | `index.ts` + `select-child.resolver.ts` + `child.resolver.ts` 恢复并接通真实下游 | | 7 | TypeScript 类型定义扩展 | ✅ | `src/graphql/types.ts` 新增 `ChildType`,`ChildBriefType` 使用 `classId`/`className` | | 8 | Response Mapper 更新 | ✅ | `mapParent` 增加 permissions/schoolId,新增 `mapChildBrief` | | 9 | JSON scalar 支持 | ✅ | `resolvers/index.ts` 新增 JSONScalar | | 10 | pnpm build 通过 | ✅ | `nest build` 成功,dist/ 产物正常 | ### 2.2 关键修复详情 **GraphQL 端点路径(ARB-022 §24.4 ISSUE-003 方案 A):** | 文件 | 路径配置 | | --------------------------------- | -------------------------------- | | `src/entry/graphql.controller.ts` | `@Controller("v1/graphql")` | | `src/graphql/yoga.ts` | `graphqlEndpoint: "/v1/graphql"` | | `src/graphql/graphql.module.ts` | `forRoutes("v1/graphql")` | **Dockerfile 关键修复:** - builder stage:`pnpm install --no-frozen-lockfile --ignore-scripts`(跳过 husky prepare) - runtime stage:`pnpm install --prod --no-frozen-lockfile --ignore-workspace --ignore-scripts`(避免 workspace 解析 + 跳过 native 构建) - 运行时保留 `packages/shared-proto` + `packages/shared-ts` 目录结构(proto/schema 加载需要) --- ## 3. 上游依赖(调用 parent-bff 的模块) ### 3.1 api-gateway(ai01 负责)— P0 | # | 依赖项 | 用途 | 状态 | | --- | ------------------------------------------ | ---------------------------------------------------------------------------------- | ---- | | 1 | `/api/v1/parent/*` 反向代理路由 | 前端请求经 api-gateway 代理到 parent-bff:3010 | ✅ | | 2 | `registerBffProxy("parent", ...)` 路径重写 | 剥离 `/api/v1/parent`,转发剩余路径(`/v1/graphql`)到 parent-bff | ✅ | | 3 | JWT 鉴权 + x-user-* 头注入 | api-gateway 校验 JWT 后注入 `x-user-id`/`x-user-roles`/`x-data-scope`/`x-trace-id` | ✅ | | 4 | CORS 白名单 | `CORS_ORIGINS` 环境变量配置 | ✅ | | 5 | 限流(IP 级令牌桶) | 100 rps,突发 20 | ✅ | | 6 | 熔断(下游 5xx 触发) | `CircuitBreaker("downstream")` | ✅ | **验证要点:** - api-gateway 入站 `/api/v1/parent/v1/graphql` → 剥离 `/api/v1/parent` → 转发 `/v1/graphql` 到 parent-bff:3010 - parent-bff GraphqlController 注册在 `/v1/graphql`,接收路径匹配 - 经 api-gateway 代理访问 `http://api-gateway:8080/api/v1/parent/v1/graphql` 应返回 200 ### 3.2 parent-portal(ai15 负责)— P0 | # | 依赖项 | 用途 | 状态 | | --- | -------------------------------------- | --------------------------------------------------- | ---- | | 1 | `NEXT_PUBLIC_GRAPHQL_ENDPOINT` 配置 | 前端 GraphQL 客户端调用 `/api/v1/parent/v1/graphql` | ✅ | | 2 | 32 个 Query + 6 个 Mutation operations | 前端定义的 GraphQL 操作,需 parent-bff schema 对齐 | ✅ | | 3 | Mock 数据禁用 | `NEXT_PUBLIC_API_MOCKING=disabled`,使用真实后端 | ✅ | **前端调用路径:** ``` parent-portal → POST /api/v1/parent/v1/graphql → api-gateway 剥离 /api/v1/parent → parent-bff:3010/v1/graphql ``` --- ## 4. 下游依赖(parent-bff 调用的模块) ### 4.1 iam 服务(ai06 负责,gRPC :50052)— P0 | # | RPC 方法 | 用途 | 状态 | 说明 | | --- | --------------------------------- | ------------------------------ | ---- | -------------------------------------------------------- | | 1 | `getUserInfo(userId)` | 获取家长个人信息 | ✅ | `GrpcIamClient.getUserInfo`,用于 `me` Query | | 2 | `getChildrenByParent(parentId)` | 获取家长绑定的孩子列表 | ✅ | `GrpcIamClient.getChildrenByParent`,ChildGuard 缓存 30s | | 3 | `getViewports(userId)` | 获取家长可见视口 | ✅ | `GrpcIamClient.getViewports` | | 4 | `getEffectivePermissions(userId)` | 获取家长有效权限 | ✅ | `GrpcIamClient.getEffectivePermissions` | | 5 | `GET /healthz` 端点 | /readyz 下游健康检查 | ⏳ | iam 服务容器未运行,/readyz 报告 down | | 6 | `GET /.well-known/jwks.json` | RS256 公钥集(api-gateway 用) | ⏳ | iam 服务容器未运行 | **环境变量:** `IAM_GRPC_TARGET=iam:50052`(Docker 网络)/ `localhost:50052`(本地开发) **影响:** iam 不可达时,`me`/`children`/`myNotifications` 等核心 Query 降级返回空数据。 ### 4.2 core-edu 服务(ai07 负责,gRPC :50053)— P0 | # | RPC 方法 | 用途 | 状态 | 说明 | | --- | -------------------------------- | -------------------- | ---- | ------------------------------------------------ | | 1 | `listGradesByStudent(studentId)` | 孩子成绩列表 | ✅ | 用于 `childGrades`/`childSummary`/`childDetail` | | 2 | `listHomeworkByClass(classId)` | 班级作业列表 | ✅ | 用于 `childHomework`/`childSummary` | | 3 | `listExamsByClass(classId)` | 班级考试列表 | ✅ | 用于 `childExams`/`childSummary` | | 4 | `getClass(classId)` | 班级信息 | ⚠️ | ISSUE-008: proto 缺 ClassService,当前返回默认值 | | 5 | `GET /healthz` 端点 | /readyz 下游健康检查 | ⏳ | core-edu 服务容器未运行 | **环境变量:** `CORE_EDU_GRPC_TARGET=core-edu:50053`(Docker 网络)/ `localhost:50053`(本地开发) **降级查询:** `childAttendance`/`childExamResult`/`childReportCard`/`childLeaveRequests`/`academicYears`/`childClasses`/`createLeaveRequest`/`exportChildGrades` 在 RPC 未就绪时降级返回空数据。 ### 4.3 data-ana 服务(ai09 负责,gRPC :50055)— P1 | # | RPC 方法 | 用途 | 状态 | 说明 | | --- | ---------------------------------------------- | -------------------- | ---- | ------------------------------------- | | 1 | `getStudentWeakness(studentId, subjectId)` | 学生薄弱知识点 | ✅ | 用于 `childWeakness`/`childAnalytics` | | 2 | `getLearningTrend(studentId, start, end)` | 学习趋势 | ✅ | 用于 `childTrend`/`childAnalytics` | | 3 | `getClassPerformance(classId, subjectId, ...)` | 班级绩效 | ✅ | 用于 `classRank`/`classAverage` 计算 | | 4 | `GET /healthz` 端点 | /readyz 下游健康检查 | ⏳ | data-ana 服务容器未运行 | **环境变量:** `DATA_ANA_GRPC_TARGET=data-ana:50055`(Docker 网络)/ `localhost:50055`(本地开发) **降级查询:** `childGrowthArchive`/`childLearningPath`/`childErrorBookStats`/`childTopWrongQuestions`/`childWeakKps`/`childMasterySummary`/`childDiagnosticReports`/`childPracticeStats`/`childPracticeSessions` 在 RPC 未就绪时降级返回空数据。 ### 4.4 msg 服务(ai08 负责,gRPC :50056)— P1 | # | RPC 方法 | 用途 | 状态 | 说明 | | --- | -------------------------------------- | -------------------- | ---- | ------------------------------------------ | | 1 | `listNotifications(parentId, unread)` | 通知列表 | ✅ | 用于 `myNotifications` | | 2 | `markAsRead(notificationId)` | 标记已读 | ✅ | 用于 `markAsRead`/`markAllAsRead` Mutation | | 3 | `getNotificationPreferences(parentId)` | 通知偏好 | ⚠️ | proto 未定义 RPC,gRPC 实现返回默认值 | | 4 | `updateNotificationPreferences(...)` | 更新通知偏好 | ⚠️ | proto 未定义 RPC,gRPC 实现返回输入 | | 5 | `GET /healthz` 端点 | /readyz 下游健康检查 | ⏳ | msg 服务容器未运行 | **环境变量:** `MSG_GRPC_TARGET=msg:50056`(Docker 网络)/ `localhost:50056`(本地开发) ### 4.5 Redis(基础设施)— P0 | # | 依赖项 | 用途 | 状态 | | --- | ------------------ | --------------------------------------------------- | ---- | | 1 | `redis://...:6379` | ChildGuard 缓存 + dashboard/grades/permissions 缓存 | ✅ | **环境变量:** `REDIS_URL=redis://edu-redis:6379`(Docker 网络)/ `redis://localhost:6379`(本地开发) **验证结果:** 容器内 Redis 连接成功(`/readyz` 报告 redis:up,latency_ms=28)。 ### 4.6 降级模式说明 以下 Query 在下游 RPC 未就绪时降级返回空数据(不阻塞前端渲染): | Query | 降级行为 | 待补全的下游 RPC | | ------------------------ | ----------------- | ----------------------------- | | `childAttendance` | 返回空数组 | core-edu AttendanceService | | `childExamResult` | 返回 null | core-edu ExamResultService | | `childReportCard` | 返回 null | core-edu ReportCardService | | `childGrowthArchive` | 返回空 dataPoints | data-ana GrowthArchiveService | | `childLearningPath` | 返回空数组 | data-ana LearningPathService | | `childErrorBookStats` | 返回零值 | data-ana ErrorBookService | | `childTopWrongQuestions` | 返回空数组 | data-ana ErrorBookService | | `childWeakKps` | 返回空数组 | data-ana WeakKpsService | | `childMasterySummary` | 返回零值 | data-ana MasteryService | | `childDiagnosticReports` | 返回空数组 | data-ana DiagnosticService | | `childPracticeStats` | 返回零值 | data-ana PracticeService | | `childPracticeSessions` | 返回空数组 | data-ana PracticeService | | `childCoursePlans` | 返回空数组 | content CoursePlanService | | `childCoursePlanDetail` | 返回 null | content CoursePlanService | | `childLessonPlans` | 返回空数组 | content LessonPlanService | | `childLessonPlanDetail` | 返回 null | content LessonPlanService | | `childElective` | 返回空数组 | content ElectiveService | | `childLeaveRequests` | 返回空数组 | core-edu LeaveRequestService | | `academicYears` | 返回空数组 | core-edu AcademicYearService | | `childClasses` | 返回空数组 | classes ClassService | | `createLeaveRequest` | 返回 PENDING 状态 | core-edu LeaveRequestService | | `exportChildGrades` | 返回临时 URL | core-edu ExportService | --- ## 5. Docker 本地测试 ### 5.1 镜像构建 ```bash # 在仓库根目录执行(需要访问 packages/shared-proto + packages/shared-ts) docker build -t edu/parent-bff:test -f services/parent-bff/Dockerfile . ``` ### 5.2 容器启动 ```bash # 加入 edu-full_default 网络(与 Redis/MySQL/Kafka 等基础设施同网络) docker run -d \ --name edu-parent-bff-test \ --network edu-full_default \ -p 3010:3010 \ -e NODE_ENV=production \ -e DEV_MODE=false \ -e PORT=3010 \ -e REDIS_URL=redis://edu-redis:6379 \ -e IAM_GRPC_TARGET=iam:50052 \ -e CORE_EDU_GRPC_TARGET=core-edu:50053 \ -e DATA_ANA_GRPC_TARGET=data-ana:50055 \ -e MSG_GRPC_TARGET=msg:50056 \ -e CORS_ORIGINS=http://localhost:4002 \ -e GRAPHQL_INTROSPECTION_ENABLED=true \ edu/parent-bff:test ``` ### 5.3 健康检查验证 ```bash # liveness(返回 200) curl http://localhost:3010/healthz # {"status":"ok","service":"parent-bff","timestamp":"..."} # readiness(返回 200,status=degraded 因为下游 gRPC 不可达) curl http://localhost:3010/readyz # {"status":"degraded","checks":{"iam":{"status":"down",...},"core-edu":{"status":"down",...},"data-ana":{"status":"down",...},"redis":{"status":"up",...}}} # Prometheus 指标 curl http://localhost:3010/metrics ``` ### 5.4 GraphQL 端点验证 ```bash # 必须携带 x-user-* 头(api-gateway 注入) curl -X POST http://localhost:3010/v1/graphql \ -H "Content-Type: application/json" \ -H "x-user-id: parent-001" \ -H "x-user-roles: parent" \ -H "x-data-scope: CHILDREN:child-001" \ -H "x-trace-id: test-trace-001" \ -d '{"query":"{ __typename }"}' # {"data":{"__typename":"Query"}} # 优雅降级(下游不可达时返回空数据) curl -X POST http://localhost:3010/v1/graphql \ -H "Content-Type: application/json" \ -H "x-user-id: parent-001" \ -H "x-user-roles: parent" \ -d '{"query":"{ children { id name } }"}' # {"data":{"children":[]}} ``` ### 5.5 测试容器管理 ```bash # 停止测试容器 docker rm -f edu-parent-bff-test # 查看日志 docker logs edu-parent-bff-test --tail 50 ``` --- ## 6. 关键文件路径 | 文件 | 用途 | | -------------------------------------------------------------------- | ------------------------------------------------- | | `packages/shared-ts/contracts/graphql/parent-bff.graphql` | GraphQL Schema 契约(32Q + 6M,前后端共享唯一源) | | `services/parent-bff/src/entry/graphql.controller.ts` | Controller(路由 `v1/graphql`) | | `services/parent-bff/src/graphql/yoga.ts` | Yoga 实例(端点 `/v1/graphql`) | | `services/parent-bff/src/graphql/graphql.module.ts` | Module(中间件路由 `v1/graphql`) | | `services/parent-bff/src/graphql/schema.ts` | SDL 加载(process.cwd() 解析路径) | | `services/parent-bff/src/graphql/resolvers/index.ts` | Resolver 注册(含 DateTime + JSON scalar) | | `services/parent-bff/src/graphql/resolvers/extended-resolvers.ts` | 37 个新 resolver builder | | `services/parent-bff/src/graphql/resolvers/child.resolver.ts` | legacy childAnalytics resolver | | `services/parent-bff/src/graphql/resolvers/select-child.resolver.ts` | legacy selectChild Mutation resolver | | `services/parent-bff/src/graphql/types.ts` | TypeScript 类型定义 | | `services/parent-bff/src/aggregation/response-mapper.ts` | DTO → GraphQL Type 映射 | | `services/parent-bff/src/clients/grpc/grpc.factory.ts` | gRPC 客户端工厂(process.cwd() 解析 proto 路径) | | `services/parent-bff/src/config/env.ts` | Zod 环境变量校验 | | `services/parent-bff/Dockerfile` | Docker 构建(多阶段,--ignore-scripts) | --- ## 7. 遗留事项 | # | 事项 | 优先级 | 说明 | | --- | ------------------------------------ | ------ | ----------------------------------------------------------- | | 1 | 补全 core-edu ClassService proto | P1 | ISSUE-008: `getClass` 当前返回默认值 | | 2 | 补全 msg NotificationPreferences RPC | P2 | 通知偏好查询/更新当前降级 | | 3 | 补全 data-ana 各分析 RPC | P2 | 练习/诊断/错题本/掌握度等分析类查询当前降级 | | 4 | 补全 content 课程/备课/选修 RPC | P2 | 课程计划/备课/选修查询当前降级 | | 5 | 端到端联调 | P1 | 待 iam/core-edu/data-ana/msg 服务容器就绪后执行 | | 6 | parent-bff 加入 deploy compose | P1 | `infra/docker-compose.deploy.yml` 未包含 parent-bff,需补充 | --- ## 8. 已完成项汇总 | 工作项 | 状态 | 验证方式 | | ------------------------------------- | ---- | ----------------------------------------------------------- | | GraphQL 端点路径修复(`/v1/graphql`) | ✅ | Docker 测试 `/v1/graphql` 返回 200 | | Dockerfile 构建修复 | ✅ | `edu/parent-bff:test` 镜像构建成功 | | Proto 文件路径修复 | ✅ | 容器启动日志显示 gRPC client 创建成功 | | GraphQL Schema 扩展(32Q + 6M) | ✅ | `{ __typename }` 返回 Query 类型 | | 全部 resolver 实现 | ✅ | `{ children { id } }` 返回空数组(降级正常) | | Legacy resolver 修复 | ✅ | selectChild/Child.analytics resolver 恢复 | | TypeScript 编译零错误 | ✅ | `tsc --noEmit -p tsconfig.test.json` 通过 | | ESLint 零错误 | ✅ | `eslint src test` 通过 | | 单元测试 + 集成测试通过 | ✅ | 128 tests, 13 test files 全部通过 | | Docker 镜像构建 | ✅ | `edu/parent-bff:test` 构建成功 | | Docker 容器运行验证 | ✅ | /healthz 200 + /readyz 200 + /v1/graphql 200 + /metrics 200 | | 优雅降级验证 | ✅ | 下游 gRPC 不可达时返回空数据而非崩溃 | | 可观测性验证 | ✅ | /metrics 含 graphql_requests_total + downstream_calls_total | --- **本文件由 ai04 维护。parent-bff 已完成全部 P0 工作并通过本地 Docker 测试(DEV_MODE=false,连接真实 Redis,下游 gRPC 不可达时优雅降级)。等待下游应用服务(iam/core-edu/data-ana/msg)容器就绪后即可端到端联调。**