Files
Edu/docs/architecture/issues/contracts/student-bff_contract.md

35 KiB
Raw Permalink Blame History

student-bff 对接契约

负责人ai04 阶段P3批次 2 启动) 版本v2对齐 coord-final-decisions B1-B8 + president-final-rulings §2.2/2.3/2.4/2.6/2.7/2.8/2.9 日期2026-07-10 关联文档:


§0 裁决对齐声明

本文件已对齐以下裁决(无遗漏):

裁决编号 内容 本文件章节
B1 P2 起直接 GraphQLGraphQL Yoga + DataLoader §1.2 / §1.3
B2 首次实现即 gRPC 调用下游 §2.1 / §2.4
B3 BFF 豁免 @RequirePermission §1.6 / §1.7
B4 全部 BFF 强制自我越权防御 §1.7
B5 错误码前缀 BFF_STUDENT_ §1.6
B6 Redis 5-30s 短缓存 §7
B7 P2-P4 不订阅 KafkaP5 后再订阅 §1.5 / §2.2
B8 DownstreamClient 抽象回写 teacher-bff §2.4
G14 错误码前缀服务名大写 §1.6
F4 i18n key error.bffStudent.<code_snake> §1.6
F7 权限点命名 <RESOURCE>_<ACTION>[_<SCOPE>] §1.3.4
F9 前端用 urql/apollo 消费 GraphQL §1.2
§2.2 GraphQL schema 仲裁机制 §1.3.2 / §1.3.3
§2.3 BFF 跨阶段扩展例外 §3
§2.4 /readyz 探针按阶段扩展 §4
§2.6 降级模式方案 Bdata 内 degraded 字段) §1.4.2
§2.7 BFF 错误码语义区分3 类) §1.6 / §1.7.3
§2.8 Dashboard Query Resolver P2 实现方式 §1.3.5
§2.9 越权防御 P2 实现方式DEV_MODE 放行) §1.7.4
§5.1 admin-portal 复用 teacher-bff不占 student-bff 命名空间 §1.3.6

§1 我提供什么(对外接口)

1.1 服务基础信息

项目
服务名 student-bff
服务类型 BFF 聚合层(无 DB无 Outbox
HTTP 端口 3009
gRPC 端口 不暴露BFF 仅对下游走 gRPC不对外提供 gRPC
路由前缀 /student/*api-gateway 透传)
部署目录 services/student-bff/
角色要求 studentJWT 必需 + student 角色)

1.2 HTTP 端点

Method Path 用途 认证 实现
POST /graphql 学生 BFF GraphQL 端点B1 裁决GraphQL Yoga JWT 必需 + student 角色 P3
GET /graphql GraphQL Playground开发环境 开发环境公开 P3
GET /healthz 健康检查liveness 公开 P3
GET /readyz 就绪检查readiness按阶段扩展探针 公开 P3
GET /metrics Prometheus 指标端点 公开(生产限内网) P3

B1 裁决P2 起直接 GraphQLGraphQL Yoga + DataLoader禁止 REST → GraphQL 渐进式过渡。 B2 裁决:对下游通信首次实现即 gRPC禁止 HTTP fetch → gRPC 渐进式过渡。 F9 裁决:前端 student-portal 用 urql/apollo 消费 GraphQL。

1.3 GraphQL schema核心契约

1.3.1 schema 存放路径

强制路径packages/shared-ts/contracts/graphql/student-bff.schema.graphql

依据 president-final-rulings §2.2.13 个 BFF schema 统一存放于 packages/shared-ts/contracts/graphql/,由各 BFF 负责 AI 起草、coord 仲裁。student-bff schema 不放在 services/student-bff/src/ 下,避免前端 AI 难以发现。

1.3.2 起草与仲裁流程

依据 president-final-rulings §2.2.6

  1. 起草方ai04 在批次 1 等待期起草 student-bff GraphQL schema 草案
  2. 仲裁方coord 在批次 2 启动前仲裁第一版 schema
  3. 消费方ai14student-portal基于仲裁版 schema 消费
  4. 变更流程
    • schema 变更需 PR + ai04BFF+ ai14前端双方 review
    • 重大变更(删除字段 / 修改类型)需 coord 仲裁
    • 新增字段允许,无需 coord 仲裁,但需通知 ai14
  5. 版本管理SDL-firstschema.graphql 文件),配合 graphql-codegen 生成 TS 类型

1.3.3 schema 设计规范

依据 president-final-rulings §2.2.5

规范项 规则
命名风格 Query/Mutation 用 camelCasestudentDashboard / myClasses
分页规范 Relay Cursor Connections{ edges, pageInfo, totalCount }
错误响应 GraphQL errors 数组 + extensions.code + extensions.traceId
权限点标注 注释形式 # @permission: DASHBOARD_VIEWF7 规范)
DataScope 标注 注释形式 # @dataScope: OWN(学生数据隔离 SELF
字段命名 Type 用 PascalCase字段用 camelCase枚举用 UPPER_SNAKE_CASE
非空与可选 必填字段用 !,可空字段不标 !(避免破坏性变更)

1.3.4 核心 Query / Mutation 域

完整 SDL 定义见 packages/shared-ts/contracts/graphql/student-bff.schema.graphqlai04 起草coord 仲裁)。

Query 域

Query 用途 聚合下游 RPC 权限点标注 DataScope
currentUser 当前学生信息 + 权限 + 视口 iam.GetUserInfo + GetEffectivePermissions + GetViewports # @permission: AUTH_READ OWN
myClasses 我的班级列表 core-edu.ClassService.GetClass + ListStudentsByClass # @permission: CLASS_READ OWN
myExams 我的考试列表 core-edu.ExamService.ListExamsByClass # @permission: EXAM_READ OWN
myHomework 我的作业列表 core-edu.HomeworkService.ListHomeworkByClass # @permission: HOMEWORK_READ OWN
myGrades 我的成绩列表 core-edu.GradeService.ListGradesByStudent # @permission: GRADE_READ OWN
myAttendance 我的考勤记录 core-edu.AttendanceService.ListAttendanceByStudent # @permission: ATTENDANCE_READ OWN
textbooks 教材列表 content.TextbookService.ListTextbooks # @permission: TEXTBOOK_READ OWN
chapters 章节列表 content.ChapterService.ListChapters # @permission: CHAPTER_READ OWN
learningPath 学习路径推荐 content.KnowledgeGraphService.GetLearningPath # @permission: LEARNING_PATH_READ OWN
studentDashboard 学生仪表盘 data-ana.AnalyticsService.GetStudentDashboard # @permission: DASHBOARD_VIEW OWN
myWeakness 我的薄弱点 data-ana.AnalyticsService.GetStudentWeakness # @permission: WEAKNESS_READ OWN
myTrend 学习趋势 data-ana.AnalyticsService.GetLearningTrend # @permission: TREND_READ OWN
myNotifications 我的通知列表 msg.NotificationService.ListNotifications # @permission: NOTIFICATION_READ OWN
myNotificationUnreadCount 通知未读数 msg.NotificationService.GetUnreadCount # @permission: NOTIFICATION_READ OWN

Mutation 域

Mutation 用途 聚合下游 RPC 权限点标注 DataScope
submitHomework 提交作业 core-edu.HomeworkService.SubmitHomework # @permission: HOMEWORK_SUBMIT OWN
markNotificationAsRead 标记通知已读 msg.NotificationService.MarkAsRead # @permission: NOTIFICATION_UPDATE OWN

分页:所有列表 QuerymyClasses / myExams / myHomework / myGrades / myAttendance / textbooks / chapters / myNotifications使用 Relay Cursor Connections 规范,返回 { edges, pageInfo, totalCount }DataScope=OWN:学生数据隔离为 SELF所有 Query 透传 x-user-id 给下游,下游 Repository 按 DataScope=SELF 过滤。

1.3.5 Dashboard Query Resolver 实现方式§2.8 裁决)

依据 president-final-rulings §2.8

  1. GraphQL schema 设计完整(含全部 Query/Mutation 字段定义P3 即定型,后续不重构
  2. Resolver 实现
    • P3 阶段studentDashboard Query Resolver 内部调 iam + core-edu gRPC返回学生基础信息 + 班级列表 + 考试列表 + 作业列表 + 成绩列表
    • P4 扩展:增加 content教材/章节)+ data-ana仪表盘/薄弱点/趋势)数据源,将 null 字段替换为真实数据
    • P5 扩展:增加 msg通知未读数+ aiAI 助教入口)数据源
    • 未启用的下游字段返回 null + extensions.warning = "field_unavailable_in_p3"
  3. 前端配合ai14 student-portal 对 null 字段做 UI 降级展示(如"数据加载中"或隐藏模块)
  4. 此方案不违反"不分阶段"schema 即最终方案Resolver 内部数据源扩展属"跨阶段扩展例外"(见 §3

1.3.6 admin-portal 命名空间

依据 president-final-rulings §5.1

  • admin-portal 复用 teacher-bff GraphQL endpoint,不新建 admin-bff 服务
  • student-bff 不预留 admin schema 命名空间admin 操作走 teacher-bff 的 admin.* 命名空间)
  • ai04 无需为 admin-portal 做任何 schema 预留

1.4 GraphQL 错误响应格式

1.4.1 GraphQL errors 数组 + ActionState 扩展

依据 president-final-rulings §2.2.3 + G8 裁决:

GraphQL 错误响应遵循标准 errors 数组格式,扩展 ActionState 字段:

{
  "errors": [
    {
      "message": "学生身份验证失败",
      "extensions": {
        "code": "BFF_STUDENT_UNAUTHORIZED",
        "traceId": "abc-123-def-456",
        "i18nKey": "error.bffStudent.unauthorized",
        "severity": "error"
      }
    }
  ],
  "data": null
}
字段 类型 说明
extensions.code string 错误码BFF_STUDENT_* 前缀,见 §1.6
extensions.traceId string 全链路追踪 ID由 Gateway 注入 X-Request-Id
extensions.i18nKey string i18n keyF4 规范:error.bffStudent.<code_snake>
extensions.severity string error / warning / info

1.4.2 降级模式(方案 B§2.6 裁决)

依据 president-final-rulings §2.6:当下游服务不可用但需返回部分数据时,采用方案 Bsuccess=true + error=null + data 内 degraded 字段):

{
  "data": {
    "studentDashboard": {
      "user": { "id": "stu-001", "name": "李同学" },
      "classes": [{ "id": "cls-001", "name": "高三1班" }],
      "weakness": null,
      "degraded": true,
      "degradedReason": "data_ana_unavailable",
      "degradedFields": ["weakness"]
    }
  }
}

规则

  1. 降级时 HTTP 200GraphQL data 非 null
  2. 降级字段返回 null,并在父对象内加 degraded: true + degradedReason: string + degradedFields: string[]
  3. 前端检查 data.degraded 判断降级,对 degradedFields 内字段做 UI 降级展示
  4. 降级场景示例:
    • data-ana gRPC 不可用 → studentDashboard.weakness / myTrend / myWeakness 降级
    • content gRPC 不可用 → textbooks / chapters / learningPath 降级
    • msg gRPC 不可用 → myNotifications / myNotificationUnreadCount 降级

1.5 Kafka 事件发布

。student-bff 是纯聚合层,不发布 Kafka 事件,不写 Outbox。

1.6 错误码前缀与列表

依据 B5 + G14 + F4 + §2.7 裁决:

错误码前缀BFF_STUDENT_(统一 BFF_ 前缀,服务名大写)

i18n key 规范error.bffStudent.<code_snake>F4 裁决)

错误码清单

错误码 HTTP 场景 i18n key
BFF_STUDENT_UNAUTHORIZED 401 x-user-id 缺失或无效 error.bffStudent.unauthorized
BFF_STUDENT_FORBIDDEN_RESOURCE 403 学生越权访问他人数据(场景 A error.bffStudent.forbidden_resource
BFF_STUDENT_IDENTITY_MISMATCH 403 JWT userId 与请求 body userId 不一致(场景 B error.bffStudent.identity_mismatch
BFF_STUDENT_BAD_GATEWAY 502 下游 gRPC 调用失败(非业务错误) error.bffStudent.bad_gateway
BFF_STUDENT_UPSTREAM_UNAVAILABLE 503 下游服务不可用(降级模式触发) error.bffStudent.upstream_unavailable
BFF_STUDENT_AGGREGATION_FAILED 500 聚合逻辑异常(未知错误) error.bffStudent.aggregation_failed
BFF_STUDENT_VALIDATION_ERROR 400 输入参数校验失败Zod 校验) error.bffStudent.validation_error
BFF_STUDENT_GRAPHQL_PARSE_ERROR 400 GraphQL 语法解析错误 error.bffStudent.graphql_parse_error
BFF_STUDENT_GRAPHQL_VALIDATION_ERROR 400 GraphQL 字段类型校验错误 error.bffStudent.graphql_validation_error
BFF_STUDENT_RATE_LIMITED 429 限流触发Gateway 层处理BFF 兜底) error.bffStudent.rate_limited

B3 裁决澄清BFF 豁免 @RequirePermission 指不做"功能权限决策"(如"能否查看仪表盘"),但必须做"数据权限防御"(如"只能看自己的数据"),见 §1.7。

1.7 BFF 越权防御契约B4 裁决)

1.7.1 越权防御场景

依据 B4 + §2.7 裁决student-bff 强制自我越权防御:

场景 描述 防御方式
A 学生查询他人数据(如 query 传入非自己 userId AuthorizationGuard 比对 x-user-id 与查询参数
B JWT userId 与请求 body userId 不一致 Mutation 入参校验,拒绝不一致请求

1.7.2 AuthorizationGuard 接口

依据 §2.9 裁决:

// services/student-bff/src/middleware/authorization.guard.ts
export interface AuthorizationGuard {
  /**
   * 校验学生是否有权访问指定资源
   * @param currentUserId 从 x-user-id header 获取
   * @param resourceUserId 查询参数中的 userId
   * @returns true 允许访问false 拒绝
   */
  canAccessSelfData(currentUserId: string, resourceUserId: string): Promise<boolean>;
}

1.7.3 3 类错误码语义§2.7 裁决)

错误码 HTTP 场景 触发条件
BFF_STUDENT_UNAUTHORIZED 401 x-user-id 缺失或无效 header 无 x-user-id 或为空
BFF_STUDENT_FORBIDDEN_RESOURCE 403 学生越权访问他人数据(场景 A canAccessSelfData 返回 false
BFF_STUDENT_IDENTITY_MISMATCH 403 JWT userId 与请求 body userId 不一致(场景 B Mutation submitHomework 等 body userId 不匹配

1.7.4 P3 实现方式§2.9 裁决)

依据 §2.9 裁决:

  1. P3 抽象 AuthorizationGuard 接口canAccessSelfData,见 §1.7.2
  2. P3 内部实现为"DEV_MODE 放行 + 生产拒绝"(保守策略):
    • DEV_MODE=true:放行所有请求,仅记录 warn 日志
    • DEV_MODE=false:严格校验,越权返回 BFF_STUDENT_FORBIDDEN_RESOURCE
  3. Redis 缓存P3 后期接入):
    • key: authz:student:{userId}
    • value: 用户基础信息userId / roles / classIds
    • TTL: 5min
    • AuthorizationGuard 优先查缓存,缓存未命中调 iam gRPC
  4. 此方案不违反"不分阶段"Guard 接口即最终方案P3→P3 后期仅替换内部实现(属"跨阶段扩展例外",见 §3

P3 阶段生产环境Guard 全部拒绝时student-bff P3 端到端验证仅限 DEV_MODE。生产环境 P3 不接入流量(仅 dev 测试P3 后期接入真实校验后正式上线。


§2 我消费什么(依赖上游)

2.1 gRPC 调用(同步)

依据 B2 裁决student-bff 对下游全部走 gRPC禁止 HTTP fetch

被调用方 Service.RPC 用途 启用阶段 mock 策略
iam (ai06) IamService.GetUserInfo 获取当前学生信息 P3 iam 就绪前返回固定 UserInfostudent 角色)
iam (ai06) IamService.GetEffectivePermissions 权限校验 P3 iam 就绪前返回学生权限集
iam (ai06) IamService.GetViewports 学生导航菜单 P3 iam 就绪前返回固定视口列表
iam (ai06) IamService.GetEffectiveDataScope DataScope 透传 P3 iam 就绪前返回 SELF
core-edu (ai08) ClassService.GetClass 我的班级详情 P3 core-edu 就绪前返回固定 ClassInfo
core-edu (ai08) ClassService.ListStudentsByClass 班级同学名单 P3 core-edu 就绪前返回固定 30 个 StudentInfo
core-edu (ai08) ExamService.ListExamsByClass 我的考试 P3 core-edu 就绪前返回固定 2 个 Exam
core-edu (ai08) HomeworkService.ListHomeworkByClass 我的作业 P3 core-edu 就绪前返回固定 3 个 Homework
core-edu (ai08) HomeworkService.SubmitHomework 提交作业 P3 core-edu 就绪前返回 success=true
core-edu (ai08) GradeService.ListGradesByStudent 我的成绩 P3 core-edu 就绪前返回固定 5 个 Grade
core-edu (ai08) AttendanceService.ListAttendanceByStudent 我的考勤 P3 core-edu 就绪前返回固定 10 条 Attendance
content (ai09) TextbookService.ListTextbooks 教材列表 P4 content 就绪前返回固定 5 个教材
content (ai09) ChapterService.ListChapters 章节列表 P4 content 就绪前返回固定章节树
content (ai09) KnowledgeGraphService.GetLearningPath 学习路径 P4 content 就绪前返回固定 8 个知识点推荐顺序
data-ana (ai11) AnalyticsService.GetStudentDashboard 学生仪表盘 P4 data-ana 就绪前返回固定仪表盘
data-ana (ai11) AnalyticsService.GetStudentWeakness 我的薄弱点 P4 data-ana 就绪前返回固定 3 个 weak_points
data-ana (ai11) AnalyticsService.GetLearningTrend 学习趋势 P4 data-ana 就绪前返回固定趋势数据
msg (ai10) NotificationService.ListNotifications 学生通知 P5 msg 就绪前返回固定 10 条通知
msg (ai10) NotificationService.GetUnreadCount 通知未读数 P5 msg 就绪前返回固定 count=3
msg (ai10) NotificationService.MarkAsRead 标记已读 P5 msg 就绪前返回 success=true

命名对齐coord §5.2

  • AnalyticsService.GetStudentDashboard(无 Stats 后缀,统一命名)
  • KnowledgeGraphService.GetLearningPath(禁用 ContentService 命名)

2.2 Kafka 事件订阅(异步)

依据 B7 裁决:

  • P3-P4 阶段不订阅 Kafka(仅同步 gRPC 聚合)
  • P5 阶段push-gateway 落地后,评估是否订阅 Kafkaedu.identity.user.role_changed 用于权限缓存失效)

P5 可选订阅的 topic(待 P5 评估):

Topic 用途 触发动作
edu.identity.user.role_changed 学生角色变更,失效权限缓存 iam 发布student-bff 清除 Redis 缓存

P3-P4 降级方案:权限缓存用短 TTL5min兜底不订阅 Kafka 事件。

2.3 HTTP 调用

。依据 B2 裁决student-bff 对下游全部走 gRPC禁止 HTTP fetch。

2.4 DownstreamClient 抽象层B8 裁决)

依据 B8 裁决:

  1. 回写 teacher-bffDownstreamClient 作为 BFF 模式 v2 标准抽象,回写到 teacher-bff3 个 BFFteacher-bff / student-bff / parent-bff统一使用
  2. 抽象位置packages/shared-ts/src/bff/downstream-client.tscoord 维护)
  3. 核心能力
export class DownstreamClient {
  /**
   * gRPC 调用封装
   * @param service 下游服务名(如 'iam' / 'core-edu'
   * @param method RPC 方法名(如 'GetUserInfo'
   * @param request 请求 message
   * @param options 超时 / 重试 / traceId 透传
   */
  call<TRequest, TResponse>(
    service: string,
    method: string,
    request: TRequest,
    options?: CallOptions,
  ): Promise<TResponse>;
}

interface CallOptions {
  timeoutMs?: number;       // 默认 5000ms
  retryCount?: number;      // 默认 2
  retryBackoffMs?: number;  // 默认 100ms指数退避
  traceId?: string;         // 从 x-request-id header 获取
  metadata?: Record<string, string>; // gRPC metadata含 x-user-id / x-user-roles / x-dataScope
}
  1. student-bff 使用方式
// services/student-bff/src/auth/auth.service.ts
import { DownstreamClient } from '@edu/shared-ts/bff/downstream-client';

@Injectable()
export class AuthService {
  constructor(private readonly downstream: DownstreamClient) {}

  async getCurrentUser(userId: string): Promise<UserInfo> {
    return this.downstream.call('iam', 'GetUserInfo', { userId }, {
      metadata: { 'x-user-id': userId },
    });
  }
}
  1. 回写义务ai04 在 P3 实现时,将 DownstreamClient 抽象回写到 teacher-bff替换 teacher-bff 现有的散落 fetch 调用),保证 3 个 BFF 统一。

§3 跨阶段扩展例外规则§2.3 裁决)

依据 president-final-rulings §2.3

3.1 允许扩展(无需 coord 仲裁)

  1. 新增下游 gRPC 调用(新增 RPC 方法到 DownstreamClient
  2. 新增下游配置gRPC endpoint 配置)
  3. 新增 /readyz 探针(按阶段启用,见 §4
  4. Dashboard Query Resolver 内部数据源扩展null 字段 → 真实数据)
  5. AuthorizationGuard 内部实现替换DEV_MODE 放行 → 真实 gRPC 校验 + Redis 缓存)

3.2 禁止变更(需 coord 仲裁)

  1. 修改已有 RPC 调用的签名或返回类型
  2. 删除已实现的 RPC 调用(除非下游服务下线)
  3. 修改 GraphQL schema 已有字段的类型(新增字段允许,无需仲裁)
  4. 修改 /readyz 已有探针的检查项(只能新增,不能修改)

3.3 验收标准

扩展时必须:

  1. 更新 student-bff 02-architecture-design.md 下游调用矩阵§7
  2. 更新 packages/shared-ts/contracts/graphql/student-bff.schema.graphql
  3. 运行 pnpm run arch:scan 更新 arch.db
  4. coord 在批次验收时检查上述 3 项

§4 /readyz 探针按阶段扩展规则§2.4 裁决)

依据 president-final-rulings §2.4 + G2 裁决:

4.1 探针列表(按阶段)

student-bff 无 DB探针仅检查 Redis + 下游 gRPC 可达性:

阶段 探针列表 数量
P3 Redis PING + iam gRPC 50052 + core-edu gRPC 50053 3
P4 + content gRPC 50054 + data-ana gRPC 50055 5
P5 + ai gRPC 50058 + msg gRPC 50056 7

实现方式DownstreamHealthCheck 注册表模式,每个下游注册独立探针,按阶段启用(通过 ENV 过滤):

const checks: HealthCheck[] = [
  checkRedis(),
  checkGrpc('iam', 50052),
  checkGrpc('core-edu', 50053),
];
if (env.CONTENT_GRPC_ENABLED) checks.push(checkGrpc('content', 50054));
if (env.DATA_ANA_GRPC_ENABLED) checks.push(checkGrpc('data-ana', 50055));
if (env.AI_GRPC_ENABLED) checks.push(checkGrpc('ai', 50058));
if (env.MSG_GRPC_ENABLED) checks.push(checkGrpc('msg', 50056));

4.2 软失败规则

依据 §2.4.3

  • 必需依赖Redis + 已启用 gRPC 下游):失败返回 503触发 Pod 重启
  • 可选依赖(未启用 gRPC 下游 / Kafka 订阅):失败仅告警,返回 200 + body degraded: true

student-bff 软失败场景

依赖 类型 失败行为
Redis 必需 503缓存失效会影响性能但 BFF 仍可降级运行)
iam gRPC 50052 必需 503无 iam 无法做身份校验)
core-edu gRPC 50053 必需 503核心数据源
content gRPC 50054 可选P4 启用前) 200 + degraded=true
data-ana gRPC 50055 可选P4 启用前) 200 + degraded=true
ai gRPC 50058 可选P5 启用前) 200 + degraded=true
msg gRPC 50056 可选P5 启用前) 200 + degraded=true

P3 阶段content / data-ana / ai / msg 均为可选P3 /readyz 仅检查 Redis + iam + core-edu3 项)。


§5 就绪信号

5.1 我依赖的上游就绪标志

上游依赖 就绪标志 责任方 完成期限
iam gRPC 50052 iam /readyz 返回 200 + GetUserInfo RPC 可调 ai06 批次 1 完成
core-edu gRPC 50053 core-edu /readyz 返回 200 + ListExamsByClass RPC 可调 ai08 批次 2 完成
content gRPC 50054 content /readyz 返回 200 + ListTextbooks RPC 可调 ai09 批次 3 完成
data-ana gRPC 50055 data-ana /readyz 返回 200 + GetStudentDashboard RPC 可调 ai11 批次 3 完成
msg gRPC 50056 msg /readyz 返回 200 + ListNotifications RPC 可调 ai10 批次 4 完成
GraphQL schema 仲裁 coord 仲裁 student-bff schema 第一版 coord 批次 2 启动前
DownstreamClient 抽象 packages/shared-ts/src/bff/downstream-client.ts 就绪 coord 批次 1 启动前
shared-go 包 packages/shared-go/ 骨架就绪 coord 批次 0.14

5.2 我的就绪标志(供下游消费)

就绪标志 完成标准 完成阶段
student-bff GraphQL :3009 启用 /healthz 返回 200 P3
/readyz 返回 200P3 探针 3 项) Redis + iam gRPC + core-edu gRPC 全部通过 P3
GraphQL schema 可内省 POST /graphql { query: "{ __schema { types { name } } }" } 返回 schema P3
核心 Query 可执行 currentUser / myClasses / myExams / myHomework / myGrades 返回真实数据 P3
核心 Mutation 可执行 submitHomework 可执行 P3
AuthorizationGuard 接口就绪 canAccessSelfData 接口已实现DEV_MODE 放行) P3
DownstreamClient 回写 teacher-bff 完成 teacher-bff 已切换为 DownstreamClient 抽象 P3
/readyz 扩展 P4 探针5 项) + content gRPC + data-ana gRPC P4
/readyz 扩展 P5 探针7 项) + ai gRPC + msg gRPC P5

§6 Mock 策略

6.1 我提供的 mock供 student-portal 消费)

在 student-bff 真实就绪前,为 ai14student-portal提供以下 mock

GraphQL mock 实现方式GraphQL Yoga 内置 mock 模式(graphql-yogamocking 配置)或 MSW 拦截 POST /graphql

Query/Mutation mock 返回
currentUser 固定学生id="student-001", name="李同学", roles=["student"]
myClasses 固定 1 个班级id="cls-001", name="高三1班"
myExams 固定 2 个考试
myHomework 固定 3 个作业1 个待提交)
myGrades 固定 5 个成绩avg_score=85.0
myAttendance 固定 10 条考勤
studentDashboard 固定仪表盘avg_score=85.0, class_rank=5
myNotifications 固定 10 条通知3 条未读)
myNotificationUnreadCount 固定 count=3
submitHomework 返回 success=true
markNotificationAsRead 返回 success=true

P4 字段降级P3 阶段 studentDashboard.weakness / myTrend / textbooks / chapters / learningPath 返回 null + extensions.warning = "field_unavailable_in_p3"

6.2 我消费的 mock上游未就绪时

在真实上游就绪前student-bff 使用以下 mock详见 §2.1 mock 策略列):

上游 mock 实现
iam 固定 UserInfo + 学生权限集 + 固定视口 + DataScope=SELF
core-edu 固定班级/同学/考试/作业/成绩/考勤
content 固定教材/章节/学习路径P4 启用前)
data-ana 固定仪表盘/薄弱点/趋势P4 启用前)
msg 固定通知列表 + GetUnreadCount + MarkAsRead successP5 启用前)

mock 实现方式:通过 DownstreamClient 的 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用。具体:在 DownstreamClient.call() 内部检查 env.MOCK_UPSTREAM=true,若为 true 则返回 mock 数据,否则走真实 gRPC。


§7 缓存策略B6 裁决)

依据 B6 裁决 + 004 §6.2 BFF 混合读策略:

7.1 Redis 短缓存

缓存对象 key 模式 TTL 失效策略
currentUser 聚合结果 bff:student:user:{userId} 30s TTL 过期
myClasses 聚合结果 bff:student:classes:{userId} 30s TTL 过期
studentDashboard 聚合结果 bff:student:dashboard:{userId} 5s TTL 过期(实时性要求高)
权限列表 authz:student:{userId} 5min P5 订阅 Kafka 事件失效
视口配置 bff:student:viewports:{userId} 5min TTL 过期

7.2 缓存规则

  1. 仅缓存 QueryMutation 不缓存
  2. 缓存粒度:按 Query + userId 维度缓存(学生数据隔离 SELF
  3. 降级策略Redis 不可用时,直接走 gRPC 调用(不缓存),返回数据 + degraded: true(标记缓存降级)
  4. 缓存击穿防护:使用 ioredisGETSET 或单飞模式(同一 key 并发请求只发一个 gRPC 调用)

§8 变更记录

日期 版本 变更 负责人
2026-07-09 v1 初始创建 ai04
2026-07-10 v2 对齐 coord B1-B8 + president §2.2-2.9 裁决schema 路径/错误响应/越权防御/DownstreamClient/跨阶段扩展/readyz 探针/降级模式/缓存策略) ai04