chore(parent-bff): merge parent-bff module into main
Merge feat/parent-bff-ai05 into main, conflicts resolved in favor of feature branch
This commit is contained in:
@@ -481,26 +481,45 @@
|
||||
|
||||
> parent-bff 是 BFF 聚合层,无 DB,无聚合根,无 @RequirePermission(U4 仲裁豁免)。本节记录 parent-bff 特有的"场景→技术"映射。
|
||||
|
||||
| 场景 | 技术/规则 |
|
||||
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| BFF 聚合层定位 | 只聚合不持状态,无 DB,无聚合根,无 @RequirePermission(U4 仲裁豁免,权限由 Gateway JWT + 下游服务负责) |
|
||||
| GraphQL Yoga 端点 | POST /graphql(GraphQL over HTTP),对前端 GraphQL,对下游 gRPC(U3 仲裁 GraphQL P2 立即引入,parent-bff P4 直接用 GraphQL) |
|
||||
| DataScope=CHILDREN 越权校验 | ChildGuard 在 Resolver 执行前校验 requestedChildId ∈ iam.GetChildrenByParent(parentId),不绑定抛 BFF_PARENT_CHILD_NOT_BOUND(403) |
|
||||
| 多子女切换方案 A | 前端管理 childId(Zustand slice),BFF 无状态,ParentSession 仅请求级值对象不持久化;selectChild mutation 仅记录审计日志 |
|
||||
| 并行 gRPC 编排 | Promise.allSettled + 降级策略(iam 失败返回 user=null,core-edu 失败返回 grades=null),部分失败标记 partial=true |
|
||||
| DataLoader 批量去重 | per-request 实例,按 parentId 批量取孩子列表、按 studentId 批量取成绩/作业/考试,防 N+1 |
|
||||
| Redis 聚合缓存 | 30s TTL(coord 推断 60s,ai05 调整为 30s + 主动失效),key=bff:parent:{userId}:{queryHash},singleflight 防击穿 |
|
||||
| 错误码前缀 | BFF_PARENT__(coord 仲裁 C1,对齐 004 §11.4 BFF 在前风格,不是 PARENT_BFF__) |
|
||||
| 端口分配 | HTTP 3010,不暴露 gRPC(coord 仲裁 C2,BFF 对下游走 gRPC,对上游仅 HTTP/GraphQL) |
|
||||
| 下游依赖 | iam + core-edu + data-ana + msg(coord 仲裁 C6 扩展,与 student-bff 对齐) |
|
||||
| P0 阻塞 | iam 缺 GetChildrenByParent RPC + iam_student_guardians 表,ai02 补全前 parent-bff 无法启动 |
|
||||
| push-gateway 调用 | 走 HTTP /internal/push(U2 仲裁豁免 gRPC,msg 调用方改用 HTTP) |
|
||||
| Kafka topic 命名 | edu.notification.sent/read/recalled/failed(coord 仲裁 C5,废弃 edu.notification.events 抽象名) |
|
||||
| /readyz 下游探针 | P4 添加并行 ping iam + core-edu + data-ana + msg 的 /healthz(coord 推断 BFF 直接 ok,ai05 调整为 P4 加下游探针) |
|
||||
| GraphQL complexity 防御 | depth limit(≤7)+ query cost analysis(≤1000)+ persisted queries(P6 预留) |
|
||||
| 优雅关闭 | HTTP server → Redis → gRPC clients → Tracer(BFF 无 DB/Kafka,关闭顺序简化) |
|
||||
| ParentSession 值对象 | parentId/roles/dataScope:CHILDREN/requestedChildId/traceId,每次请求重建不跨请求(004 §12.1 无状态约束) |
|
||||
| 通知偏好配置 | NotificationPreference(渠道开关 channels[] + 频率限制 frequency_limit + 静默时段 quiet_hours),P4 通过 msg 服务管理 |
|
||||
| 场景 | 技术/规则 |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| BFF 聚合层定位 | 只聚合不持状态,无 DB,无聚合根,无 @RequirePermission(U4 仲裁豁免,权限由 Gateway JWT + 下游服务负责) |
|
||||
| GraphQL Yoga 端点 | POST /graphql(GraphQL over HTTP),对前端 GraphQL,对下游 gRPC(U3 仲裁 GraphQL P2 立即引入,parent-bff P4 直接用 GraphQL) |
|
||||
| DataScope=CHILDREN 越权校验 | ChildGuard 在 Resolver 执行前校验 requestedChildId ∈ iam.GetChildrenByParent(parentId),不绑定抛 BFF_PARENT_CHILD_NOT_BOUND(403) |
|
||||
| 多子女切换方案 A | 前端管理 childId(Zustand slice),BFF 无状态,ParentSession 仅请求级值对象不持久化;selectChild mutation 仅记录审计日志 |
|
||||
| 并行 gRPC 编排 | Promise.allSettled + 降级策略(iam 失败返回 user=null,core-edu 失败返回 grades=null),部分失败标记 partial=true |
|
||||
| DataLoader 批量去重 | per-request 实例,按 parentId 批量取孩子列表、按 studentId 批量取成绩/作业/考试,防 N+1 |
|
||||
| Redis 聚合缓存 | 30s TTL(coord 推断 60s,ai05 调整为 30s + 主动失效),key=bff:parent:{userId}:{queryHash},singleflight 防击穿 |
|
||||
| 错误码前缀 | BFF_PARENT__(coord 仲裁 C1,对齐 004 §11.4 BFF 在前风格,不是 PARENT_BFF__) |
|
||||
| 端口分配 | HTTP 3010,不暴露 gRPC(coord 仲裁 C2,BFF 对下游走 gRPC,对上游仅 HTTP/GraphQL) |
|
||||
| 下游依赖 | iam + core-edu + data-ana + msg(coord 仲裁 C6 扩展,与 student-bff 对齐) |
|
||||
| P0 阻塞 | iam 缺 GetChildrenByParent RPC + iam_student_guardians 表,ai02 补全前 parent-bff 无法启动 |
|
||||
| push-gateway 调用 | 走 HTTP /internal/push(U2 仲裁豁免 gRPC,msg 调用方改用 HTTP) |
|
||||
| Kafka topic 命名 | edu.notification.sent/read/recalled/failed(coord 仲裁 C5,废弃 edu.notification.events 抽象名) |
|
||||
| /readyz 下游探针 | P4 添加并行 ping iam + core-edu + data-ana + msg 的 /healthz(coord 推断 BFF 直接 ok,ai05 调整为 P4 加下游探针) |
|
||||
| GraphQL complexity 防御 | depth limit(≤7)+ query cost analysis(≤1000)+ persisted queries(P6 预留) |
|
||||
| 优雅关闭 | HTTP server → Redis → gRPC clients → Tracer(BFF 无 DB/Kafka,关闭顺序简化) |
|
||||
| ParentSession 值对象 | parentId/roles/dataScope:CHILDREN/requestedChildId/traceId,每次请求重建不跨请求(004 §12.1 无状态约束) |
|
||||
| 通知偏好配置 | NotificationPreference(渠道开关 channels[] + 频率限制 frequency_limit + 静默时段 quiet_hours),P4 通过 msg 服务管理 |
|
||||
| P5 msg gRPC 客户端 | MsgClient interface + MockMsgClient + GrpcMsgClient;NotificationPreferences RPC 未定义时回退默认偏好(避免下游未实现阻塞 BFF) |
|
||||
| P5 push-gateway HTTP 客户端 | PushHttpClient interface + MockPushHttpClient + HttpPushHttpClient(fetch + 3s timeout + AbortController) |
|
||||
| P5 通知偏好过滤 | EVENT_TYPE_MAP 映射事件→通知类型,拉取偏好后检查偏好开关 + channels 交集,跳过关闭偏好的家长推送 |
|
||||
| P5 Kafka consumer 多 handler | 同一 topic 可注册多个 handler(push + cache-invalidation),handlers 改为 `Map<string, EventHandler[]>` 支持多 handler |
|
||||
| P5 Kafka 幂等 + DLQ | event_id Redis SETNX 去重(24h TTL)+ MAX_RETRIES=3 指数退避 + DLQ `edu.parent-bff.dlq` |
|
||||
| P5 缓存失效 handler | edu.teaching.grade.recorded→grades+dashboard 失效;edu.notification.read/recalled→notifications 失效 |
|
||||
| P6 opossum 熔断器 | per-service 单例 + 事件监听(open/close/halfOpen→circuitStateGauge)+ withCircuitBreaker 包装 callWithMetrics,开启抛 503 |
|
||||
| P6 熔断器配置 | timeout 5s / errorThresholdPercentage 50% / resetTimeout 30s / volumeThreshold 10 |
|
||||
| P6 Helm chart 结构 | Chart.yaml + values.yaml + values-{dev,staging,prod}.yaml + templates/(_helpers.tpl + deployment + service + hpa + configmap) |
|
||||
| P6 podAntiAffinity | preferredDuringSchedulingIgnoredDuringExecution + weight 100 + topologyKey kubernetes.io/hostname |
|
||||
| P6 HPA 多环境 | dev: 1 replica HPA disabled;staging: 2 replicas HPA 2-5;prod: 3 replicas HPA 3-10 + CPU 70%/memory 80% |
|
||||
| P6 SLO 告警规则 | parent-bff-slo 组 9 条告警:P95>200ms / 错误率>0.1% / 可用性<99.9% / 熔断器open / 下游错误率>5% / 缓存命中率<50% / Kafka停滞 / push失败率>5% / ChildGuard突增 |
|
||||
| P6 Grafana 面板 | 11 面板:P95延迟/错误率/可用性/熔断器/缓存命中率/ChildGuard/下游QPS/下游P95/Kafka消费/push推送/GraphQL QPS |
|
||||
| ESM 模式 import 后缀 | 相对 import 用 `.js` 后缀(NodeNext 构建产物),test 文件 import 源码也用 `.js` 后缀 |
|
||||
| vitest coverage 配置 | coverage 配置在 `test` 内部(vitest v2 API),不在根层级;exclude 排除基础设施文件(gRPC clients/DataLoader/GraphQL 框架/Redis/Kafka) |
|
||||
| 测试覆盖率排除策略 | 基础设施/框架代码(gRPC client 实现/DataLoader 工厂/GraphQL 框架设置/Redis client/Kafka consumer)需 E2E 测试覆盖,单元/集成测试排除 |
|
||||
| Kafka consumer Buffer 类型 | kafkajs `message.value` 类型为 `Buffer | null`,传参需 `?? undefined`转换为`Buffer | undefined` |
|
||||
| Mock 客户端 markAsRead 类型 | `MOCK_NOTIFICATIONS[parentId]` 可能 undefined,需 `if (!list) continue`;`list[idx]` 可能 undefined,需 `const target = list[idx]; if (target)` 守卫 |
|
||||
| msg.client callWithMetrics | callWithMetrics 签名是 `(client, method, request, serviceName)`,不是 `("msg", "X", () => fn)` |
|
||||
|
||||
### 2.15 student-portal(学生端微前端 Remote,P3)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user