29 KiB
parent-bff 模块上下游依赖与工作清单(Next Steps v2)
版本:v2 日期:2026-07-14 负责人:ai04 关联:
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-14,DEV_MODE=false,不使用 mock):
- ✅ 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/graphqlGraphQL 查询正常响应({ __typename }→{ data: { __typename: "Query" } }) - ✅
/v1/graphql扩展查询正常({ myChildren { id } }→{ data: { myChildren: [] } }) - ✅
/v1/graphqlMutation 正常(mutation { markAllAsRead { count } }→{ data: { markAllAsRead: { count: 0 } } }) - ✅
/metrics返回 Prometheus 指标(graphql_requests_total + graphql_duration_seconds) - ✅ 优雅降级:下游 gRPC 不可达时 resolver 返回空数据而非崩溃
2. 已完成工作(v2 全部完成)
2.1 P0 阻塞项(已解决)
| # | 工作项 | 状态 | 实现详情 |
|---|---|---|---|
| 1 | GraphQL 端点路径修复(/graphql → /v1/graphql) |
✅ | controller + yoga + module 三处路径改为 v1/graphql,对齐 api-gateway registerBffProxy 剥离策略 |
| 2 | GraphQL Schema 扩展至 32 Query + 6 Mutation | ✅ | packages/shared-ts/contracts/graphql/parent-bff.graphql 已扩展,对齐 parent-portal 全部 operations |
| 3 | TypeScript 类型定义扩展(37+ 个新类型) | ✅ | src/graphql/types.ts 新增 ChildBrief/ChildSummary/ChildDetail/ChildGrade/AttendanceRecord 等 37+ 类型 |
| 4 | 全部新 Query/Mutation resolver 实现(37 个 resolver builder) | ✅ | src/graphql/resolvers/extended-resolvers.ts 实现 21 个 Query + 6 个 Mutation resolver |
| 5 | Legacy resolver 修复(children/child/selectChild/Child.analytics) | ✅ | index.ts + select-child.resolver.ts + child.resolver.ts 恢复并接通真实下游 |
| 6 | JSON scalar 支持 | ✅ | resolvers/index.ts 新增 JSONScalar(用于 NotificationPreferences.preferences 等动态结构) |
| 7 | Response Mapper 更新 | ✅ | mapParent 增加 permissions/schoolId,新增 mapChildBrief |
| 8 | Notification resolver 兼容旧版/新版 schema | ✅ | notification.resolver.ts + notification-preference.resolver.ts 同时填充两组字段 |
| 9 | Dockerfile 构建修复 | ✅ | 构建上下文改为 repo root,复制 shared-proto + shared-ts + tsconfig.base.json |
| 10 | Proto/Schema 路径解析修复 | ✅ | grpc.factory.ts + schema.ts 改用 process.cwd() 解析,兼容开发/生产模式 |
| 11 | pino 导入修复 | ✅ | logger.ts 从 import pino from "pino" 改为 import { pino } from "pino"(ESM 兼容) |
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") |
Proto/Schema 路径解析(process.cwd() 方式):
| 文件 | 路径解析 |
|---|---|
src/graphql/schema.ts |
process.cwd() + ../../packages/shared-ts/contracts/graphql/parent-bff.graphql |
src/clients/grpc/grpc.factory.ts |
process.cwd() + ../../packages/shared-proto/proto |
Dockerfile 关键修复:
- 构建上下文改为 repo root(访问 packages/shared-proto + packages/shared-ts)
- 复制
tsconfig.base.json(tsconfig.jsonextends../../tsconfig.base.json) - runtime stage:
--ignore-workspace --ignore-scripts(避免 workspace 解析 + 跳过 native 构建) - 运行时保留
packages/shared-proto+packages/shared-ts目录结构
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/currentUser 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/currentUser/children/myChildren 等核心 Query 降级返回空数据。
4.2 core-edu 服务(ai07 负责,gRPC :50053)— P0
| # | RPC 方法 | 用途 | 状态 | 说明 |
|---|---|---|---|---|
| 1 | listGradesByStudent(studentId) |
孩子成绩列表 | ✅ | 用于 childGrades/childSummary/childDetail |
| 2 | listHomeworkByClass(classId) |
班级作业列表 | ✅ | 用于 childHomework/childSummary/childDetail |
| 3 | listExamsByClass(classId) |
班级考试列表 | ✅ | 用于 childExams/childSummary/childDetail |
| 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/notifications |
| 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=30)。
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 镜像构建
# 在仓库根目录执行(需要访问 packages/shared-proto + packages/shared-ts + tsconfig.base.json)
docker build -t edu/parent-bff:test -f services/parent-bff/Dockerfile .
5.2 容器启动
# 加入 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 健康检查验证
# 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",...},"redis":{"status":"up",...}}}
# Prometheus 指标
curl http://localhost:3010/metrics
5.4 GraphQL 端点验证
# 必须携带 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":"{ myChildren { id name } }"}'
# {"data":{"myChildren":[]}}
# Mutation
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":"mutation { markAllAsRead { count } }"}'
# {"data":{"markAllAsRead":{"count":0}}}
6. 后续其他模块配合工作
6.1 待下游服务补全的 RPC(P1)
| 服务 | 待补全 RPC | 用途 | 影响 Query/Mutation |
|---|---|---|---|
| core-edu | AttendanceService.ListAttendance |
孩子考勤记录 | childAttendance |
| core-edu | ExamResultService.GetExamResult |
考试成绩详情 | childExamResult |
| core-edu | ReportCardService.GetReportCard |
成绩单 | childReportCard |
| core-edu | LeaveRequestService.List/Create |
请假记录 | childLeaveRequests/createLeaveRequest |
| core-edu | AcademicYearService.List |
学年列表 | academicYears |
| core-edu | ClassService.GetClass |
班级信息(ISSUE-008) | childClasses |
| core-edu | ExportService.ExportGrades |
成绩导出 | exportChildGrades |
| data-ana | GrowthArchiveService.Get |
成长档案 | childGrowthArchive |
| data-ana | LearningPathService.List |
学习路径 | childLearningPath |
| data-ana | ErrorBookService.GetStats/List |
错题本统计/列表 | childErrorBookStats/childTopWrongQuestions |
| data-ana | WeakKpsService.List |
薄弱知识点 | childWeakKps |
| data-ana | MasteryService.GetSummary |
掌握度汇总 | childMasterySummary |
| data-ana | DiagnosticService.List |
诊断报告 | childDiagnosticReports |
| data-ana | PracticeService.GetStats/List |
练习统计/会话 | childPracticeStats/childPracticeSessions |
| content | CoursePlanService.List/Get |
课程计划 | childCoursePlans/childCoursePlanDetail |
| content | LessonPlanService.List/Get |
备课计划 | childLessonPlans/childLessonPlanDetail |
| content | ElectiveService.List |
选修课 | childElective |
| msg | NotificationPreferencesService |
通知偏好查询/更新 | myNotificationPreferences/updateMyNotificationPreferences |
6.2 端到端联调待办(P1)
| # | 联调项 | 触发条件 |
|---|---|---|
| 1 | iam 服务容器启动 + JWKS 端点就绪 | iam 服务 Docker 化 |
| 2 | core-edu 服务容器启动 + gRPC 端口就绪 | core-edu 服务 Docker 化 |
| 3 | data-ana 服务容器启动 + gRPC 端口就绪 | data-ana 服务 Docker 化 |
| 4 | msg 服务容器启动 + gRPC 端口就绪 | msg 服务 Docker 化 |
| 5 | parent-bff 加入 deploy compose | infra/docker-compose.deploy.yml 补充 parent-bff 服务定义 |
| 6 | parent-portal → api-gateway → parent-bff 端到端 | 所有服务容器就绪后执行 |
6.3 给下游模块的工作要求
给 iam(ai06):
- 补全
getChildrenByParentRPC(返回 ChildDto 列表,含 id/name/grade/classId/className/gradeId) - 补全
getViewportsRPC(返回 ViewportDto 列表) - 补全
getEffectivePermissionsRPC(返回 permissions 数组) - 启动
/healthz端点供 parent-bff /readyz 探测
给 core-edu(ai07):
- 补全
ClassService.GetClassRPC(ISSUE-008 仲裁) - 补全
AttendanceService/ExamResultService/ReportCardService/LeaveRequestService/AcademicYearService/ExportService - 启动
/healthz端点供 parent-bff /readyz 探测
给 data-ana(ai09):
- 补全
GrowthArchiveService/LearningPathService/ErrorBookService/WeakKpsService/MasteryService/DiagnosticService/PracticeService - 启动
/healthz端点供 parent-bff /readyz 探测
给 msg(ai08):
- 补全
NotificationPreferencesService(Get/Update) - 启动
/healthz端点供 parent-bff /readyz 探测
给 api-gateway(ai01):
- 确认
/api/v1/parent/*路由已注册(已完成) - 确认
registerBffProxy("parent", ...)路径重写剥离/api/v1/parent(已完成)
给 parent-portal(ai15):
- 确认
NEXT_PUBLIC_GRAPHQL_ENDPOINT=/api/v1/parent/v1/graphql(已完成) - 确认 32 Query + 6 Mutation operations 与 parent-bff.graphql schema 对齐(已完成)
7. 关键文件路径
| 文件 | 用途 |
|---|---|
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 类型定义(37+ 个新类型) |
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 构建(多阶段,repo root 上下文) |
8. 已完成项汇总
| 工作项 | 状态 | 验证方式 |
|---|---|---|
GraphQL 端点路径修复(/v1/graphql) |
✅ | Docker 测试 /v1/graphql 返回 200 |
| GraphQL Schema 扩展(32Q + 6M) | ✅ | { __typename } 返回 Query 类型 |
| TypeScript 类型定义扩展(37+ 个新类型) | ✅ | tsc --noEmit 零错误 |
| 全部 resolver 实现(37 个 resolver builder) | ✅ | { myChildren { id } } 返回空数组(降级正常) |
| Legacy resolver 修复 | ✅ | selectChild/Child.analytics resolver 恢复 |
| JSON scalar 支持 | ✅ | NotificationPreferences.preferences 字段可用 |
| Response Mapper 更新 | ✅ | mapParent 含 permissions/schoolId,mapChildBrief 新增 |
| Notification resolver 兼容旧版/新版 schema | ✅ | 同时填充 type/content 和 eventType/body |
| Dockerfile 构建修复 | ✅ | edu/parent-bff:test 构建成功 |
| Proto/Schema 路径解析修复 | ✅ | 容器启动日志显示 gRPC client 创建成功 |
| pino 导入修复 | ✅ | 容器启动无错误 |
| 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 + graphql_duration_seconds |
| Mutation 验证 | ✅ | markAllAsRead { count } 返回 0 |
本文件由 ai04 维护。parent-bff 已完成 v2 全部工作并通过本地 Docker 测试(DEV_MODE=false,连接真实 Redis,下游 gRPC 不可达时优雅降级)。等待下游应用服务(iam/core-edu/data-ana/msg)容器就绪后即可端到端联调。