Files
Edu/services/parent-bff/docs/nextstep.md

346 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 是家长端聚合层 BFFBackend 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`
- ✅ 容器启动正常(端口 3010DEV_MODE=false 生产模式)
-`/healthz` 返回 200liveness 通过)
-`/readyz` 返回 200status=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-gatewayai01 负责)— 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-portalai15 负责)— 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 未定义 RPCgRPC 实现返回默认值 |
| 4 | `updateNotificationPreferences(...)` | 更新通知偏好 | ⚠️ | proto 未定义 RPCgRPC 实现返回输入 |
| 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:uplatency_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返回 200status=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容器就绪后即可端到端联调。**