Merge worktree branch merge-15-modules-to-main-5ug5xJ

This commit is contained in:
SpecialX
2026-07-10 15:28:20 +08:00
parent 60d7173545
commit df62ffc176
51 changed files with 11559 additions and 1908 deletions

View File

@@ -1,49 +1,71 @@
# admin-portal 对接契约
> 负责人ai16
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](./matrix.md)、[coord.md](./coord.md)、[ai-allocation.md §5 ai16](../../ai-allocation.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 说明:本契约基于 GraphQLARB-001 admin 命名空间)+ 端口 4003 + ai16 归属,作为 01/02 文档修订基准(见 [objections/admin-portal_issue.md](../objections/admin-portal_issue.md) ISSUE-001~006
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
无。admin-portal 是前端微前端 Remote。
无。admin-portal 是前端微前端 Remote,不提供 gRPC
### 1.2 HTTP 端点(如有
### 1.2 前端路由MF Remote 暴露给 Shell 动态加载
| Method | Path | 用途 | 认证 |
| ------ | ----------- | ---------------- | --------------------- |
| GET | / | 管理后台首页 | JWT 必需 + admin 角色 |
| GET | /users | 用户管理 | JWT 必需 + admin |
| GET | /roles | 角色权限管理 | JWT 必需 + admin |
| GET | /classes | 班级管理(全局) | JWT 必需 + admin |
| GET | /teachers | 教师管理 | JWT 必需 + admin |
| GET | /students | 学生管理 | JWT 必需 + admin |
| GET | /audit-logs | 审计日志 | JWT 必需 + admin |
| GET | /dashboard | 管理员仪表盘 | JWT 必需 + admin |
| GET | /system | 系统配置 | JWT 必需 + admin |
> admin-portal 是前端,**不对外提供 HTTP 端点**。此处列出的是 admin-portal 在 Shell 路由树 `/admin/*` 下注册的前端页面路由,由 Shell 动态 import admin Remote 加载。
### 1.3 GraphQL schema如 BFF
| 路由 | 页面 | 权限点 | 数据范围 |
| ------------------- | ---------------- | ---------------------- | --------------- |
| `/admin/dashboard` | 管理员仪表盘 | `ADMIN_DASHBOARD_VIEW` | L3-L5 |
| `/admin/users` | 用户管理 | `IAM_USER_READ` | L3-L5 |
| `/admin/roles` | 角色权限管理 | `IAM_ROLE_READ` | L3-L5 |
| `/admin/permissions`| 权限点管理 | `IAM_PERMISSION_READ` | L5 |
| `/admin/viewports` | 视口配置 | `IAM_VIEWPORT_READ` | L5 |
| `/admin/organization` | 组织管理 | `ORG_MANAGE` | L3-L5 |
| `/admin/system` | 学校设置 | `ADMIN_SYSTEM_MANAGE` | L5 |
| `/admin/classes` | 班级管理(全局) | `ADMIN_CLASS_READ` | L3-L5 |
| `/admin/teachers` | 教师管理 | `ADMIN_TEACHER_READ` | L3-L5 |
| `/admin/students` | 学生管理 | `ADMIN_STUDENT_READ` | L3-L5 |
| `/admin/audit-logs` | 审计日志 | `ADMIN_AUDIT_READ` | L5 |
不适用。admin-portal 消费 teacher-bff GraphQL admin namespace自身不提供 schema
> 权限点使用 `ADMIN_` / `IAM_` / `ORG_` 前缀ai-allocation §5admin 权限点 `ADMIN_` 前缀)。`ADMIN_*` 常量由 coord 维护于 `packages/contracts/src/permissions.ts`,前端不硬编码
### 1.4 Kafka 事件发布(如有)
### 1.3 GraphQL schema
不适用。admin-portal **消费** teacher-bff GraphQL admin 命名空间,自身不提供 schema
### 1.4 Kafka 事件发布
无。前端不发布 Kafka 事件。
### 1.5 错误码前缀
无(前端不定义错误码前缀,透传 BFF 错误码)。
无(前端不定义错误码前缀,透传 BFF/网关错误码)。消费的错误码前缀见 §2.5。
### 1.6 微前端架构(补充)
### 1.6 微前端架构
| 角色 | 说明 |
| ---------------------- | ----------------------------------------------- |
| MF Remote | 管理后台是微前端远程模块 |
| 暴露的 remote 模块 | AdminApp管理后台完整应用、shared 管理端组件 |
| module federation 配置 | `apps/admin-portal/module-federation.config.ts` |
| 角色 | 说明 |
| ---- | ---- |
| MF Remote | admin-portal 是微前端远程模块,挂载到 teacher-portal Shell |
| Remote 名称 | `admin_app`Shell `remotes` 中引用为 `admin: 'admin_app@http://localhost:4003/_next/static/chunks/remoteEntry.js'` |
| 暴露模块 | `./AdminApp`(管理后台应用入口,供 Shell 动态 import |
| MF 配置位置 | `apps/admin-portal/next.config.js`NextFederationPlugin对齐 ARB-002 §2.2 配置风格) |
| sharedsingleton | react / react-dom / urql / graphql / @edu/ui-tokens / @edu/ui-components / @edu/hooks / @edu/contracts |
| 复用 Shell 暴露 | `GraphQLProvider` / `AppShell` / `useAuth` / `usePermission` / `useGraphQLClient` / `ErrorBoundary` / `RequirePermission`ARB-002 §2.2 |
> admin-portal **不暴露共享组件给 Shell**(共享组件由 Shell 统一暴露,对齐 ARB-002。admin 特有组件UserManagementTable / RolePermissionMatrix / ViewportConfigEditor仅 admin-portal 内部使用。
### 1.7 i18n 命名空间
| 命名空间 | 用途 |
| -------- | ---- |
| `admin.*` | 管理端 UI 文案(导航/按钮/表单标签等) |
| `iam.error.*` | iam 服务错误码 i18n透传 |
| `bff.error.*` | teacher-bff 错误码 i18n透传 |
| `gateway.error.*` | api-gateway 错误码 i18n透传 |
| `network.error.*` | 前端网络层错误 i18n |
---
@@ -55,28 +77,49 @@
### 2.2 Kafka 事件订阅(异步)
无。前端不直接订阅 Kafka审计日志通过 GraphQL 查询,非直接订阅)。
无。前端不直接订阅 Kafka审计日志经 teacher-bff 聚合后通过 GraphQL `auditLogs` Query 消费链路iam → Kafka `edu.iam.audit.created`**teacher-bff 消费** → GraphQL → admin-portal)。
### 2.3 HTTP 调用(如有)
> 注:[matrix.md §4](../matrix.md) 将 admin-portal 列为 `edu.iam.audit.created` 消费方,表述不精确,已提请 coord 修正为 teacher-bff见 [objections ISSUE-007](../objections/admin-portal_issue.md))。
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ----------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/admin/graphql | 管理 GraphQL 查询(经网关代理到 teacher-bff admin namespace | api-gateway/teacher-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 管理员登录 | api-gateway 就绪前使用 MSW 返回固定 JWTadmin 角色) |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
### 2.3 HTTP 调用
### 2.4 GraphQL 查询域(经 api-gateway 代理到 teacher-bff admin namespace
| 被调用方 | Method.Path | 用途 | mock 策略 |
| -------- | ----------- | ---- | --------- |
| api-gateway (ai01) | POST /api/admin/graphql | 管理 GraphQL 查询(经网关代理到 teacher-bff admin 命名空间) | api-gateway/teacher-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知(审计告警/异常登录/系统异常) | push-gateway 就绪前使用 mock-socket 模拟 WS 推送(**ISSUE-006 待 coord 仲裁** |
| Query/Mutation | 用途 | mock 策略 |
| ------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------- |
| currentUser | 当前管理员信息 | MSW 返回固定管理员admin 角色) |
| adminUsers / createUser / updateUser / deleteUser | 用户管理 | MSW 返回固定 50 个用户 + CRUD success |
| adminRoles / updateRolePermissions | 角色权限管理 | MSW 返回固定 5 个角色 + 权限矩阵 |
| adminClasses | 班级管理(全局) | MSW 返回固定 20 个班级 |
| adminTeachers | 教师管理 | MSW 返回固定 50 个教师 |
| adminStudents | 学生管理 | MSW 返回固定 1200 个学生 |
| auditLogs | 审计日志(聚合 iam AuditEvent | MSW 返回固定 100 条审计日志 |
| adminDashboard | 管理员仪表盘 | MSW 返回固定仪表盘total_teachers=50, total_students=1200, school_avg_score=80.0 |
> **登录不自行实现**admin-portal 复用 Shell 统一登录入口 `/login`ARB-002 §2.3:登录页 P2 不走 MFShell 独占)。管理员登录后按 admin 角色重定向到 `/admin/dashboard`。开发期 mock 登录由 Shell 的 MSW handler 提供admin 角色 JWT + permissions=["*"])。
### 2.4 GraphQL 查询域(经 api-gateway 代理到 teacher-bff admin 命名空间)
> 依赖 teacher-bff admin 命名空间 schemaARB-001**ISSUE-005 待 ai03 补齐**)。以下为 admin-portal 消费的 Query/Mutation 清单,作为 ai03 补齐 schema 的输入。
| Query/Mutation | 类型 | 用途 | mock 策略 |
| -------------- | ---- | ---- | --------- |
| `currentUser` | Query | 当前管理员信息 | MSW 返回固定管理员admin 角色) |
| `adminUsers` | Query | 用户列表(含筛选/分页) | MSW 返回固定 50 个用户 |
| `adminUser(id)` | Query | 用户详情 | MSW 返回对应用户 |
| `createUser` / `updateUser` / `deleteUser` / `toggleUserStatus` | Mutation | 用户 CRUD | MSW 返回 CRUD success |
| `adminRoles` | Query | 角色列表(含权限) | MSW 返回固定 5 个角色 + 权限矩阵 |
| `createRole` / `updateRolePermissions` | Mutation | 角色 CRUD + 权限矩阵 | MSW 返回 success |
| `adminPermissions` | Query | 全量权限点(按 resource 分组) | MSW 返回固定权限矩阵 |
| `adminViewports(scope)` | Query | 视口配置列表 | MSW 返回固定 7 个视口 |
| `updateViewport` | Mutation | 视口配置更新 | MSW 返回 success |
| `adminOrganization(parentId)` | Query | 组织树 | MSW 返回固定 school/grade/class 树 |
| `adminClasses` | Query | 班级管理(全局) | MSW 返回固定 20 个班级 |
| `adminTeachers` | Query | 教师管理 | MSW 返回固定 50 个教师 |
| `adminStudents` | Query | 学生管理 | MSW 返回固定 1200 个学生 |
| `auditLogs(filter)` | Query | 审计日志(聚合 iam AuditEvent | MSW 返回固定 100 条审计日志 |
| `adminDashboard` | Query | 管理员仪表盘聚合 | MSW 返回固定仪表盘total_teachers=50, total_students=1200, school_avg_score=80.0 |
### 2.5 消费的错误码前缀(前端 i18n 路由)
| 前缀 | 来源服务 | i18n key 模式 |
| ---- | -------- | ------------- |
| `IAM_` | iam | `iam.error.{{code}}` |
| `BFF_TEACHER_` | teacher-bff | `bff.error.{{code}}` |
| `GW_` | api-gateway | `gateway.error.{{code}}` |
| `NETWORK_` | 前端网络层 | `network.error.{{code}}` |
---
@@ -85,18 +128,21 @@
### 3.1 我依赖的上游就绪标志
- [ ] api-gateway HTTP :8080 启用ai01—— 前端请求入口 + admin 角色校验
- [ ] teacher-bff GraphQL :3003 启用ai03—— admin namespace 可用
- [ ] teacher-portal Shell MF exposes/shared 就绪ai13ARB-002—— Remote 挂载前提
- [ ] teacher-bff GraphQL :3003 启用 + **admin 命名空间 schema 就绪**ai03—— **ISSUE-005 待 ai03 补齐**
- [ ] iam gRPC 50052 启用ai06—— 用户/角色/审计日志数据来源
- [ ] edu.iam.audit.created topic 有事件发布ai06—— 审计日志来源
- [ ] data-ana gRPC 50055 启用ai11)—— adminDashboard 数据来源
- [ ] push-gateway WebSocket :8081/ws 启用ai02—— 实时通知
- [ ] `edu.iam.audit.created` topic 有事件发布ai06—— 审计日志来源(经 teacher-bff 消费)
- [ ] push-gateway WebSocket :8081/ws 启用ai02)—— 实时通知(**ISSUE-006 待 coord 仲裁**
- [ ] `packages/contracts` admin 权限点 `ADMIN_*` 常量就绪coord
> adminDashboard 的数据聚合由 teacher-bff 完成teacher-bff 内部聚合 iam + core-edu + data-ana。admin-portal **不直连 data-ana / core-edu gRPC**,统一经 teacher-bff GraphQL。原 contract 误列 data-ana gRPC 50055 为直接依赖,已修正。
### 3.2 我的就绪标志(供下游消费)
- [ ] admin-portal dev server :4003 启用
- [ ] MF Remote 可被 AppShell 加载(暴露 AdminApp 模块)
- [ ] MF Remote 可被 AppShell 加载(暴露 `./AdminApp` 模块)
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫 + admin 角色校验)
- [ ] 登录流程可用(POST /api/auth/login 获取 JWT前端校验 admin 角色
- [ ] 登录流程可用(复用 Shell `/login`admin 角色校验后重定向 `/admin/dashboard`
- [ ] GraphQL 查询可执行currentUser / adminDashboard / auditLogs 返回数据)
- [ ] 用户/角色 CRUD 可执行createUser / updateRolePermissions
- [ ] WebSocket 通知可接收
@@ -117,13 +163,25 @@ admin-portal 是前端,无下游消费方。但对开发体验提供:
在真实上游就绪前admin-portal 使用以下 mock
- **HTTP/GraphQL mock**:使用 MSW 拦截所有请求
- POST /api/auth/login → 返回固定 JWT + UserInfoadmin 角色permissions=["*"]
- POST /api/admin/graphql → 根据 operationName 返回对应 mock 响应(与 teacher-bff admin namespace mock 数据一致)
- POST /api/admin/graphql → 按 operationName 返回对应 mock 响应(与 teacher-bff admin namespace mock 数据一致
- auditLogs mock 返回固定 100 条审计日志(含 action: create/update/delete/login/logout/permission_change
- adminDashboard mock 返回固定全校统计仪表盘
- 所有 mock 响应定义在 `apps/admin-portal/src/mocks/fixtures/*.json`
- **WebSocket mock**:使用 mock-socket 库
- 连接后每 30 秒推送 1 条 mock 系统通知
- **JWT mock**:使用固定 mock JWTadmin 角色),存入 httpOnly cookie
- 连接后每 30 秒推送 1 条 mock 系统通知(审计告警/异常登录)
- **JWT mock**:使用固定 mock JWTadmin 角色permissions=["*"]),由 Shell MSW handler 写入 httpOnly cookie复用 Shell 登录 mock
- **权限矩阵 mock**:内置固定 5 个角色 + 完整权限矩阵teacher/student/parent/admin/super_admin
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`
---
## §5 跨模块契约确认清单(需对应 AI 确认)
| 契约 | 提供方 | 当前状态 |
| ---- | ------ | -------- |
| teacher-bff admin 命名空间 schema§2.4 全部 Query/Mutation | ai03 | ⚠️ 待补齐ISSUE-005 |
| Shell 暴露 GraphQLProvider / useGraphQLClient / AppShell / useAuth / usePermission | ai13 | ⏳ P2 交付ARB-002 |
| iam 用户/角色/权限/视口 CRUD gRPC + AuditEvent Kafka | ai06 | ⏳ P2.1 |
| api-gateway /api/admin/graphql 代理路由 + admin 角色校验 | ai01 | ⏳ |
| push-gateway GET /wsadmin-portal 实时通知) | ai02 | ⚠️ 待 coord 仲裁ISSUE-006 |
| `packages/contracts` ADMIN_* 权限点常量 | coord | ⏳ |

View File

@@ -1,42 +1,99 @@
# ai 对接契约
> 负责人ai12
> 关联:[matrix.md](./matrix.md)、[ai.proto](../../../packages/shared-proto/proto/ai.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](../matrix.md)、[port-allocation.md](../../../../infra/port-allocation.md)、[ai.proto](../../../../packages/shared-proto/proto/ai.proto)、[events.proto](../../../../packages/shared-proto/proto/events.proto)、[02-architecture-design.md](../../../../services/ai/docs/02-architecture-design.md)、[objections/ai_issue.md](../objections/ai_issue.md)
> 端口权威源:[port-allocation.md](../../../../infra/port-allocation.md) §3/§5 —— ai = HTTP 3008 / gRPC 50058
> **本契约已对齐 02-architecture-design.md 设计文档**。原 coord 模板的 5 处矛盾已修正(见 [objections/ai_issue.md](../objections/ai_issue.md) ISSUE-05端口 50057→50058、补 HTTP 端点、topic 三义待裁决、错误码对齐 §6.2、消费事件改 P6+ 评估。标注 ⏳ 的字段待 coord 裁决 ISSUE-02/03/04 后最终定稿。
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 |
| --------- | ---------------------- | ----------------------------- | ------------------------ | ----- |
| AiService | Chat | ChatRequest | ChatResponse | 50057 |
| AiService | StreamChat | ChatRequest | stream ChatChunk | 50057 |
| AiService | GenerateQuestion | GenerateQuestionRequest | GeneratedQuestion | 50057 |
| AiService | OptimizeExpression | OptimizeExpressionRequest | OptimizedExpression | 50057 |
| AiService | GenerateLessonPlan | GenerateLessonPlanRequest | LessonPlan | 50057 |
| AiService | StreamGenerateQuestion | StreamGenerateQuestionRequest | stream GeneratedQuestion | 50057 |
| Service | RPC | 请求 | 响应 | 端口 | 状态 |
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | ----- | ---- |
| AiService | Chat | `ChatRequest{messages, model, temperature, user_id?, session_id?, data_scope?}` | `ChatResponse{content, model, usage}` | 50058 | ⏳ 待实现 |
| AiService | StreamChat | `ChatRequest` | `stream ChatChunk` | 50058 | ⏳ 待实现 |
| AiService | GenerateQuestion | `GenerateQuestionRequest{prompt, subject, difficulty, grade?, knowledge_point_ids?, question_type?, count?}` | `GeneratedQuestion` | 50058 | ⏳ 待实现 |
| AiService | StreamGenerateQuestion | `GenerateQuestionRequest` | `stream GeneratedQuestionChunk` | 50058 | ⏳ 待补 protoISSUE-03 |
| AiService | OptimizeExpression | `OptimizeExpressionRequest{text, context}` | `OptimizedExpression` | 50058 | ⏳ 待实现 |
| AiService | GenerateLessonPlan | `GenerateLessonPlanRequest{class_id, subject_id, topic, user_id, data_scope}` | `LessonPlanResponse{workflow_id, status, questions?}` | 50058 | ⏳ 待补 protoISSUE-03 |
### 1.2 HTTP 端点(如有)
> **RPC 总数**P5 目标 6 RPCai12 建议,见 ISSUE-03。备课工作流的"查询状态/确认入库"用 HTTP 端点实现,避免 RPC 膨胀;如 coord 裁定需 gRPC 则扩到 8 RPC追加 GetLessonPlanStatus / ConfirmLessonPlan
> **proto 现状**ai.proto 仅 4 RPCChat/StreamChat/GenerateQuestion/OptimizeExpression缺 GenerateLessonPlan / StreamGenerateQuestion且字段未扩展。待 coord 升级 ai.proto 到 v1 完整版ISSUE-03
> **proto package 偏离**:现状 `next_edu_cloud.ai.v1`,不符合 project_rules §5 `edu.<domain>.v1`,见 ISSUE-08。
无对外 HTTP 端点,仅 gRPC含 2 个 Server Streaming RPCStreamChat / StreamGenerateQuestion
### 1.2 HTTP 端点
> HTTP 保留作 api-gateway 直连降级 + SSE 流式。api-gateway 代理 `/api/v1/ai/*` → ai `/ai/v1/*`(见 [main.py:70](../../../../services/ai/src/ai/main.py) 注释 + matrix.md §5
| Method | Path | 权限 | 响应 | 说明 | 状态 |
| ------ | ------------------------------------------------- | ------------------------ | -------------------------------------- | --------------------------------- | ---- |
| GET | `/healthz` | — | `{status, service}` | liveness | ✅ 已实现 |
| GET | `/readyz` | — | `{status, llm_configured, providers, downstream_grpc, redis, kafka}` | readiness多维度检查 | ⚠️ 待扩展 |
| GET | `/metrics` | — | Prometheus | 指标 | ✅ 已实现 |
| POST | `/ai/v1/chat` | `AI_CHAT` | `ActionState<ChatData>` | LLM 聊天 | ⚠️ 当前 `/ai/chat`,待加 /v1 + ActionState |
| POST | `/ai/v1/chat/stream` | `AI_CHAT` | SSE stream | 流式聊天 | ⚠️ 同上 |
| POST | `/ai/v1/generate/question` | `AI_QUESTION_GENERATE` | `ActionState<GeneratedQuestionData>` | 生成题目 | ⚠️ 同上 |
| POST | `/ai/v1/generate/question/stream` | `AI_QUESTION_GENERATE` | SSE stream题目逐字生成 | 题目逐字流式 | ⏳ 待实现 |
| POST | `/ai/v1/optimize/expression` | `AI_EXPRESSION_OPTIMIZE` | `ActionState<OptimizedExpressionData>` | 优化表达 | ⚠️ 同上 |
| POST | `/ai/v1/lesson/preparation` | `AI_LESSON_PREPARE` | `ActionState<LessonPreparationData>` | 备课工作流启动 | ⏳ 待实现 |
| GET | `/ai/v1/lesson/preparation/{workflow_id}` | `AI_LESSON_PREPARE` | `ActionState<WorkflowState>` | 查询工作流状态 | ⏳ 待实现 |
| POST | `/ai/v1/lesson/preparation/{workflow_id}/confirm` | `AI_LESSON_PREPARE` | `ActionState<PersistResult>` | 教师确认入库 | ⏳ 待实现 |
| GET | `/ai/v1/prompts` | `AI_PROMPT_READ` | `ActionState<Page<TemplateSummary>>` | 模板列表 | ⏳ 待实现 |
| POST | `/ai/v1/prompts` | `AI_PROMPT_CREATE` | `ActionState<PromptTemplate>` | 创建模板 | ⏳ 待实现 |
| GET | `/ai/v1/prompts/{id}` | `AI_PROMPT_READ` | `ActionState<PromptTemplate>` | 获取模板 | ⏳ 待实现 |
| PUT | `/ai/v1/prompts/{id}` | `AI_PROMPT_UPDATE` | `ActionState<PromptTemplate>` | 更新模板(版本化) | ⏳ 待实现 |
| GET | `/ai/v1/usage/me` | `AI_USAGE_READ` | `ActionState<UsageSummary>` | 当前用户用量 | ⏳ 待实现 |
| GET | `/ai/v1/usage/school/{school_id}` | `AI_USAGE_READ_ALL` | `ActionState<UsageSummary>` | 学校用量(管理员) | ⏳ 待实现 |
> **响应信封**:所有响应必须为 ActionState004 §11.5 强制,见 ISSUE-09。当前 main.py 返回 `{success, data, degraded}` 顶层 degraded 字段违反约束P5 必须整改。
> **路径演进**:当前实现是 `/ai/*`(无 /v1目标态 `/ai/v1/*`(加版本前缀,便于未来破坏性变更)。
### 1.3 GraphQL schema如 BFF
不适用。
不适用。ai 是业务服务,不暴露 GraphQL由 teacher-bff 聚合 ai gRPC 能力为 GraphQL。
### 1.4 Kafka 事件发布(如有)
### 1.4 Kafka 事件发布
| Topic | Event | 消费方 |
| ------------------- | ---------------------------------------------------------------------------------------- | -------- |
| edu.ai.usage.events | AIUsageEventoperation: chat/generate_question/optimize_expression/lesson_preparation | data-ana |
| Topic | Event | 触发时机 | 消费方 | Payload |
| ----------------- | ---------------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `edu.ai.usage`(建议,待 ISSUE-02 裁决) | `AIUsageEvent`(待 ISSUE-04 补 proto | 每次 LLM 调用完成 | data-ana | `{event_id, aggregate_id, event_type, occurred_at, user_id, school_id, request_id, provider, model, operation, prompt_tokens, completion_tokens, total_tokens, latency_ms, success, degraded, metadata}` |
> AIUsageEvent 豁免 Outbox 模式(派生数据事件,见 004 §12.2 + §15.3 #6)。
> **topic 命名三义**01/02 文档写 `edu.insight.ai.usage`、matrix.md 写 `edu.ai.usage`、原 contract 写 `edu.ai.usage.events`。ai12 建议采用 `edu.ai.usage`(最简短),待 coord 裁决ISSUE-02)。
> **Outbox 豁免**AIUsageEvent 为派生数据事件004 §12.2 + §15.3 #6 仲裁豁免 Outbox允许直接 producer`aiokafka` + acks=all + idempotent + transactional_id
> **events.proto 现状**:无 `AIUsageEvent` message待 coord 补全ISSUE-04建议 schema 见 [02-architecture-design.md §3.3](../../../../services/ai/docs/02-architecture-design.md)。
> **长期可发布事件**P6+ 评估,待 coord 仲裁):`AIContentGenerated`topic `edu.ai.generated`,生成内容审计)、`AIFeedbackRecorded`topic `edu.ai.feedback`RLHF 数据)、`AIWorkflowEvent`topic `edu.ai.workflow`,工作流监控)。
### 1.5 错误码前缀
`AI_`如 AI_PROVIDER_UNAVAILABLE、AI_TOKEN_LIMIT_EXCEEDED、AI_CONTENT_FILTERED
`AI_*`对齐 [02-architecture-design.md §6.2](../../../../services/ai/docs/02-architecture-design.md) 完整清单matrix.md §6 已确认 ai 前缀为 `AI_`
| 错误码 | 触发条件 | HTTP | gRPC status |
| -------------------------------- | ----------------------------------- | ---- | ------------------- |
| `AI_UNAUTHORIZED` | 缺失 x-user-id 或 token 无效 | 401 | UNAUTHENTICATED |
| `AI_FORBIDDEN` | 角色无对应权限 | 403 | PERMISSION_DENIED |
| `AI_RATE_LIMITED` | 触发限流user/IP/school | 429 | RESOURCE_EXHAUSTED |
| `AI_QUOTA_EXCEEDED` | 学校/教师月度 token 配额耗尽 | 429 | RESOURCE_EXHAUSTED |
| `AI_LLM_UNAVAILABLE` | LLM Provider 不可达(降级骨架) | 200 | OK + degraded flag |
| `AI_LLM_TIMEOUT` | LLM 调用超时30s | 504 | DEADLINE_EXCEEDED |
| `AI_LLM_ALL_PROVIDERS_FAILED` | 所有 Provider 故障切换链均失败 | 503 | UNAVAILABLE |
| `AI_INVALID_MODEL` | model 名不支持 | 400 | INVALID_ARGUMENT |
| `AI_INVALID_DIFFICULTY` | difficulty 不在 easy/medium/hard | 400 | INVALID_ARGUMENT |
| `AI_INVALID_QUESTION_TYPE` | question_type 不在枚举内 | 400 | INVALID_ARGUMENT |
| `AI_DOWNSTREAM_UNAVAILABLE` | content / data-ana gRPC 不可达 | 502 | UNAVAILABLE |
| `AI_PROMPT_RENDER_FAILED` | Prompt 模板渲染失败 | 500 | INTERNAL |
| `AI_PROMPT_TEMPLATE_NOT_FOUND` | Prompt 模板不存在 | 404 | NOT_FOUND |
| `AI_WORKFLOW_NOT_FOUND` | 备课工作流 ID 不存在 | 404 | NOT_FOUND |
| `AI_WORKFLOW_EXPIRED` | 工作流已过期24h 未审核) | 410 | FAILED_PRECONDITION |
| `AI_WORKFLOW_STATE_INVALID` | 工作流状态不允许此操作 | 409 | FAILED_PRECONDITION |
| `AI_EVALUATION_FAILED` | 生成质量评估未通过且重试耗尽 | 503 | UNAVAILABLE |
| `AI_PII_DETECTED` | 输入包含未脱敏 PII | 400 | INVALID_ARGUMENT |
| `AI_PROMPT_INJECTION_DETECTED` | 输入疑似 Prompt 注入攻击 | 400 | INVALID_ARGUMENT |
| `AI_CONTENT_MODERATION_REJECTED` | 输出内容审核不通过(敏感词) | 503 | UNAVAILABLE |
| `AI_INTERNAL_ERROR` | 未捕获异常 | 500 | INTERNAL |
---
@@ -44,24 +101,37 @@
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | -------------------------------------- | ---------------------------- | ----------------------------------------------------------- |
| content (ai09) | KnowledgeGraphService.GetPrerequisites | 生成题目时获取知识点前置依赖 | content 就绪前使用本地知识点 stub固定 3 个前置知识点) |
| content (ai09) | QuestionService.SearchQuestions | 备课时检索同类题目参考 | content 就绪前返回空列表 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 个性化出题时获取学生薄弱点 | data-ana 就绪前使用本地薄弱点 stub固定 2 个 weak_points |
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | -------------------------------------- | ---------------------------- | ------------------------------------------------------------- |
| content (ai09) | `KnowledgeGraphService.GetPrerequisites` | 出题上下文:查询知识点前置依赖 | content 就绪前使用本地知识点 stub固定 3 个前置知识点) |
| content (ai09) | `KnowledgeGraphService.GetLearningPath` | 个性化出题:查询学生学习路径 | content 就绪前返回空路径 |
| content (ai09) | `TextbookService.ListTextbooks` | 备课工作流:查询教材章节关联知识点 | content 就绪前返回空列表 |
| content (ai09) | `QuestionService.CreateQuestions`(待 coord 补 proto | 备课工作流:生成的题目入库 | content 就绪前跳过入库,工作流标记 PersistFailed |
| data-ana (ai11) | `AnalyticsService.GetStudentWeakness` | 靶向出题:查询学生薄弱知识点 | data-ana 就绪前使用本地薄弱点 stub固定 2 个 weak_points |
| data-ana (ai11) | `AnalyticsService.GetLearningTrend` | 难度调节:查询学习趋势 | data-ana 就绪前返回默认趋势 |
| data-ana (ai11) | `AnalyticsService.GetClassPerformance` | 备课工作流:班级整体学情 | data-ana 就绪前降级跳过学情查询(`degraded:true` |
| iam (ai06) | `IamService.GetEffectiveDataScope`(待 P4 补全ISSUE-07 | 多租户配额:查询用户 DataScope | iam RPC 未就绪时降级为"仅按 user_id 配额,不按 school_id" |
> **端口**content gRPC 50054 / data-ana 50055 / iam 50052[port-allocation.md](../../../../infra/port-allocation.md) §5。注意 02-architecture-design.md §1.1/§1.2 mermaid 图误标为 3005/3006/3002HTTP 端口),应以 50054/50055/50052 为准。
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| ---------------------------------- | ------------------- | -------------- | -------------------------------------- |
| edu.content.knowledge_point.events | KnowledgePointEvent | content (ai09) | content 就绪前不订阅,使用内置知识点表 |
| edu.content.question.events | QuestionEvent | content (ai09) | content 就绪前忽略 |
> ai 是**无状态服务P5 不消费任何 Kafka 事件**01/02 §5.1 明确)。原 contract 列出的 content 事件订阅已删除(与设计文档矛盾,见 ISSUE-05.5)。
### 2.3 HTTP 调用(如有)
**P6+ 评估可消费事件**(待 coord 仲裁,非 P5 范围):
| Topic | Event | 发布方 | 用途 | 评估阶段 |
| ---------------------------------- | ------------------- | -------------- | -------------------------------- | -------- |
| `edu.content.question.events` | QuestionEvent | content (ai09) | 题目查重:避免 AI 生成与已有重复 | P6+ |
| `edu.iam.user.events` | UserEvent | iam (ai06) | 角色变更:主动失效 DataScope 缓存 | P6+ |
### 2.3 HTTP 调用(外部)
| 被调用方 | Method.Path | 用途 | mock 策略 |
| -------------------------------- | ------------------------- | ------------------ | ------------------------------------------------------------------ |
| LLM ProviderOpenAI/百川/本地 | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse不消耗真实 token |
| LLM ProviderOpenAI/Anthropic/百川/Ollama | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse不消耗真实 token |
> 多 Provider 通过 `ProviderFailoverChain` 故障切换OpenAI → Anthropic → 百川 → 本地 Ollama熔断器连续 3 次失败触发 60s 熔断。
---
@@ -69,18 +139,24 @@
### 3.1 我依赖的上游就绪标志
- [ ] content gRPC 50054 启用ai09—— 知识点维度 + 题库检索
- [ ] edu.content.knowledge_point.events topic 有事件发布ai09
- [ ] data-ana gRPC 50055 启用ai11—— 学生薄弱点可选ai 可先独立运行
- [ ] ai.proto 升级 v1 完整版6 RPC + 字段扩展)—— coordISSUE-03
- [ ] events.proto 补 `AIUsageEvent` message —— coordISSUE-04
- [ ] ai 用量事件 topic 命名裁决 —— coordISSUE-02
- [ ] content gRPC 50054 启用ai09—— 知识点维度 + 题库检索 + 入库
- [ ] data-ana gRPC 50055 启用ai11可选—— 学生薄弱点(可降级独立运行)
- [ ] iam `GetEffectiveDataScope` RPC P4 补全ai06 + coordISSUE-07可降级
- [ ] LLM Provider API key 配置(人类决策者)
### 3.2 我的就绪标志(供下游消费)
- [ ] ai gRPC 50057 启用HealthService.Check 返回 SERVING
- [ ] ai gRPC 50058 启用HealthService.Check 返回 SERVING
- [ ] AiService.Chat / StreamChat 可调用(含流式响应)
- [ ] AiService.GenerateQuestion / StreamGenerateQuestion 可调用
- [ ] AiService.GenerateLessonPlan 可调用P5 补全)
- [ ] AiService.OptimizeExpression 可调用
- [ ] edu.ai.usage.events topic 可发布(供 data-ana 统计 AI 用量)
- [ ] ai 用量事件 topic 可发布(供 data-ana 统计 AI 用量)
> **端口**50058[port-allocation.md](../../../../infra/port-allocation.md) §3/§5/§7 权威源2026-07-09 coord 仲裁"50058 让给 ai")。注意 [matrix.md](../matrix.md) §2/§8 仍写 50057待 coord 同步ISSUE-01
---
@@ -90,12 +166,13 @@
在 ai 真实服务就绪前为下游teacher-bff提供以下 mock
- **gRPC mock**:使用 grpc-mock 拦截 50057 端口
- **gRPC mock**:使用 grpc-mock 拦截 **50058** 端口
- AiService.Chat 返回固定 ChatResponsecontent="这是 AI 助手的模拟回复"
- AiService.StreamChat 返回固定流3 个 ChatChunk最后一个 done=true
- AiService.GenerateQuestion 返回固定 GeneratedQuestionquestion/answer/explanation
- AiService.GenerateLessonPlan 返回固定 LessonPlan3 个 LessonSection
- AiService.StreamGenerateQuestion 返回固定流2 个 GeneratedQuestion
- AiService.GenerateLessonPlan 返回固定 LessonPlanResponseworkflow_id + 3 个题目
- AiService.StreamGenerateQuestion 返回固定流2 个题目逐字 chunk
- AiService.OptimizeExpression 返回固定 OptimizedExpression
- **Kafka mock**ai 就绪前不发布真实 AIUsageEventdata-ana 仪表盘 AI 用量显示"暂无数据"
### 4.2 我消费的 mock
@@ -105,4 +182,22 @@
- **LLM Provider mock**:本地启动 mock serverPOST /v1/chat/completions 返回固定 JSON不消耗真实 token不产生费用
- **content 知识点**:内置固定知识点表(数学 20 个知识点 + 前置依赖关系),不依赖 content gRPC
- **data-ana 薄弱点**内置固定学生薄弱点2 个 weak_points不依赖 data-ana gRPC
- **事件订阅**:不订阅 content 事件,知识点维度表静态
- **iam DataScope**:内置固定 DataScopeSCHOOL 级),不依赖 iam GetEffectiveDataScope
- **事件订阅**P5 不订阅任何事件,无 mock 需要
---
## §5 契约待裁决项汇总
> 以下字段待 coord 裁决后最终定稿ai12 当前按建议方案先行实现。
| 待裁决项 | ISSUE | ai12 建议方案 | 影响章节 |
| --------------------------------- | ------ | ---------------------------------------------- | -------------- |
| ai gRPC 端口50057 vs 50058 | ISSUE-01 | 50058port-allocation.md 已定,待 matrix 同步) | §1.1 / §3.2 |
| ai 用量事件 topic 命名 | ISSUE-02 | `edu.ai.usage` | §1.4 |
| ai.proto P5 目标 RPC 数6 vs 8 | ISSUE-03 | 6 RPC查询/确认用 HTTP | §1.1 |
| events.proto 补 AIUsageEvent | ISSUE-04 | 按 02-architecture-design.md §3.3 schema | §1.4 |
| 备课工作流 TemporalP6 决策点) | ISSUE-06 | P5 用 BackgroundTasks + RedisP6 评估 Temporal | §2.1(无影响) |
| iam GetEffectiveDataScope P4 补全 | ISSUE-07 | 确认 P4 已补全;未补全则降级 | §2.1 |
| proto package 命名(全局) | ISSUE-08 | 待 coord 裁定是否迁移 `edu.<domain>.v1` | §1.1 |
| 响应信封 ActionState 整改 | ISSUE-09 | P5 整改degraded 作为 error.details 子字段 | §1.2 |

View File

@@ -1,40 +1,90 @@
# api-gateway 对接契约
> 负责人ai01
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[objections/api-gateway_issue.md](../objections/api-gateway_issue.md)、[worklines/api-gateway_workline.md](../worklines/api-gateway_workline.md)、[02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md)
> 依据:[coord-final-decisions.md](../../coord-final-decisions.md) §3.8 W1-W8、[president-final-rulings.md](../../president-final-rulings.md) §2.15/§2.16/§2.19
> 版本v22026-07-10 修正:路径前缀 /api→/api/v1、JWKS 拉取方式 gRPC→HTTP、删除 GetEffectiveAccess、错误码加 GW_ 前缀)
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
无。api-gateway 是 HTTP 入口,不对外提供 gRPC。
无。api-gateway 是 HTTP 入口,不对外提供 gRPC(依据 [president-final-rulings.md](../../president-final-rulings.md) §2.16 裁决gateway 保持 HTTP 透传,不改为 gRPC 客户端)
### 1.2 HTTP 端点(如有)
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 |
| ------ | -------------- | ------------------------------------------- | ---------------------- |
| ANY | /api/auth/* | 代理到 iam 认证相关(登录/注册/刷新 token | 公开(登录注册免认证) |
| ANY | /api/teacher/* | 代理到 teacher-bff GraphQL:3003 | JWT 必需 |
| ANY | /api/student/* | 代理到 student-bff GraphQL:3009 | JWT 必需 |
| ANY | /api/parent/* | 代理到 parent-bff GraphQL:3010 | JWT 必需 |
| ANY | /api/admin/* | 代理到 teacher-bff GraphQL admin namespace | JWT 必需 + admin 角色 |
| GET | /healthz | 网关健康检查liveness | 公开 |
| GET | /readyz | 网关就绪检查readiness含 iam 连通性) | 公开 |
| GET | /metrics | Prometheus 指标端点 | 公开(内网) |
#### 1.2.1 健康检查与指标端点(公开,无鉴权)
### 1.3 GraphQL schema如 BFF
| Method | Path | 用途 | 认证 | 阶段 |
| ------ | ---------- | ------------------------------------------- | ---- | ---- |
| GET | /healthz | 网关存活探针liveness | 公开 | P1 |
| GET | /readyz | 网关就绪探针readiness并行 ping 下游 /healthz| 公开 | P2 |
| GET | /metrics | Prometheus 指标端点7 个业务指标 + Go runtime| 公开(内网)| P2 |
不适用。api-gateway 仅做 HTTP 反向代理 + JWT 验签,不解析 GraphQL。
#### 1.2.2 反向代理路由(JWT 必需,除白名单外)
### 1.4 Kafka 事件发布(如有)
> **路径前缀**:所有业务路由统一 `/api/v1/*` 前缀(与 [main.go](../../../services/api-gateway/main.go) L59 `r.Group("/api/v1")` 一致)。
>
> **API 版本化**:按 [president-final-rulings.md](../../president-final-rulings.md) §2.15 裁决,各服务 Controller 内加 `/v1` 前缀。Gateway 透传时不改路径,前端调用 `/api/v1/<service>/v1/*`ISSUE-003 待 coord 仲裁最终方案,本表暂列方案 A
无。api-gateway 不发布事件。
| Method | PathGateway 外部路径) | 目标服务 | 端口 | 鉴权 | 公开子路径 | 阶段 |
| ------ | --------------------------------------------------- | ------------------------- | ----- | ---- | --------------------------------------- | ---- |
| ANY | /api/v1/iam/v1/*path + /api/v1/iam/v1 | iam | 3002 | JWT | `/iam/v1/register` `/login` `/refresh` | P2 |
| ANY | /api/v1/teacher/v1/*path + /api/v1/teacher/v1 | teacher-bffGraphQL | 3003 | JWT | — | P2 |
| ANY | /api/v1/student/v1/*path + /api/v1/student/v1 | student-bffGraphQL | 3009 | JWT | — | P3 |
| ANY | /api/v1/parent/v1/*path + /api/v1/parent/v1 | parent-bffGraphQL | 3010 | JWT | — | P4 |
| ANY | /api/v1/classes/v1/*path + /api/v1/classes/v1 | core-educlasses 合并) | 3004 | JWT | — | P3 |
| ANY | /api/v1/exams/v1/*path + /api/v1/homework/v1/*path + /api/v1/grades/v1/*path | core-edu | 3004 | JWT | — | P3 |
| ANY | /api/v1/textbooks/v1/*path + /api/v1/chapters/v1/*path + /api/v1/knowledge-points/v1/*path + /api/v1/questions/v1/*path | content | 3005 | JWT | — | P4 |
| ANY | /api/v1/analytics/v1/*path + /api/v1/dashboard/v1/*path | data-ana | 3006 | JWT | — | P4 |
| ANY | /api/v1/notifications/v1/*path + /api/v1/messages/v1/*path | msg | 3007 | JWT | — | P5 |
| ANY | /api/v1/ai/v1/*path + /api/v1/ai/v1 | ai | 3008 | JWT | — | P5 |
| ANY | /api/v1/admin/v1/*path + /api/v1/admin/v1 | teacher-bff admin namespace | 3003 | JWT + admin 角色 | — | P6 |
> **注 1**:每个前缀同时注册无尾斜杠与通配符两条路由(`/iam/v1` + `/iam/v1/*path`),因为 `r.RedirectTrailingSlash = false`[main.go](../../../services/api-gateway/main.go) L34
>
> **注 2**admin-portal P6 阶段复用 teacher-bff + admin schema 命名空间(依据 [coord.md](../coord.md) §1.3 ARB-001 裁决)。
>
> **注 3**:当前 P1 代码暂未加 `/v1` 前缀(如 `/api/v1/iam/*path`),待 ISSUE-003 仲裁后 P2.7 任务统一迁移。
#### 1.2.3 透传请求头Gateway 注入,下游读取)
| 头名 | 来源 | 用途 | 下游消费方 |
| -------------- | ----------------------------- | ----------------------------- | ---------- |
| `x-user-id` | JWT `sub` claim | 用户身份传递 | 所有下游 |
| `x-user-roles` | JWT `roles` claim逗号分隔| 角色传递 | 所有下游 |
| `x-data-scope` | JWT `data_scope` claim | 数据范围传递P2 新增) | 所有下游 |
| `X-Request-Id` | 客户端透传或 Gateway 生成 UUID | 请求 ID全链路追踪 | 所有下游 |
| `traceparent` | OTel SDK 自动处理 | W3C Trace Context链路追踪| 所有下游 |
| `tracestate` | OTel SDK 自动处理 | W3C Trace Context 扩展 | 所有下游 |
### 1.3 GraphQL schema
不适用。api-gateway 仅做 HTTP 反向代理 + JWT 验签,不解析 GraphQLGraphQL 由 BFF 层处理)。
### 1.4 Kafka 事件发布
无。api-gateway 不发布 Kafka 事件(纯同步 HTTP 反向代理)。
### 1.5 错误码前缀
`GW_`如 GW_UNAUTHORIZED、GW_RATE_LIMITED、GW_CIRCUIT_OPEN、GW_BACKEND_UNAVAILABLE
`GW_`依据 [coord-final-decisions.md](../../coord-final-decisions.md) W1 / G14 裁决
### 1.6 错误码清单
| 错误码 | HTTP | 触发条件 | 响应体ActionState 信封) |
| ------------------------ | ---- | --------------------- | ----------------------------------------------------------------------- |
| `GW_UNAUTHORIZED` | 401 | 缺失 Authorization 头 | `{success:false,error:{code:"GW_UNAUTHORIZED",message:"..."}}` |
| `GW_INVALID_TOKEN` | 401 | JWT 签名/格式错误 | 同上 |
| `GW_INVALID_CLAIMS` | 401 | JWT claims 解析失败 | 同上 |
| `GW_RATE_LIMITED` | 429 | 超出令牌桶限流 | `{success:false,error:{code:"GW_RATE_LIMITED",message:"...",retry_after:60}}` |
| `GW_CIRCUIT_OPEN` | 503 | 下游熔断打开 | `{success:false,error:{code:"GW_CIRCUIT_OPEN",message:"...",retry_after:30}}` |
| `GW_REQUEST_TOO_LARGE` | 413 | 请求体超 10MB | `{success:false,error:{code:"GW_REQUEST_TOO_LARGE",message:"..."}}` |
| `GW_INTERNAL_ERROR` | 500 | panic 兜底 | `{success:false,error:{code:"GW_INTERNAL_ERROR",message:"...",request_id:"..."}}` |
> 依据 W1 / W2 裁决:错误码统一 `GW_` 前缀,响应体统一 ActionState 信封 `{success,error:{code,message}}`。
---
@@ -42,22 +92,48 @@
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| ---------- | ----------------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| iam (ai06) | IamService.GetPublicKey | 启动时拉取 RS256 公钥,用于 JWT 验签 | iam 就绪前使用本地固定 mock 公钥(与 mock 私钥配对签发 mock JWT |
| iam (ai06) | IamService.GetEffectiveAccess | 权限校验(可选,部分路由需要细粒度权限) | iam 就绪前放行所有请求(仅校验 JWT 签名) |
**无**。依据 [president-final-rulings.md](../../president-final-rulings.md) §2.16 裁决"gateway 保持 HTTP 透传,不改为 gRPC 客户端"api-gateway 不消费任何 gRPC 接口。
### 2.2 Kafka 事件订阅(异步)
> **勘误**v1 版本曾错误声明消费 `IamService.GetPublicKey` 和 `IamService.GetEffectiveAccess`v2 已删除):
> - JWT 公钥拉取走 HTTP JWKS 端点,非 gRPC见 §2.3
> - Gateway 不做权限点校验,不消费 `GetEffectiveAccess`(权限由下游服务 Controller `@RequirePermission` 自校验,见 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §2 + B3 裁决)
> - [matrix.md](./matrix.md) §2 gRPC 接口提供方矩阵中"iam 消费方"应移除 api-gateway已提请 ISSUE-001
### 2.2 Kafka 事件订阅
无。api-gateway 不订阅 Kafka 事件。
### 2.3 HTTP 调用(如有
### 2.3 HTTP 调用(同步
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------ | ------------- | --------------------------- | -------------------------------------------------- |
| teacher-bff (ai03) | POST /graphql | 反向代理教师端 GraphQL 请求 | teacher-bff 就绪前返回 502前端使用本地 mock 数据 |
| student-bff (ai04) | POST /graphql | 反向代理学生端 GraphQL 请求 | student-bff 就绪前返回 502 |
| parent-bff (ai05) | POST /graphql | 反向代理家长端 GraphQL 请求 | parent-bff 就绪前返回 502 |
| 被调用方 | Method.Path | 用途 | mock 策略 | 阶段 |
| ------------------ | ----------------------------------- | ------------------------------------------ | --------------------------------------------------------------- | ---- |
| iam (ai06) | `GET /.well-known/jwks.json` | 拉 RS256 公钥集JWKSTTL 5min 缓存 | iam 就绪前使用本地固定 mock RS256 公钥(与 mock 私钥配对签发 mock JWT| P2 |
| iam (ai06) | `GET /healthz` | /readyz 下游健康检查 | iam 就绪前 /readyz 软失败(返回 200 + degraded | P2 |
| teacher-bff (ai03) | `POST /graphql`(反向代理) | 代理教师端 GraphQL 请求 | teacher-bff 就绪前返回 503 + Retry-After前端降级到本地 mock | P2 |
| student-bff (ai04) | `POST /graphql`(反向代理) | 代理学生端 GraphQL 请求 | student-bff 就绪前返回 503 | P3 |
| parent-bff (ai05) | `POST /graphql`(反向代理) | 代理家长端 GraphQL 请求 | parent-bff 就绪前返回 503 | P4 |
| core-edu (ai08) | `GET /healthz` + 业务 REST反向代理| /readyz 检查 + 业务请求代理 | core-edu 就绪前 /readyz 软失败 | P3 |
| content (ai09) | `GET /healthz` + 业务 REST | /readyz 检查 + 业务请求代理 | content 就绪前 /readyz 软失败 | P4 |
| data-ana (ai11) | `GET /healthz` + 业务 REST | /readyz 检查 + 业务请求代理 | data-ana 就绪前 /readyz 软失败 | P4 |
| msg (ai10) | `GET /healthz` + 业务 REST | /readyz 检查 + 业务请求代理 | msg 就绪前 /readyz 软失败 | P5 |
| ai (ai12) | `GET /healthz` + 业务 REST | /readyz 检查 + 业务请求代理 | ai 就绪前 /readyz 软失败 | P5 |
> **JWKS 缓存策略**[packages/shared-go/jwks/jwks.go](../../../packages/shared-go/jwks/jwks.go)
> - TTL 5min到期后台异步刷新不阻塞请求
> - kid 未命中时强制同步刷新一次
> - 刷新失败保留旧公钥集继续服务fail-open 1 次后 fail-close
> - 启动时同步拉取一次,失败则 panic 拒绝启动
### 2.4 shared-go 包依赖
依据 [president-final-rulings.md](../../president-final-rulings.md) §2.19 裁决ai01 直接 import `packages/shared-go`
| 模块 | 用途 | 接入阶段 |
| ----------------- | ------------------------------------------ | -------- |
| `shared-go/jwks` | JWKS FetcherHTTP 拉 RS256 公钥 + 缓存) | P2.2 |
| `shared-go/logger`| 结构化日志zap 或 slog待 ISSUE-004 仲裁)| P2.1 |
| `shared-go/tracer`| OTel tracer 初始化(评估接入,若接口兼容) | P2.1 |
| `shared-go/env` | 环境变量加载(评估接入,若接口兼容) | P2.1 |
---
@@ -65,38 +141,70 @@
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— GetPublicKey 拉取验签公钥
- [ ] teacher-bff GraphQL :3003 启用ai03
- [ ] student-bff GraphQL :3009 启用ai04
- [ ] parent-bff GraphQL :3010 启用ai05
| 上游 | 就绪标志 | 阶段 | 状态 |
| ---- | -------- | ---- | ---- |
| coord | shared-go 包骨架tracer/logger/jwks/env 4 模块)| 批次 0 | ✅ 已完成 |
| coord | ISSUE-001 仲裁JWKS HTTP 确认)| P2 启动前 | ⏳ 待仲裁 |
| coord | ISSUE-002 仲裁shared-go 接入)| P2 启动前 | ⏳ 待仲裁 |
| coord | ISSUE-003 仲裁API 版本化路由方案)| P2.7 前 | ⏳ 待仲裁 |
| coord | ISSUE-004 仲裁zap vs slog| P2.1 前 | ⏳ 待仲裁 |
| iam (ai06) | `GET /.well-known/jwks.json` HTTP 端点 + RS256 JWT 签发 | P2 | ⏳ |
| iam (ai06) | `GET /healthz` 端点 | P2 | ⏳ |
| teacher-bff (ai03) | `POST /graphql` :3003 启用 + `GET /healthz` | P2 | ⏳ |
| student-bff (ai04) | `POST /graphql` :3009 启用 + `GET /healthz` | P3 | ⏳ |
| core-edu (ai08) | `GET /healthz` + classes 合并 | P3 | ⏳ |
| parent-bff (ai05) | `POST /graphql` :3010 启用 + `GET /healthz` | P4 | ⏳ |
| content (ai09) | `GET /healthz` + REST 端点 | P4 | ⏳ |
| data-ana (ai11) | `GET /healthz` + REST 端点 | P4 | ⏳ |
| msg (ai10) | `GET /healthz` + REST 端点 | P5 | ⏳ |
| ai (ai12) | `GET /healthz` + REST 端点 | P5 | ⏳ |
| push-gateway (ai02) | :8081 启用 + /internal/pushWebSocket 协作评估)| P5 | ⏳ |
### 3.2 我的就绪标志(供下游消费)
- [ ] api-gateway HTTP :8080 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 iam 连通性检查通过)
- [ ] JWT 验签链路打通(使用 iam 公钥校验 access_token
- [ ] /api/auth/* 代理到 iam 认证链路可用
- [ ] /api/teacher/* /api/student/* /api/parent/* 反向代理到各 BFF 可用
- [ ] 限流IP 级令牌桶)+ 熔断(各后端独立熔断器)生效
| 阶段 | 就绪标志 | 状态 |
| ---- | -------- | ---- |
| P2 | api-gateway HTTP :8080 启用(/healthz 返回 200| ⏳ |
| P2 | /readyz 返回 200含 iam + teacher-bff 连通性检查通过)| ⏳ |
| P2 | JWT RS256 验签链路打通(使用 iam JWKS 公钥校验 access_token| ⏳ |
| P2 | /api/v1/iam/v1/* 代理到 iam 认证链路可用 | ⏳ |
| P2 | /api/v1/teacher/v1/* 反向代理到 teacher-bff GraphQL 可用 | ⏳ |
| P2 | 限流IP 级令牌桶)+ 熔断(共享 downstream+ CORS 白名单生效 | ⏳ |
| P2 | 7 个业务指标暴露在 /metrics | ⏳ |
| P2 | 错误响应统一 ActionState 信封 + GW_ 前缀 | ⏳ |
| P3 | /api/v1/student/v1/* + /api/v1/exams/v1/* 等路由可用 | ⏳ |
| P4 | /api/v1/parent/v1/* + /api/v1/textbooks/v1/* + /api/v1/analytics/v1/* 路由可用 | ⏳ |
| P5 | /api/v1/notifications/v1/* + /api/v1/ai/v1/* 路由可用 | ⏳ |
| P6 | 限流迁 Redis + 测试覆盖率 ≥ 80% | ⏳ |
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游各前端 portal 消费)
在 api-gateway 真实就绪前,为下游(各前端 portal提供以下 mock
在 api-gateway 真实就绪前,为下游(teacher-portal / student-portal / parent-portal / admin-portal提供以下 mock
- **HTTP mock**:使用 MSWMock Service Worker或本地 nginx 拦截
- /api/auth/login 返回固定 JWTmock 签发)+ UserInfo
- /api/teacher/* /api/student/* /api/parent/* 直接返回各 BFF 的 mock GraphQL 响应
- /healthz /readyz 返回 200
- **HTTP mock**(由各前端 portal 自行用 MSW 拦截api-gateway 不提供 mock 服务):
- `/api/v1/iam/v1/login` 返回固定 JWTmock 签发)+ UserInfo
- `/api/v1/teacher/v1/*` `/api/v1/student/v1/*` `/api/v1/parent/v1/*` 直接返回各 BFF 的 mock GraphQL 响应
- `/healthz` `/readyz` 返回 200
- **JWT mock**:前端开发期使用固定 mock JWTapi-gateway 就绪前不走真实验签)
### 4.2 我消费的 mock
### 4.2 我消费的 mock(在真实上游就绪前)
在真实上游就绪前api-gateway 使用以下 mock
- **iam 公钥**:使用本地固定 mock RS256 公钥(与 mock 私钥配对),验签 mock JWT
- **iam 权限校验**GetEffectiveAccess 返回 allowed=true放行所有请求
- **各 BFF 代理**BFF 就绪前返回 503 + Retry-After前端降级到本地 mock 数据
- **iam JWKS 公钥**:使用本地固定 mock RS256 公钥(与 mock 私钥配对),验签 mock JWTDevMode 下生效)
- **iam /readyz 检查**iam 就绪前软失败,返回 200 + `degraded: true`
- **各 BFF 代理**BFF 就绪前返回 503 + `Retry-After`,前端降级到本地 mock 数据
- **下游 /healthz 检查**:未就绪服务软失败(返回 200 + degraded已就绪服务硬失败返回 503
---
## §5 变更记录
| 版本 | 日期 | 变更内容 | 变更者 |
| ---- | ---------- | ------------------------------------------------------------------------ | ------ |
| v1 | 2026-07-09 | 初始创建(含错误:/api/* 路径、gRPC GetPublicKey、GetEffectiveAccess | ai01 |
| v2 | 2026-07-10 | 修正:路径 /api→/api/v1、JWKS 拉取 gRPC→HTTP、删除 GetEffectiveAccess、错误码加 GW_ 前缀、对齐 ActionState 信封、补充 shared-go 依赖、补充 admin-portal P6 路由 | ai01 |

View File

@@ -1,53 +1,223 @@
# content 对接契约
> 负责人ai09
> 关联:[matrix.md](./matrix.md)、[content.proto](../../../packages/shared-proto/proto/content.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](../matrix.md)、[content.proto](../../../packages/shared-proto/proto/content.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[../../services/content/docs/02-architecture-design.md](../../../services/content/docs/02-architecture-design.md)、[../objections/content_issue.md](../objections/content_issue.md)、[../worklines/content_workline.md](../worklines/content_workline.md)
> 当前分支:`feat-review-content-module-docs-WAIyMA`
> 契约策略:[coord-final-decisions.md §2 B2](../../coord-final-decisions.md) "首次实现即 gRPC 调用下游"——content 对外契约统一为 gRPCREST 端点仅自身管理面用,不作为下游消费契约)
---
## §0 契约状态总览
| 维度 | 当前状态 | 目标状态P4 完成) |
| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| gRPC | ❌ 未实现([content.proto](../../../packages/shared-proto/proto/content.proto) 仅 5 RPC 定义) | ✅ 21 RPC4 Service |
| HTTP REST | ✅ 已实现 22 端点4 Controller | 🔁 保留作为管理面(不作为下游契约,下游统一 gRPC |
| Kafka 发布 | ❌ 未实现 | ✅ 4 聚合 topic待 ISSUE-002 仲裁确认策略) |
| Kafka 消费 | ❌ 未实现 | 🟢 可选content 是上游,不主动消费 core-edu 事件,见 ISSUE-005 |
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有
### 1.1 gRPC 接口(目标态 · P4 完成
| Service | RPC | 请求 | 响应 | 端口 |
| --------------------- | ------------------ | ------------------------- | -------------------------- | ----- |
| TextbookService | CreateTextbook | CreateTextbookRequest | Textbook | 50054 |
| TextbookService | GetTextbook | GetTextbookRequest | Textbook | 50054 |
| TextbookService | ListTextbooks | ListTextbooksRequest | ListTextbooksResponse | 50054 |
| ChapterService | GetChapter | GetChapterRequest | Chapter | 50054 |
| ChapterService | ListChapters | ListChaptersRequest | ListChaptersResponse | 50054 |
| ChapterService | CreateChapter | CreateChapterRequest | Chapter | 50054 |
| ChapterService | UpdateChapter | UpdateChapterRequest | Chapter | 50054 |
| KnowledgeGraphService | GetPrerequisites | GetPrerequisitesRequest | KnowledgePointsResponse | 50054 |
| KnowledgeGraphService | GetLearningPath | GetLearningPathRequest | LearningPath | 50054 |
| KnowledgeGraphService | AddPrerequisite | AddPrerequisiteRequest | AddPrerequisiteResponse | 50054 |
| KnowledgeGraphService | RemovePrerequisite | RemovePrerequisiteRequest | RemovePrerequisiteResponse | 50054 |
| QuestionService | CreateQuestion | CreateQuestionRequest | Question | 50054 |
| QuestionService | GetQuestion | GetQuestionRequest | Question | 50054 |
| QuestionService | ListQuestions | ListQuestionsRequest | ListQuestionsResponse | 50054 |
| QuestionService | UpdateQuestion | UpdateQuestionRequest | Question | 50054 |
| QuestionService | DeleteQuestion | DeleteQuestionRequest | DeleteQuestionResponse | 50054 |
| QuestionService | PublishQuestion | PublishQuestionRequest | PublishQuestionResponse | 50054 |
| QuestionService | SearchQuestions | SearchQuestionsRequest | SearchQuestionsResponse | 50054 |
> 依据 [coord-final-decisions.md §3.3](../../coord-final-decisions.md) N1/N3/N5 + [02-architecture-design.md §4.2](../../../services/content/docs/02-architecture-design.md)
> 待 ISSUE-004 仲裁后定稿,当前按建议方案 21 RPC 列出
### 1.2 HTTP 端点(如有)
| Service | RPC | 请求 | 响应 | 端口 | 阶段 |
| --------------------- | -------------------- | ------------------------------------------------------------ | ----------------------------------------------------------- | ----- | ---- |
| TextbookService | CreateTextbook | CreateTextbookRequest{title, subject_id, grade_id, version?} | Textbook | 50054 | P4 |
| TextbookService | GetTextbook | GetTextbookRequest{id} | Textbook | 50054 | P4 |
| TextbookService | ListTextbooks | ListTextbooksRequest{subject_id?, grade_id?, page_token, page_size} | ListTextbooksResponse{textbooks[], next_page_token} | 50054 | P4 |
| TextbookService | UpdateTextbook | UpdateTextbookRequest{id, title?, status?, metadata?} | Textbook | 50054 | P4 |
| TextbookService | DeleteTextbook | DeleteTextbookRequest{id} | Empty | 50054 | P4 |
| ChapterService | CreateChapter | CreateChapterRequest{textbook_id, title, order, parent_id?} | Chapter | 50054 | P4 |
| ChapterService | GetChapter | GetChapterRequest{id} | Chapter | 50054 | P4 |
| ChapterService | ListChapters | ListChaptersRequest{textbook_id, parent_id?} | ListChaptersResponse{chapters[]} | 50054 | P4 |
| ChapterService | UpdateChapter | UpdateChapterRequest{id, title?, order?, status?} | Chapter | 50054 | P4 |
| ChapterService | DeleteChapter | DeleteChapterRequest{id} | Empty | 50054 | P4 |
| KnowledgeGraphService | GetPrerequisites | GetPrerequisitesRequest{knowledge_point_id, depth?} | KnowledgePointsResponse{points[]} | 50054 | P4 |
| KnowledgeGraphService | GetLearningPath | GetLearningPathRequest{student_id, subject_id} | LearningPath{points[], recommended_order[]} | 50054 | P4 |
| KnowledgeGraphService | AddPrerequisite | AddPrerequisiteRequest{kp_id, prerequisite_id} | Empty | 50054 | P4 |
| KnowledgeGraphService | RemovePrerequisite | RemovePrerequisiteRequest{kp_id, prerequisite_id} | Empty | 50054 | P4 |
| QuestionService | CreateQuestion | CreateQuestionRequest{knowledge_point_id, type, content, options?, answer, explanation?, difficulty?, source?, created_by?} | Question | 50054 | P4 |
| QuestionService | BatchCreateQuestions | BatchCreateQuestionsRequest{questions[]} | BatchCreateQuestionsResponse{ids[], failed[]} | 50054 | P4 |
| QuestionService | GetQuestion | GetQuestionRequest{id} | Question | 50054 | P4 |
| QuestionService | ListQuestions | ListQuestionsRequest{knowledge_point_id?, type?, difficulty?, status?, page_token, page_size} | ListQuestionsResponse{questions[], next_page_token} | 50054 | P4 |
| QuestionService | UpdateQuestion | UpdateQuestionRequest{id, content?, answer?, status?} | Question | 50054 | P4 |
| QuestionService | DeleteQuestion | DeleteQuestionRequest{id} | Empty | 50054 | P4 |
| QuestionService | PublishQuestion | PublishQuestionRequest{id} | Empty | 50054 | P4 |
| QuestionService | SearchQuestions | SearchQuestionsRequest{q?, type?, difficulty?, knowledge_point_id?, page_token, page_size} | SearchQuestionsResponse{questions[], total, next_page_token} | 50054 | P5 |
无对外 HTTP 端点,仅 gRPC。
**RPC 总数**21TextbookService 5 + ChapterService 5 + KnowledgeGraphService 4 + QuestionService 7
### 1.3 GraphQL schema如 BFF
> ⚠️ **matrix.md §2 同步项**:当前 matrix.md §2 登记 content 为 18 RPC待 ISSUE-004 仲裁后需更新为 21 RPC差额ChapterService 补 Update + Delete = +2TextbookService 补 Update + Delete = +2QuestionService 原 contract 已含 Publish/Searchdesign doc 缺,对齐后 +0原合计 18 + 4 - 1 = 21
不适用。
#### proto message 字段说明(关键字段)
### 1.4 Kafka 事件发布(如有)
```protobuf
message Textbook {
string id = 1;
string title = 2;
string subject_id = 3;
string grade_id = 4;
string version = 5;
string status = 6; // draft/pending_review/published/archived
string tenant_id = 7; // 多租户预留
google.protobuf.Struct metadata = 8; // 扩展字段
int64 created_at = 9;
int64 updated_at = 10;
}
| Topic | Event | 消费方 |
| ---------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------- |
| edu.content.knowledge_point.events | KnowledgePointEventaction: created/updated/prerequisite_added/prerequisite_removed | data-ana / ai / Neo4j Sync Worker / ES Sync Worker |
| edu.content.question.events | QuestionEventaction: created/updated/published/deleted | data-ana |
message Chapter {
string id = 1;
string textbook_id = 2;
string title = 3;
int32 order = 4; // DB 列名 order_numproto 字段名 order
string parent_id = 5; // 树形结构
string status = 6;
int64 created_at = 7;
int64 updated_at = 8;
}
message KnowledgePoint {
string id = 1;
string chapter_id = 2;
string title = 3;
string description = 4;
int32 difficulty = 5; // 1-5
google.protobuf.Struct metadata = 6;
int64 created_at = 7;
int64 updated_at = 8;
}
message Question {
string id = 1;
string knowledge_point_id = 2;
string type = 3; // single_choice/multiple_choice/short_answer/essay
string content = 4; // 题干HTML/markdown
google.protobuf.Struct options = 5; // 选项(选择题)
string answer = 6;
string explanation = 7;
int32 difficulty = 8; // 1-5
string status = 9; // draft/pending_review/published/rejected/archived
string source = 10; // manual/ai_generated/imported
string created_by = 11;
google.protobuf.Struct metadata = 12;
int64 created_at = 13;
int64 updated_at = 14;
}
```
### 1.2 HTTP 端点(管理面 · 非下游契约)
> ⚠️ 以下 REST 端点仅供 content 自身管理面/直接 Gateway 访问用,**下游服务teacher-bff/student-bff/ai统一走 gRPC**,不消费以下 REST 端点。
当前已实现 22 端点4 Controller详见 [02-architecture-design.md §4.1](../../../services/content/docs/02-architecture-design.md)。P4 重构后将统一加 `/v1/` 版本前缀(见 ISSUE-008
### 1.3 GraphQL schema
不适用。content 是 gRPC 服务,不暴露 GraphQL。
### 1.4 Kafka 事件发布(目标态 · P4 完成)
> 待 ISSUE-002 仲裁确认 topic 命名策略,当前按**聚合 topic + action 字段**策略列出(与 matrix.md §4 / events.proto ClassEvent 模式一致)
| Topic | Event message | action 字段值 | 消费方 | 阶段 |
| ------------------------------------ | -------------------- | ---------------------------------------------------------- | -------------------------------------------------- | ---- |
| edu.content.textbook.events | TextbookEvent | created / updated / published / archived | data-ana / msg通知教师教材发布 | P4 |
| edu.content.chapter.events | ChapterEvent | created / updated / deleted | data-ana | P4 |
| edu.content.knowledge_point.events | KnowledgePointEvent | created / updated / prerequisite_added / prerequisite_removed | data-ana / ai / Neo4j Sync Worker / ES Sync Worker | P4 |
| edu.content.question.events | QuestionEvent | created / updated / published / deleted | data-ana / ai / ES Sync Worker | P4 |
> ⚠️ **ISSUE-003 待仲裁**:当前 matrix.md §4 仅登记 kp + question 两类 topicTextbook/Chapter 事件未登记。本契约按 design doc §5.1 补全 4 类,待 coord 仲裁后同步 matrix.md。
#### 事件 payload schema待补 events.proto
需在 [events.proto](../../../packages/shared-proto/proto/events.proto) 追加 4 个 message
```protobuf
message TextbookEvent {
string event_id = 1;
string aggregate_id = 2;
string event_type = 3; // edu.content.textbook.created 等
int64 occurred_at = 4;
string textbook_id = 5;
string title = 6;
string subject_id = 7;
string grade_id = 8;
string version = 9;
string action = 10; // created/updated/published/archived
map<string, string> metadata = 11;
}
message ChapterEvent {
string event_id = 1;
string aggregate_id = 2;
string event_type = 3;
int64 occurred_at = 4;
string chapter_id = 5;
string textbook_id = 6;
string title = 7;
int32 order = 8;
string action = 9; // created/updated/deleted
map<string, string> metadata = 10;
}
message KnowledgePointEvent {
string event_id = 1;
string aggregate_id = 2;
string event_type = 3;
int64 occurred_at = 4;
string kp_id = 5;
string chapter_id = 6;
string title = 7;
int32 difficulty = 8;
string action = 9; // created/updated/prerequisite_added/prerequisite_removed
string prerequisite_id = 10; // 仅 prerequisite_* 有值
map<string, string> metadata = 11;
}
message QuestionEvent {
string event_id = 1;
string aggregate_id = 2;
string event_type = 3;
int64 occurred_at = 4;
string question_id = 5;
string kp_id = 6;
string type = 7; // single_choice 等
int32 difficulty = 8;
string status = 9;
string source = 10; // manual/ai_generated/imported
string created_by = 11;
string action = 12; // created/updated/published/deleted
map<string, string> metadata = 13;
}
```
### 1.5 错误码前缀
`CONTENT_`如 CONTENT_TEXTBOOK_NOT_FOUND、CONTENT_QUESTION_DUPLICATE
`CONTENT_`详见 [02-architecture-design.md §6.2](../../../services/content/docs/02-architecture-design.md)
| 错误码 | HTTP | gRPC status | 触发条件 |
| ------------------------- | ---- | ------------------ | ---------------------------------- |
| CONTENT_VALIDATION_ERROR | 400 | INVALID_ARGUMENT | Zod 校验失败 / 题型非法 / 难度越界 |
| CONTENT_NOT_FOUND | 404 | NOT_FOUND | 资源不存在 |
| CONTENT_PERMISSION_DENIED | 403 | PERMISSION_DENIED | 权限不足 |
| CONTENT_CONFLICT | 409 | ALREADY_EXISTS | 唯一约束冲突 / 状态机非法转换 |
| CONTENT_BUSINESS_ERROR | 422 | FAILED_PRECONDITION | 业务规则违反(如循环依赖检测) |
| CONTENT_DATABASE_ERROR | 500 | INTERNAL | Drizzle 操作异常 |
| CONTENT_INTERNAL_ERROR | 500 | INTERNAL | 未知异常 |
| CONTENT_NEO4J_UNAVAILABLE | 503 | UNAVAILABLE | Neo4j 不可用且无降级路径 |
### 1.6 健康检查端点
| 端点 | 用途 | 鉴权 | 响应 |
| ----------- | ------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- |
| GET /healthz | 存活探针liveness仅返回进程状态 | 无 | `{ "status": "ok", "service": "content", "timestamp": "..." }` |
| GET /readyz | 就绪探针readiness检查 DB/Neo4j/Kafka 三依赖 | 无 | `{ "status": "ok|degraded|down", "checks": { database, neo4j, kafka_producer, kafka_consumer } }` |
| GET /metrics | Prometheus 指标 | 无 | Prometheus exposition format |
---
@@ -55,18 +225,30 @@
### 2.1 gRPC 调用(同步)
无直接 gRPC 调用上游。content 通过 Kafka 事件接收 core-edu 班级/学生变更用于数据一致性
**无**。content 是内容资源上游提供方,不主动调用其他业务服务 gRPC
> content 与 core-edu 的关系澄清(见 ISSUE-005content 是 core-edu 的上游core-edu 调 content 查知识点不是下游。content 不消费 core-edu 事件。
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| ---------------- | --------------------------------- | --------------- | ----------------------------------------------------- |
| edu.class.events | ClassEventaction: transferred | core-edu (ai08) | core-edu 就绪前不订阅content 内部不依赖班级实时数据 |
| edu.exam.events | ExamEvent | core-edu (ai08) | core-edu 就绪前忽略,题目关联知识点不依赖考试事件 |
**P4 不订阅任何事件**(按 ISSUE-005 建议删除原 `edu.teaching.content.invalidated` 条目)。
### 2.3 HTTP 调用(如有)
若 P6+ 有教材/章节联动需求,由 core-edu 主动调用 content gRPC UpdateQuestion/UpdateTextbook 状态变更,而非事件驱动。
无。
### 2.3 HTTP 调用
**无**
### 2.4 基础设施依赖
| 依赖 | 用途 | 配置项env.ts | 必需性 |
| --------------- | ----------------------------- | ----------------------------- | ---------------------- |
| MySQL 8 | 写模型主库4 张业务表 + outbox | DATABASE_URL | 🔴 必需 |
| Neo4j 5 | 知识图谱PREREQUISITE_OF | NEO4J_URL / NEO4J_PASSWORD | 🔴 必需(图谱查询核心) |
| Kafka | Outbox 事件发布 | KAFKA_BROKERS待补 env.ts | 🔴 必需Outbox 强制) |
| Redis | 缓存(教材树/章节树P5+ | REDIS_URL已预留 | 🟢 P5+ 可选 |
| Elasticsearch 8 | 题库全文检索P5+ | ES_URL已预留 | 🟢 P5+ 必需 |
| OTLP Collector | 链路追踪 | OTEL_EXPORTER_OTLP_ENDPOINT | 🟢 可选(未配置时降级) |
---
@@ -74,37 +256,113 @@
### 3.1 我依赖的上游就绪标志
- [ ] core-edu gRPC 50053 启用ai08—— 用于知识点与班级关联可选content 可先独立运行)
- [ ] edu.class.events / edu.exam.events topic 有事件发布ai08
| 上游 | 就绪标志 | 必需性 | mock 策略 |
| -------------- | ----------------------------------------- | ---------------------- | ----------------------------------------------- |
| infra | MySQL 8 可用 | 🔴 必需 | 本地 docker-compose |
| infra | Neo4j 5 可用 | 🔴 必需 | 本地 docker-compose未配置时图谱查询返回 503 |
| infra | Kafka 集群可用 | 🔴 必需 | 本地 docker-compose |
| shared-proto | content.proto / events.proto 补全 | 🔴 必需P4.6 自身完成) | ai09 自行修改 proto |
| core-edu (ai08) | gRPC 50053 启用 | 🟢 可选content 独立) | 不依赖 core-edu 实时数据 |
| ai (ai12) | gRPC 50058 启用 | 🟢 P5 联调时必需 | grpc-mock 拦截 |
### 3.2 我的就绪标志(供下游消费)
#### P4 就绪(核心交付)
- [ ] content gRPC 50054 启用HealthService.Check 返回 SERVING
- [ ] TextbookService 3 RPC 可调用
- [ ] ChapterService 4 RPC 可调用
- [ ] TextbookService 5 RPC 可调用Create/Get/List/Update/Delete
- [ ] ChapterService 5 RPC 可调用Create/Get/List/Update/Delete
- [ ] KnowledgeGraphService 4 RPC 可调用GetPrerequisites/GetLearningPath/AddPrerequisite/RemovePrerequisite
- [ ] QuestionService 7 RPC 可调用(含 SearchQuestions 全文检索
- [ ] edu.content.knowledge_point.events / edu.content.question.events topic 可发布
- [ ] QuestionService 7 RPC 可调用(Create/BatchCreate/Get/List/Update/Delete/Publish/Search
- [ ] edu.content.textbook.events / chapter.events / knowledge_point.events / question.events 4 topic 可发布
- [ ] /readyz 返回 DB/Neo4j/Kafka 三依赖状态
- [ ] Outbox Publisher worker 运行中content_outbox_events 表 PENDING 事件 < 100
- [ ] Neo4j Sync Worker 运行中(知识点节点最终一致延迟 < 2s
#### P5 就绪(检索 + AI 集成)
- [ ] GET /questions/search 检索 API 可用(延迟 < 200ms
- [ ] QuestionService.SearchQuestions gRPC 可调用
- [ ] QuestionService.BatchCreateQuestions 与 ai12 联调通过
- [ ] ES Sync Worker 运行中(题目索引最终一致延迟 < 2s
- [ ] 测试覆盖率 ≥ 80%
#### P6+ 就绪(演进)
- [ ] Question 审核工作流状态机完整
- [ ] 知识图谱可视化 API 可用
- [ ] 教材版本管理启用
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游消费)
在 content 真实服务就绪前为下游teacher-bff / student-bff / ai / data-ana提供以下 mock
- **gRPC mock**:使用 grpc-mock 拦截 50054 端口
- TextbookService.ListTextbooks 返回固定 5 个 Textbook语数英理化
- ChapterService.ListChapters 返回固定章节树(每教材 10 章)
- KnowledgeGraphService.GetLearningPath 返回固定 8 个 KnowledgePoint 推荐顺序
- KnowledgeGraphService.GetPrerequisites 返回固定 3前置知识点
- QuestionService.SearchQuestions 返回固定 20 个 Question含 options
- **Kafka mock**content 就绪前不发布真实事件,下游使用本地 stub
#### gRPC mockgrpc-mock 拦截 50054 端口
| Service | RPC | Mock 返回 |
| --------------------- | --------------------- | ---------------------------------------------------------- |
| TextbookService | ListTextbooks | 固定 5 Textbook语数英理化subject_id=math/chn/eng/phy/chem |
| TextbookService | GetTextbook | 返回第一个 Textbook |
| ChapterService | ListChapters | 固定章节树(每教材 10 章,含 parent_id 树形结构) |
| KnowledgeGraphService | GetLearningPath | 固定 8 个 KnowledgePoint 推荐顺序(按 difficulty 升序) |
| KnowledgeGraphService | GetPrerequisites | 固定 3 个前置知识点 |
| QuestionService | ListQuestions | 固定 20 个 Question含 4 种题型各 5 个) |
| QuestionService | SearchQuestions | 固定 20 个 Question含 options |
| QuestionService | BatchCreateQuestions | 返回成功 + 生成 20 个 cuid2 ID |
| QuestionService | CreateQuestion | 返回成功 + 生成 1 个 cuid2 ID |
#### Kafka mock
content 就绪前不发布真实事件,下游使用本地 stubdata-ana / ai 各自维护测试数据集)。
### 4.2 我消费的 mock
在真实 core-edu 就绪前content 使用以下 mock
content P4 不消费任何上游事件,无需 mock 上游。
- 班级/学生数据:不依赖 core-edu 实时数据,知识点关联使用固定 subject_id/grade
- 事件订阅:不订阅 edu.class.events / edu.exam.events内部数据自洽
P5 联调阶段消费 ai (ai12) 的 gRPC使用 grpc-mock 拦截 50058 端口:
- AiService.GenerateQuestion 返回固定 1 个 Questionsource=ai_generated
- AiService.Chat 返回固定文本响应
---
## §5 契约一致性核查2026-07-10
> 本节记录 contract.md 与 design doc / matrix.md / proto 的对齐情况
### 5.1 与 02-architecture-design.md 对齐
| 维度 | design doc | contract.md本文件 | 一致性 | 备注 |
| ------------- | -------------------------------- | ------------------------------ | ------ | ------------------------------------------ |
| gRPC RPC 数 | §4.2 列 18 RPC | §1.1 列 21 RPC | ⚠️ | 待 ISSUE-004 仲裁;建议以 21 RPC 为准 |
| 事件 topic | §5.1 列 12 独立 topic | §1.4 列 4 聚合 topic | ⚠️ | 待 ISSUE-002 仲裁;建议以 4 聚合 topic 为准 |
| 错误码 | §6.2 列 8 个 | §1.5 列 8 个 | ✅ | 一致 |
| 健康检查 | §6.6 /readyz 多依赖 | §1.6 三依赖 | ✅ | 一致 |
### 5.2 与 matrix.md 对齐
| 维度 | matrix.md | contract.md本文件 | 一致性 | 备注 |
| ------------- | -------------------------------- | ------------------------------ | ------ | ------------------------------------------ |
| gRPC RPC 总数 | §2 列 18 RPC | §1.1 列 21 RPC | ⚠️ | 待 ISSUE-004 仲裁后同步 matrix.md |
| Kafka topic | §4 列 2 topickp + question | §1.4 列 4 topic | ⚠️ | 待 ISSUE-003 仲裁后同步 matrix.md |
| 错误码前缀 | §6 列 CONTENT_ | §1.5 列 CONTENT_ | ✅ | 一致 |
| 端口 | §2 列 50054 | §1.1 列 50054 | ✅ | 一致 |
### 5.3 与 proto 文件对齐
| 文件 | 当前状态 | 目标状态P4 完成) |
| ------------- | ---------------------------------------------------------------- | ------------------------------------------------ |
| content.proto | 5 RPCTextbookService 3 + KnowledgeGraphService 2无 Chapter/Question | 21 RPC4 Service 完整) |
| events.proto | 4 messageClassEvent/ExamEvent/HomeworkEvent/GradeEvent | 8 message+ TextbookEvent/ChapterEvent/KnowledgePointEvent/QuestionEvent |
---
## §6 变更记录
| 日期 | 版本 | 变更内容 | 变更人 |
| ---------- | ---- | ---------------------------------------------------------------------------------------------- | ------ |
| 2026-07-09 | v1.0 | 初版18 RPC2 topic | coord |
| 2026-07-10 | v1.1 | ai09 复审:补全至 21 RPC+ TextbookService Update/Delete + ChapterService Update/Delete + QuestionService Publish/Search补全至 4 topic+ Textbook/Chapter 事件);补 events.proto 4 message 草案;补 §0 状态总览 / §5 一致性核查 / §6 变更记录;明确 REST 端点为管理面(非下游契约) | ai09 |

View File

@@ -1,59 +1,151 @@
# core-edu 对接契约
> 负责人ai08
> 关联:[matrix.md](./matrix.md)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](./matrix.md)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[services/core-edu/docs/02-architecture-design.md](../../../services/core-edu/docs/02-architecture-design.md)、[objections/core-edu_issue.md](../objections/core-edu_issue.md)
> 状态说明:本契约按 P3 目标态描述。P2 已就绪部分标注 ✅P3 待实施部分标注 ⏳(依赖 [objections/core-edu_issue.md](../objections/core-edu_issue.md) ISSUE-001 ~ ISSUE-006 仲裁结果)
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有
### 1.1 gRPC 接口(P3 启用,端口 50053
| Service | RPC | 请求 | 响应 | 端口 |
| ----------------- | ----------------------- | ------------------------------ | --------------------------- | ----- |
| ClassService | GetClass | GetClassRequest | ClassInfo | 50053 |
| ClassService | GetClassesByTeacher | GetClassesByTeacherRequest | GetClassesByTeacherResponse | 50053 |
| ClassService | BatchGetClasses | BatchGetClassesRequest | BatchGetClassesResponse | 50053 |
| ClassService | ListStudentsByClass | ListStudentsByClassRequest | ListStudentsByClassResponse | 50053 |
| ExamService | CreateExam | CreateExamRequest | CreateExamResponse | 50053 |
| ExamService | GetExam | GetExamRequest | Exam | 50053 |
| ExamService | ListExamsByClass | ListExamsByClassRequest | ListExamsResponse | 50053 |
| ExamService | UpdateExam | UpdateExamRequest | UpdateExamResponse | 50053 |
| ExamService | DeleteExam | DeleteExamRequest | DeleteExamResponse | 50053 |
| HomeworkService | AssignHomework | AssignHomeworkRequest | AssignHomeworkResponse | 50053 |
| HomeworkService | GetHomework | GetHomeworkRequest | Homework | 50053 |
| HomeworkService | ListHomeworkByClass | ListHomeworkByClassRequest | ListHomeworkResponse | 50053 |
| HomeworkService | SubmitHomework | SubmitHomeworkRequest | SubmitHomeworkResponse | 50053 |
| GradeService | RecordGrade | RecordGradeRequest | RecordGradeResponse | 50053 |
| GradeService | GetGrade | GetGradeRequest | Grade | 50053 |
| GradeService | ListGradesByStudent | ListGradesByStudentRequest | ListGradesResponse | 50053 |
| GradeService | ListGradesByExam | ListGradesByExamRequest | ListGradesResponse | 50053 |
| GradeService | ListGradesByHomework | ListGradesByHomeworkRequest | ListGradesResponse | 50053 |
| AttendanceService | RecordAttendance | RecordAttendanceRequest | RecordAttendanceResponse | 50053 |
| AttendanceService | GetAttendance | GetAttendanceRequest | Attendance | 50053 |
| AttendanceService | ListAttendanceByStudent | ListAttendanceByStudentRequest | ListAttendanceResponse | 50053 |
| AttendanceService | ListAttendanceByClass | ListAttendanceByClassRequest | ListAttendanceResponse | 50053 |
> 包名:`next_edu_cloud.core_edu.v1`[coord-cross-review §2.1](../../coord-cross-review.md) 仲裁)
> 总计 5 Service / 27 RPC含 P3 新增 5 RPC
> 注:当前 `core_edu.proto` 实际仅 3 Service / 14 RPC缺 ClassService + AttendanceService + P3 新增 RPC见 [ISSUE-001](../objections/core-edu_issue.md#issue-001-ai08core_eduproto-实际状态与-coord-仲裁声称不一致p0)
### 1.2 HTTP 端点(如有)
| Service | RPC | 请求 | 响应 | 状态 |
| ------- | --- | ---- | ---- | ---- |
| ClassService | GetClass | GetClassRequest | ClassInfo | ⏳ P3 |
| ClassService | GetClassesByTeacher | GetClassesByTeacherRequest | GetClassesByTeacherResponse | ⏳ P3 |
| ClassService | BatchGetClasses | BatchGetClassesRequest | BatchGetClassesResponse | ⏳ P3 |
| ClassService | ListStudentsByClass | ListStudentsByClassRequest | ListStudentsByClassResponse | ⏳ P3 |
| ExamService | CreateExam | CreateExamRequest | CreateExamResponse | ⏳ proto 已定义 |
| ExamService | GetExam | GetExamRequest | Exam | ⏳ proto 已定义 |
| ExamService | ListExamsByClass | ListExamsByClassRequest | ListExamsResponse | ⏳ proto 已定义 |
| ExamService | UpdateExam | UpdateExamRequest | UpdateExamResponse | ⏳ proto 已定义 |
| ExamService | DeleteExam | DeleteExamRequest | DeleteExamResponse | ⏳ proto 已定义 |
| ExamService | **PublishExam** | PublishExamRequest | PublishExamResponse | ⏳ P3 新增proto 待补) |
| ExamService | **SubmitExam** | SubmitExamRequest含 answers | SubmitExamResponse | ⏳ P3 新增proto 待补) |
| ExamService | **GradeExam** | GradeExamRequest | GradeExamResponse | ⏳ P3 新增proto 待补) |
| HomeworkService | AssignHomework | AssignHomeworkRequest | AssignHomeworkResponse | ⏳ proto 已定义 |
| HomeworkService | GetHomework | GetHomeworkRequest | Homework | ⏳ proto 已定义 |
| HomeworkService | ListHomeworkByClass | ListHomeworkByClassRequest | ListHomeworkResponse | ⏳ proto 已定义 |
| HomeworkService | SubmitHomework | SubmitHomeworkRequest**P3 增强含 answers** | SubmitHomeworkResponse | ⏳ proto 已定义(缺 answers 字段) |
| HomeworkService | **GradeHomework** | GradeHomeworkRequest | GradeHomeworkResponse | ⏳ P3 新增proto 待补) |
| GradeService | RecordGrade | RecordGradeRequest | RecordGradeResponse | ⏳ proto 已定义 |
| GradeService | GetGrade | GetGradeRequest | Grade | ⏳ proto 已定义 |
| GradeService | ListGradesByStudent | ListGradesByStudentRequest | ListGradesResponse | ⏳ proto 已定义 |
| GradeService | ListGradesByExam | ListGradesByExamRequest | ListGradesResponse | ⏳ proto 已定义 |
| GradeService | ListGradesByHomework | ListGradesByHomeworkRequest | ListGradesResponse | ⏳ proto 已定义 |
| GradeService | **UpdateGrade** | UpdateGradeRequest | UpdateGradeResponse | ⏳ P3 新增proto 待补) |
| AttendanceService | **RecordAttendance** | RecordAttendanceRequest | RecordAttendanceResponse | ⏳ P3 新增proto 待补) |
| AttendanceService | **GetAttendance** | GetAttendanceRequest | Attendance | ⏳ P3 新增proto 待补) |
| AttendanceService | **ListAttendanceByStudent** | ListAttendanceByStudentRequest | ListAttendanceResponse | ⏳ P3 新增proto 待补) |
| AttendanceService | **ListAttendanceByClass** | ListAttendanceByClassRequest | ListAttendanceResponse | ⏳ P3 新增proto 待补) |
| HealthService | Check | grpc.health.v1.HealthCheckRequest | grpc.health.v1.HealthCheckResponse | ⏳ P3 |
无对外 HTTP 端点,仅 gRPC。
**统计**
- P2 基线proto 已定义3 Service / 14 RPCExamService 5 + HomeworkService 4 + GradeService 5
- P3 目标态5 Service / 27 RPC+ClassService 4 + AttendanceService 4 + P3 新增 5 RPC
- 增量13 RPCClassService 4 + AttendanceService 4 + PublishExam/SubmitExam/GradeExam/GradeHomework/UpdateGrade 5
### 1.2 HTTP 端点REST端口 3004
> 当前为 REST 入口P2 已就绪P3 启用 gRPC 后 REST 保留为 BFF 兼容入口
> 所有端点走 AuthMiddleware + PermissionGuard + Zod ValidationPipe + GlobalErrorFilterActionState 信封)
| Method | Path | 权限 | 状态 |
| ------ | ---- | ---- | ---- |
| POST | /exams | CORE_EDU_EXAM_CREATE | ✅ P2 |
| GET | /exams/:id | CORE_EDU_EXAM_READ | ✅ P2 |
| GET | /exams/class/:classId | CORE_EDU_EXAM_READ | ✅ P2 |
| PUT | /exams/:id | CORE_EDU_EXAM_UPDATE | ✅ P2 |
| DELETE | /exams/:id | CORE_EDU_EXAM_DELETE | ✅ P2 |
| POST | /exams/:id/publish | CORE_EDU_EXAM_PUBLISH | ⏳ P3 新增 |
| POST | /exams/:id/start | CORE_EDU_EXAM_SUBMIT | ⏳ P3 新增 |
| POST | /exams/:id/submit | CORE_EDU_EXAM_SUBMIT | ⏳ P3 新增 |
| POST | /exams/:id/grade | CORE_EDU_EXAM_GRADE | ⏳ P3 新增 |
| POST | /exams/:id/archive | CORE_EDU_EXAM_UPDATE | ⏳ P3 新增 |
| POST | /homework | CORE_EDU_HOMEWORK_CREATE | ✅ P2 |
| GET | /homework/:id | CORE_EDU_HOMEWORK_READ | ✅ P2 |
| GET | /homework/class/:classId | CORE_EDU_HOMEWORK_READ | ✅ P2 |
| POST | /homework/:id/submit | CORE_EDU_HOMEWORK_SUBMIT | ⏳ P3 增强(含 answers |
| POST | /homework/:id/grade | CORE_EDU_HOMEWORK_GRADE | ⏳ P3 新增 |
| POST | /grades | CORE_EDU_GRADE_CREATE | ✅ P2 |
| GET | /grades/:id | CORE_EDU_GRADE_READ | ✅ P2 |
| GET | /grades/student/:studentId | CORE_EDU_GRADE_READ | ✅ P2 |
| GET | /grades/exam/:examId | CORE_EDU_GRADE_READ | ✅ P2 |
| GET | /grades/homework/:homeworkId | CORE_EDU_GRADE_READ | ✅ P2 |
| PUT | /grades/:id | CORE_EDU_GRADE_UPDATE | ⏳ P3 新增 |
| POST | /attendance | CORE_EDU_ATTENDANCE_CREATE | ⏳ P3 新增 |
| GET | /attendance/schedule/:scheduleId | CORE_EDU_ATTENDANCE_READ | ⏳ P3 新增 |
| GET | /attendance/student/:studentId | CORE_EDU_ATTENDANCE_READ | ⏳ P3 新增 |
| POST | /courses | CORE_EDU_COURSE_CREATE | ⏳ P3 新增 |
| POST | /schedules | CORE_EDU_SCHEDULE_CREATE | ⏳ P3 新增 |
| GET | /schedules/teacher/:teacherId | CORE_EDU_SCHEDULE_READ | ⏳ P3 新增 |
| GET | /schedules/class/:classId | CORE_EDU_SCHEDULE_READ | ⏳ P3 新增 |
| GET | /healthz | 无liveness | ✅ P2 |
| GET | /readyz | 无readiness | ✅ P2P3 补 Redis/Kafka 探针) |
| GET | /metrics | 无Prometheus | ✅ P2 |
### 1.3 GraphQL schema如 BFF
不适用。
不适用。core-edu 是业务服务,不提供 GraphQL。GraphQL 由 teacher-bff / student-bff / parent-bff 提供。
### 1.4 Kafka 事件发布(如有)
### 1.4 Kafka 事件发布
| Topic | Event | 消费方 |
| ------------------- | -------------------------------------------------- | -------------- |
| edu.exam.events | ExamEventaction: created/updated/deleted | msg / data-ana |
| edu.homework.events | HomeworkEventaction: assigned/submitted/graded | msg / data-ana |
| edu.grade.events | GradeEventaction: recorded/updated | msg / data-ana |
| edu.class.events | ClassEventaction: transferred | msg / data-ana |
> Topic 命名遵循 [coord-cross-review §3.1](../../coord-cross-review.md#31-core-edu-教学事件-topic-命名双轨p0-最严重) 仲裁:`edu.teaching.<aggregate>.<action>`
> 所有事件 payload 必须含:`schema_version`(默认 `"v1"`+ `event_id`UUID+ `occurred_at`(业务时间戳)+ `metadata: { traceId, userId }`
> 注:当前 `events.proto` 仍用旧 topic 命名 + 缺 schema_version 字段 + 缺 AttendanceEvent message见 [ISSUE-002](../objections/core-edu_issue.md#issue-002-ai08eventsproto-未同步-coord-topic-命名仲裁p0)
### 1.5 错误码前缀
| Topic | Eventaction | 触发时机 | 消费方 | 状态 |
| ----- | --------------- | -------- | ------ | ---- |
| `edu.teaching.exam.created` | ExamEvent.created | CreateExam 事务内 | msg / data-ana / push-gateway | ⏳ P3TOPIC_MAP 改名) |
| `edu.teaching.exam.updated` | ExamEvent.updated | UpdateExam | msg | ⏳ P3 |
| `edu.teaching.exam.deleted` | ExamEvent.deleted | DeleteExam软删除 | data-ana | ⏳ P3 |
| `edu.teaching.exam.published` | ExamEvent.published | PublishExam状态机转换 | msg / data-ana | ⏳ P3 新增 |
| `edu.teaching.exam.submitted` | ExamEvent.submitted | 学生提交答卷 | data-ana / msg | ⏳ P3 新增 |
| `edu.teaching.homework.assigned` | HomeworkEvent.assigned | AssignHomework | msg / data-ana | ⏳ P3 |
| `edu.teaching.homework.submitted` | HomeworkEvent.submitted | 学生提交作业 | data-ana / msg | ⏳ P3 |
| `edu.teaching.homework.graded` | HomeworkEvent.graded | 教师批改完成 | msg / data-ana | ⏳ P3 新增 |
| `edu.teaching.grade.recorded` | GradeEvent.recorded | RecordGrade | data-ana / msg / push-gateway / parent-bff | ⏳ P3 |
| `edu.teaching.grade.updated` | GradeEvent.updated | UpdateGrade | data-ana | ⏳ P3 新增 |
| `edu.teaching.attendance.recorded` | AttendanceEvent.recorded | RecordAttendance | data-ana / msg | ⏳ P3 新增proto 待补 AttendanceEvent |
| `edu.teaching.class.transferred` | ClassEvent.transferred | classes 合并后 | data-ana / msg | ⏳ P3topic 待 ISSUE-004 仲裁) |
`CORE_EDU_`(如 CORE_EDU_CLASS_NOT_FOUND、CORE_EDU_EXAM_CONFLICT
**注意**
- `class.transferred` 的 topic 命名存在跨文档不一致([ISSUE-004](../objections/core-edu_issue.md#issue-004-ai08classtransferred-事件-topic-三处不一致p1)ai08 倾向 `edu.teaching.class.transferred`,待 coord 仲裁。
- 当前 `events.proto` 文件头注释仍用旧命名(`edu.exam.events` 等),需 coord 同步更新。
### 1.5 CDC 数据流(被动同步,无主动接口)
> core-edu 不主动配合 CDC由 data-ana 通过 Debezium 监听 MySQL binlog 自动同步
| MySQL 表 | CDC topic | 消费方 | 用途 |
| -------- | --------- | ------ | ---- |
| core_edu_exams | `edu-cdc.next_edu_cloud.core_edu_exams` | data-ana | ClickHouse 宽表 |
| core_edu_homework | `edu-cdc.next_edu_cloud.core_edu_homework` | data-ana | ClickHouse 宽表 |
| core_edu_grades | `edu-cdc.next_edu_cloud.core_edu_grades` | data-ana | ClickHouse 宽表 |
| core_edu_attendance | `edu-cdc.next_edu_cloud.core_edu_attendance` | data-ana | ClickHouse 宽表 |
### 1.6 错误码前缀
`CORE_EDU_*`[coord-cross-review §5.5](../../coord-cross-review.md#55-p1-问题core-edu-子模块前缀) 仲裁:子域统一 `CORE_EDU_*`,不再细分 `EXAMS_`/`HOMEWORK_`/`GRADES_`
完整错误码清单见 [02-architecture-design.md §6.2](../../../services/core-edu/docs/02-architecture-design.md#62-错误码清单)。
### 1.7 响应信封
所有 HTTP/gRPC 响应遵循 ActionState 信封([004 §11.5](../../004_architecture_impact_map.md)
```typescript
{
success: boolean,
data?: T,
error?: { code: string, message: string, details?: unknown, traceId?: string }
}
```
---
@@ -61,18 +153,30 @@
### 2.1 gRPC 调用(同步)
无直接 gRPC 调用上游。core-edu 通过 Kafka 事件接收 iam 用户变更,不主动调 iam。
| 调用方 | 目标服务 | RPC | 用途 | 阶段 |
| ------ | -------- | --- | ---- | ---- |
| core-edu | content | ContentService.GetKnowledgePoints | 排课关联知识点lessons.knowledge_point_ids 校验) | P4content 就绪后) |
| core-edu | temporal | Workflow.startexamPublishWorkflow | 考试发布编排工作流 | P3Temporal 部署后) |
> core-edu **不主动调 iam gRPC**,通过 Kafka 事件接收 iam 用户变更(见 §2.2)。
> P3 不调 content题库 question_id 仅作外键引用,不校验存在性)。
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| ------------------- | --------------------------------------------------------- | ---------- | -------------------------------------------------------------------- |
| edu.iam.user.events | UserEventaction: created/updated/deleted/role_changed | iam (ai06) | iam 就绪前不订阅使用本地内置用户数据teacher_id/student_id 固定) |
| edu.iam.role.events | RoleEventaction: created/updated | iam (ai06) | iam 就绪前忽略,权限校验在 core-edu 内部 mock |
> Topic 命名遵循 [004 §7.2](../../004_architecture_impact_map.md#72-事件-topic-分类) 事件分类
| Topic | Event | 发布方 | 消费动作 | 幂等策略 | 阶段 |
| ----- | ----- | ------ | -------- | -------- | ---- |
| `edu.identity.user.created` | UserEvent.created | iam (ai06) | 初始化教师默认班级关联(写 core_edu_teacher_associations | 唯一索引 (teacher_id, class_id, subject_id) | P3 |
| `edu.identity.user.updated` | UserEvent.updated | iam (ai06) | 更新教师关联(角色变更时) | 基于用户事件序列号去重 | P3 |
| `edu.identity.user.deleted` | UserEvent.deleted | iam (ai06) | 软删除教师关联(保留历史成绩归属) | 基于用户 id 去重 | P3 |
| `edu.insight.mastery.updated` | MasteryEvent.updated | data-ana (ai11) | 接收学生掌握度,用于推荐个性化练习 | 基于 mastery_score_id 去重 | P4P3 可选) |
> 注:当前 `events.proto` 无 UserEvent message 定义IAM 事件可能在另一个 proto 文件ai08 在 P3 实施时确认 IAM 事件 proto 定义位置。
### 2.3 HTTP 调用(如有)
无。
无。core-edu 不主动发起 HTTP 调用。
---
@@ -80,39 +184,91 @@
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— 用于用户身份一致性校验可选core-edu 可先独立运行)
- [ ] edu.iam.user.events topic 有事件发布ai06—— 用于同步用户缓存
| 依赖项 | 提供方 | 就绪信号 | 状态 |
| ------ | ------ | -------- | ---- |
| coord 仲裁 ISSUE-001 ~ ISSUE-006 | coord | coord.md 仲裁章节 | ⏳ 待仲裁 |
| core_edu.proto 补全 | coord 或 ai08 | 5 Service / 27 RPC 定义 | ⏳(见 [ISSUE-001](../objections/core-edu_issue.md) |
| events.proto 同步 | coord | 含 AttendanceEvent + schema_version + `edu.teaching.*` 注释 | ⏳(见 [ISSUE-002](../objections/core-edu_issue.md) |
| buf.gen.yaml gRPC 插件 | coord | `buf generate` 产出 TS gRPC 代码 | ⏳ |
| iam gRPC 50052 | ai06 | HealthService.Check = SERVING | ⏳(阻塞 P3.9 消费 IAM 事件core-edu 可先独立运行) |
| Redis 部署 | infra | redis:6379 可连接 | ⏳(阻塞 P3.7 分布式锁 + /readyz 探针) |
| Temporal server 部署 | infra | temporal:7233 可连接 | ⏳(阻塞 P3.10 工作流试点,可降级为纯事件驱动) |
| content gRPC 50054P4 | ai09 | HealthService.Check = SERVING | ⏳(阻塞 P4.2 知识点关联) |
| data-ana gRPC 50055P4 | ai11 | HealthService.Check = SERVING | ⏳(阻塞 P4.1 mastery 消费) |
| msg gRPC 50056P5 | ai10 | HealthService.Check = SERVING | ⏳(阻塞 P5.1 事件联调) |
### 3.2 我的就绪标志(供下游消费)
- [ ] core-edu gRPC 50053 启用HealthService.Check 返回 SERVING
- [ ] ClassService 4 RPC 可调用GetClass/GetClassesByTeacher/BatchGetClasses/ListStudentsByClass
- [ ] ExamService 5 RPC 可调用
- [ ] HomeworkService 4 RPC 可调用
- [ ] GradeService 5 RPC 可调用
- [ ] AttendanceService 4 RPC 可调用
- [ ] edu.exam.events / edu.homework.events / edu.grade.events / edu.class.events topic 可发布
| 阶段 | 就绪信号 | 消费方 | 状态 |
| ---- | -------- | ------ | ---- |
| P2已就绪 | HTTP 3004 可访问 + /healthz + /readyzDB 探针)+ REST CRUDexams/homework/grades+ Outbox | teacher-bffREST 调用) | ✅ |
| P3核心 | gRPC 50053 + 27 RPC + HealthService SERVING | teacher-bff / student-bff / parent-bff / ai | ⏳ |
| P3 子信号 1 | ClassService 4 RPC 可调用 | teacher-bff班级列表 | ⏳ |
| P3 子信号 2 | ExamService 8 RPC 可调用(含 PublishExam/SubmitExam/GradeExam | teacher-bff / student-bff / ai | ⏳ |
| P3 子信号 3 | HomeworkService 5 RPC 可调用(含 GradeHomework | teacher-bff / student-bff | ⏳ |
| P3 子信号 4 | GradeService 6 RPC 可调用(含 UpdateGrade | teacher-bff / student-bff / parent-bff | ⏳ |
| P3 子信号 5 | AttendanceService 4 RPC 可调用 | parent-bff | ⏳ |
| P3 子信号 6 | `edu.teaching.*` topic 可发布(含 attendance.recorded | msg / data-ana / push-gateway | ⏳ |
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游消费)
在 core-edu 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / content / msg / data-ana提供以下 mock
- **gRPC mock**使用 grpc-mock 拦截 50053 端口
- ClassService.GetClassesByTeacher 返回固定 3 个 ClassInfo
- ClassService.ListStudentsByClass 返回固定 30 个 StudentInfo
- ExamService.ListExamsByClass 返回固定 2 个 Exam
- HomeworkService.ListHomeworkByClass 返回固定 3 个 Homework
- GradeService.ListGradesByStudent 返回固定 5 个 Grade
- AttendanceService.ListAttendanceByStudent 返回固定 10 条 Attendance
- **Kafka mock**core-edu 就绪前不发布真实事件,下游 data-ana/msg 使用本地 stub 事件
**gRPC mock**使用 grpc-mock 拦截 50053 端口
### 4.2 我消费的 mock
| Service | RPC | mock 返回 |
| ------- | --- | --------- |
| ClassService | GetClassesByTeacher | 固定 3 个 ClassInfo |
| ClassService | ListStudentsByClass | 固定 30 个 StudentInfo |
| ClassService | BatchGetClasses | 按 id 列表返回对应 ClassInfo |
| ExamService | ListExamsByClass | 固定 2 个 Exam |
| ExamService | GetExam | 固定 1 个 Exam含题目列表 |
| ExamService | CreateExam | 返回固定 examId |
| HomeworkService | ListHomeworkByClass | 固定 3 个 Homework |
| HomeworkService | SubmitHomework | 返回固定 submissionId |
| GradeService | ListGradesByStudent | 固定 5 个 Grade |
| GradeService | ListGradesByExam | 固定 30 个 Grade按班级学生数 |
| AttendanceService | ListAttendanceByStudent | 固定 10 条 Attendance |
| AttendanceService | RecordAttendance | 返回固定 attendanceId |
| HealthService | Check | 返回 SERVING |
**Kafka mock**core-edu 就绪前不发布真实事件):
- 下游 data-ana / msg 使用本地 stub 事件(固定 JSON payload含 schema_version/event_id/occurred_at/metadata
- stub 事件 JSON 文件位置:`services/core-edu/test/stubs/events/`ai08 P3.12 测试阶段产出)
### 4.2 我消费的 mock在真实上游就绪前
在真实 iam 就绪前core-edu 使用以下 mock
- 用户数据:内置固定 teacher_id / student_id不订阅 edu.iam.user.events
- 权限校验core-edu 内部不校验权限(由 Gateway/BFF 层负责),仅记录 created_by 字段
| 依赖 | mock 方式 | 切换真实时机 |
| ---- | --------- | ------------ |
| 用户数据 | 内置固定 teacher_id / student_id不订阅 `edu.identity.user.*` | iam gRPC 50052 就绪 + IAM 事件 topic 有事件发布 |
| 权限校验 | core-edu 内部不校验权限(由 Gateway/BFF 层负责),仅记录 created_by 字段 | iam 就绪后仍由 Gateway/BFF 负责core-edu 仅做 DataScope 下推 |
| content 知识点 | 不调用 ContentService.GetKnowledgePointslessons.knowledge_point_ids 仅存储不校验 | content gRPC 50054 就绪P4 |
| Temporal 工作流 | 考试发布降级为同步事件驱动(无工作流) | Temporal server 部署就绪 |
| Redis 分布式锁 | 降级为 DB SELECT FOR UPDATE性能下降但功能可用 | Redis 部署就绪 |
---
## §5 跨模块契约对齐状态ai08 核查)
> 核查日期2026-07-10
> 详细核查记录见 [objections/core-edu_issue.md §0](../objections/core-edu_issue.md#0-已有仲裁核查记录ai08-接管后核查)
| 待确认项 | coord 仲裁结论 | 核查状态 |
| -------- | -------------- | -------- |
| iam `user.created` 等事件 topic | `edu.identity.user.created` / `.updated` / `.deleted`004 §7.2 | ✅ 已仲裁core-edu P3 实现消费端 |
| core-edu 端口 3004 + gRPC 50053 | 不冲突,已纳入 coord 全局端口矩阵 | ✅ 已仲裁 |
| Kafka topic 命名 | 统一为 `edu.teaching.<aggregate>.<action>`coord §3.1 | ✅ 已仲裁core-edu P3 修 TOPIC_MAP |
| proto 包名 | `next_edu_cloud.core_edu.v1`coord §2.1 | ✅ 已仲裁 |
| 错误码前缀 | `CORE_EDU_*` 统一coord §5.5 | ✅ 已仲裁 |
| core_edu.proto 补 AttendanceService | coord 整改 #14 | ❌ 仲裁声称已补全实际未补ISSUE-001 |
| events.proto 同步 `edu.teaching.*` 命名 | coord §3.1 | ❌ 未同步ISSUE-002 |
| 考试/作业状态命名 | 未仲裁 | ❌ 跨模块不一致ISSUE-003 |
| class.transferred topic | 未仲裁 | ❌ 三处不一致ISSUE-004 |
| RPC 数量统计口径 | 未仲裁 | ❌ 三处不一致ISSUE-005 |
| 7 项设计决策 | 未仲裁 | ❌ 待提请ISSUE-006 |

View File

@@ -1,13 +1,15 @@
# data-ana 对接契约
> 负责人ai11
> 关联:[matrix.md](./matrix.md)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](../matrix.md)、[coord-cross-review.md](../../coord-cross-review.md)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)
> 对齐文档:[01-understanding.md](../../../services/data-ana/docs/01-understanding.md)、[02-architecture-design.md](../../../services/data-ana/docs/02-architecture-design.md)、[objections/data-ana_issue.md](../objections/data-ana_issue.md)、[worklines/data-ana_workline.md](../worklines/data-ana_workline.md)
> 修订v22026-07-10 by ai11—— 修正 topic 命名 / gRPC 调用声明 / HTTP 端点声明 / 引用断裂,对齐 01/02 v2.1
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 |
| ---------------- | ---------------------- | ----------------------------- | ------------------------- | ----- |
@@ -18,31 +20,71 @@
| AnalyticsService | GetStudentDashboard | GetStudentDashboardRequest | StudentDashboard | 50055 |
| AnalyticsService | GetParentDashboard | GetParentDashboardRequest | ParentDashboard | 50055 |
| AnalyticsService | GetAdminDashboard | GetAdminDashboardRequest | AdminDashboard | 50055 |
| AnalyticsService | GetWarningList | GetWarningListRequest | WarningListResponse | 50055 |
| AnalyticsService | GetWarnings | GetWarningsRequest | WarningList | 50055 |
| AnalyticsService | TriggerWarning | TriggerWarningRequest | TriggerWarningResponse | 50055 |
| AnalyticsService | GetMasteryDistribution | GetMasteryDistributionRequest | MasteryDistribution | 50055 |
| AnalyticsService | GetStudentMastery | GetStudentMasteryRequest | StudentMastery | 50055 |
| AnalyticsService | SubscribeMasteryUpdate | SubscribeMasteryUpdateRequest | stream MasteryUpdateEvent | 50055 |
### 1.2 HTTP 端点(如有)
> **proto 状态**analytics.proto 当前仅 3 RPCGetClassPerformance / GetStudentWeakness / GetLearningTrend扩展至 12 RPC 待 coord 补全或 ai11 在 P4 阶段自行补全(见 [ISSUE-003](../objections/data-ana_issue.md))。完整 message 定义见 [02-architecture-design.md §4.2](../../../services/data-ana/docs/02-architecture-design.md)。
> **gRPC 启用阶段**P4 启用coord-cross-review.md §2.3 裁决P2-P3 仅 HTTP。
> **响应信封**:所有 RPC 返回 ActionState[T]coord-cross-review.md §5.3 P0 整改)。
无对外 HTTP 端点,仅 gRPC含 1 个 Server Streaming RPC
### 1.2 HTTP 端点
data-ana 保留 HTTP :3006 端点作 Gateway 直连降级gRPC 不可用时 BFF 可走 HTTP。共 14 端点3 基础 + 11 业务):
| method | path | 权限 | 响应 |
| ------ | -------------------------------------------- | ----------------------------- | ----------------------------------- |
| GET | `/healthz` | — | `{status, service}` |
| GET | `/readyz` | — | `{status, ready, degraded, clickhouse, cdc_consumer, redis, iam_grpc}` |
| GET | `/metrics` | — | Prometheus 格式 |
| GET | `/analytics/class/{class_id}/performance` | `ANALYTICS_CLASS_READ` | `ActionState<ClassPerformanceData>` |
| GET | `/analytics/student/{student_id}/weakness` | `ANALYTICS_STUDENT_READ` | `ActionState<StudentWeaknessData>` |
| GET | `/analytics/student/{student_id}/errorbook` | `ANALYTICS_STUDENT_READ` | `ActionState<StudentErrorBookData>` |
| GET | `/analytics/student/{student_id}/trend` | `ANALYTICS_STUDENT_READ` | `ActionState<LearningTrendData>` |
| GET | `/analytics/student/{student_id}/attendance` | `ANALYTICS_STUDENT_READ` | `ActionState<AttendanceData>` |
| GET | `/analytics/dashboard/teacher/{user_id}` | `ANALYTICS_TEACHER_DASHBOARD` | `ActionState<TeacherDashboardData>` |
| GET | `/analytics/dashboard/student/{user_id}` | `ANALYTICS_STUDENT_DASHBOARD` | `ActionState<StudentDashboardData>` |
| GET | `/analytics/dashboard/parent/{user_id}` | `ANALYTICS_PARENT_DASHBOARD` | `ActionState<ParentDashboardData>` |
| GET | `/analytics/dashboard/admin/{user_id}` | `ANALYTICS_ADMIN_DASHBOARD` | `ActionState<AdminDashboardData>` |
| GET | `/analytics/warnings` | `ANALYTICS_WARNING_READ` | `ActionState<WarningListData>` |
| GET | `/analytics/mastery/distribution` | `ANALYTICS_CLASS_READ` | `ActionState<MasteryDistributionData>` |
> 完整端点设计见 [02-architecture-design.md §4.1](../../../services/data-ana/docs/02-architecture-design.md)。
### 1.3 GraphQL schema如 BFF
不适用。
不适用。data-ana 是业务服务,不提供 GraphQL。BFF 层teacher-bff / student-bff / parent-bff聚合 data-ana gRPC 后对外暴露 GraphQL。
### 1.4 Kafka 事件发布(如有)
### 1.4 Kafka 事件发布
| Topic | Event | 消费方 |
| --------------------------- | --------------------------------------------------------- | -------------- |
| edu.data_ana.mastery.events | MasteryEventaction: mastery.updated/warning.triggered | core-edu / msg |
| Topic | Event | 消费方 | Outbox | 说明 |
| -------------------------------- | --------------- | ------------------------------- | ------ | ---- |
| `edu.insight.mastery.updated` | MasteryUpdated | core-edu推荐个性化练习/ msg | ❌ 豁免 | 掌握度计算完成触发 |
| `edu.insight.warning.triggered` | WarningTriggered | msg推送通知/ core-edu标记关注 | ❌ 豁免 | 预警阈值触发 |
> MasteryEvent 豁免 Outbox 模式(派生数据事件,见 004 §12.2 + §15.3 #6
> **Outbox 豁免**派生数据事件(非业务事务写),豁免 Outbox 约束([coord-cross-review.md §3.3](../../coord-cross-review.md) 已仲裁)。直接用 aiokafka AIOKafkaProducer 发布,`idempotent=true` + `transactional_id="data-ana-producer"`
>
> **topic 命名说明**:按 004 §7.2 命名规范 `edu.<domain>.<aggregate>.<action>`data-ana 属于 D6 智能洞察领域domain=insight故 `edu.insight.mastery.updated` 符合规范。matrix.md §4 中 `edu.analytics.mastery` 命名不符合规范(缺 action 层级domain 用了服务名而非领域名),已提请 coord 统一(见 [ISSUE-005](../objections/data-ana_issue.md))。
### 1.5 错误码前缀
`DATA_ANA_`(如 DATA_ANA_DASHBOARD_UNAVAILABLE、DATA_ANA_MASTERY_NOT_COMPUTED
`DATA_ANA_`(如 DATA_ANA_DASHBOARD_UNAVAILABLE、DATA_ANA_MASTERY_NOT_COMPUTED、DATA_ANA_CLICKHOUSE_UNAVAILABLE
> 来源:[matrix.md §6](../matrix.md) 错误码前缀矩阵。
### 1.6 ClickHouse 宽表(供 coord 统一管理 DDL
| 宽表名 | 用途 | 引擎 |
| ------------------------- | -------------------- | ----------------------------- |
| student_dashboard_view | 学生学情宽表 | ReplacingMergeTree(last_updated) |
| student_errors | 学生错题本 | ReplacingMergeTree(last_error_time) |
| mastery_snapshot | 知识点掌握度历史快照 | MergeTree |
| attendance_logs | 学生考勤记录 | ReplacingMergeTree(occurred_at) |
| ai_usage_log | AI 用量计费记录 | ReplacingMergeTree(occurred_at) |
> DDL 由 coord 统一管理在 `infra/clickhouse/ddl/`(待 coord 建立data-ana 提供内容。完整 DDL 见 [02-architecture-design.md §3](../../../services/data-ana/docs/02-architecture-design.md)。
---
@@ -50,29 +92,43 @@
### 2.1 gRPC 调用(同步)
无主动 gRPC 调用上游。data-ana 通过 CDC + Kafka 事件接收数据,计算后发布 MasteryEvent。
| 调用方 | 被调用方 | RPC | 用途 | 端口 | 阶段 | 状态 |
| -------- | -------- | ------------------------- | ---------------------------- | ----- | ---- | ---- |
| data-ana | iam | GetEffectiveDataScope | DataScope 6 级过滤解析 | 50052 | P4 | ⚠️ iam.proto 当前未实现ISSUE-001 |
> **降级兜底**iam GetEffectiveDataScope 未就绪时data-ana 按 role 映射默认 DataScope教师=CLASS学生=SELF管理员=SCHOOL标注 `details.degraded: true`。结果 Redis 缓存 5minkey: `data_ana:datascope:{user_id}`)。
>
> **裁决依据**coord-cross-review.md §2.2 #3 已仲裁 iam P4 补全此 RPC。
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| ---------------------------------- | ------------------- | --------------- | ------------------------------------------------- |
| edu.exam.events | ExamEvent | core-edu (ai08) | core-edu 就绪前使用 CDC 模拟数据 + 本地 stub 事件 |
| edu.homework.events | HomeworkEvent | core-edu (ai08) | 同上 |
| edu.grade.events | GradeEvent | core-edu (ai08) | 同上 |
| edu.class.events | ClassEvent | core-edu (ai08) | 同上 |
| edu.content.knowledge_point.events | KnowledgePointEvent | content (ai09) | content 就绪前使用内置知识点维度表 |
| edu.content.question.events | QuestionEvent | content (ai09) | content 就绪前忽略 |
| edu.ai.usage.events | AIUsageEvent | ai (ai12) | ai 就绪前忽略AI 用量统计为空 |
| Topic | Event | 发布方 | mock 策略 |
| -------------------------------------- | ------------------- | --------------- | ------------------------------------------------- |
| `edu.teaching.exam.published` | ExamEvent | core-edu (ai08) | core-edu 就绪前使用 CDC 模拟数据 + 本地 stub 事件 |
| `edu.teaching.homework.assigned` | HomeworkEvent | core-edu (ai08) | 同上 |
| `edu.teaching.grade.recorded` | GradeEvent | core-edu (ai08) | 同上 |
| `edu.teaching.class.transferred` | ClassEvent | core-edu (ai08) | 同上 |
| `edu.content.kp.events` | KnowledgePointEvent | content (ai09) | content 就绪前使用内置知识点维度表 |
| `edu.content.question.events` | QuestionEvent | content (ai09) | content 就绪前忽略 |
| `edu.insight.ai.usage` | AIUsageEvent | ai (ai12) | ai 就绪前忽略AI 用量统计为空P5 |
> **topic 命名对齐**core-edu 教学事件 topic 按 coord-cross-review.md §3.1 裁决统一为 `edu.teaching.<aggregate>.<action>` 风格。core-edu 代码实际发布 `edu.exam.events` 等,待 core-edu 整改后切换。
>
> **AIUsageEvent 缺失**events.proto 当前缺 AIUsageEvent messageISSUE-002P5 前需 coord 补全。
### 2.3 HTTP 调用(如有)
无。
无。data-ana 不通过 HTTP 调用上游服务。
### 2.4 CDC 数据源(补充)
### 2.4 CDC 数据源
| 数据源 | 用途 | mock 策略 |
| ----------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------- |
| core-edu MySQLexams/homework/grades/attendance 表) | Debezium CDC → Kafka 同步读模型 | core-edu 就绪前使用 ClickHouse 内置模拟数据集30 学生 × 5 考试 × 10 作业) |
| content MySQLknowledge_points 表) | Debezium CDC → 知识点元数据同步 | content 就绪前使用内置固定知识点表(数学 50 个知识点) |
| iam MySQLusers 表) | Debezium CDC → 用户 dataScope 同步 | iam 就绪前使用硬编码 DataScope 降级 |
> **CDC topic 命名**`edu-cdc.next_edu_cloud.<table>`coord-cross-review.md §3.2 裁决补登 CDC topic 命名规范段,待 coord 在 004 §7.2 落实)。
---
@@ -81,17 +137,23 @@
### 3.1 我依赖的上游就绪标志
- [ ] core-edu gRPC 50053 启用ai08—— 业务事件 + CDC 数据源
- [ ] edu.exam.events / edu.homework.events / edu.grade.events / edu.class.events topic 有事件发布ai08
- [ ] content gRPC 50054 启用ai09—— 知识点维度
- [ ] edu.content.knowledge_point.events topic 有事件发布ai09
- [ ] ai gRPC 50057 启用ai12—— AI 用量统计(可选,仪表盘补全
- [ ] core-edu MySQL Debezium CDC 配置ai08 + SRE—— CDC 通道前提
- [ ] `edu.teaching.exam.published` / `edu.teaching.homework.assigned` / `edu.teaching.grade.recorded` / `edu.teaching.class.transferred` topic 有事件发布ai08
- [ ] content gRPC 50054 启用ai09—— 知识点维度 CDC
- [ ] `edu.content.kp.events` topic 有事件发布ai09
- [ ] iam.proto 补全 GetEffectiveDataScope RPCai06/coordISSUE-001—— P4 阻塞项
- [ ] analytics.proto 扩展至 12 RPCcoord/ai11ISSUE-003—— gRPC stub 生成前提
- [ ] events.proto 补全 AIUsageEvent messagecoordISSUE-002—— P5 前补全
- [ ] ai gRPC 50057 启用ai12—— AI 用量统计P5可选
### 3.2 我的就绪标志(供下游消费)
- [ ] data-ana gRPC 50055 启用HealthService.Check 返回 SERVING
- [ ] AnalyticsService 12 RPC 可调用(含 4 端 Dashboard + Server Streaming SubscribeMasteryUpdate
- [ ] GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard 返回结构化数据
- [ ] edu.data_ana.mastery.events topic 可发布mastery.updated / warning.triggered
- [ ] **P4 就绪**data-ana gRPC 50055 启用HealthService.Check 返回 SERVING
- [ ] **P4 就绪**AnalyticsService 12 RPC 可调用(含 4 端 Dashboard + Warning + Mastery + Server Streaming SubscribeMasteryUpdate
- [ ] **P4 就绪**GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard 返回结构化数据
- [ ] **P4 就绪**`edu.insight.mastery.updated` topic 可发布mastery.updated / warning.triggered
- [ ] **P5 就绪**SubscribeMasteryUpdate server-streaming RPC 可订阅
- [ ] **P6 就绪**CDC 多实例水平扩展 + ExamCache Redis 化完成
---
@@ -106,16 +168,17 @@
- GetStudentDashboard 返回固定仪表盘avg_score=85.0, class_rank=5, weak_points 3 个)
- GetParentDashboard 返回固定仪表盘child_avg_score=85.0, child_class_rank=5
- GetAdminDashboard 返回固定仪表盘total_teachers=50, total_students=1200, school_avg_score=80.0
- GetWarningList 返回固定 5 条预警severity: warning/critical
- GetWarnings 返回固定 5 条预警severity: warning/critical
- GetMasteryDistribution 返回固定分布mastered=20, progressing=7, weak=3
- SubscribeMasteryUpdate 返回固定流(每 5 秒推 1 个 MasteryUpdateEvent
- **Kafka mock**data-ana 就绪前不发布真实 MasteryEventmsg 使用本地 stub 预警
- **Kafka mock**data-ana 就绪前不发布真实 MasteryUpdated / WarningTriggeredmsg 使用本地 stub 预警
### 4.2 我消费的 mock
在真实上游就绪前data-ana 使用以下 mock
- 业务数据ClickHouse 内置模拟数据集30 学生 × 5 考试 × 10 作业 × 30 天出勤),不依赖 core-edu CDC
- 知识点维度:内置固定知识点表(数学 50 个知识点),不依赖 content 事件
- AI 用量AIUsageEvent 为空,仪表盘 AI 用量区块显示"暂无数据"
- CDC 通道core-edu 就绪前 Debezium 不启动,使用 ClickHouse 批量导入模拟数据
- **业务数据**ClickHouse 内置模拟数据集30 学生 × 5 考试 × 10 作业 × 30 天出勤),不依赖 core-edu CDC
- **知识点维度**:内置固定知识点表(数学 50 个知识点),不依赖 content 事件
- **AI 用量**AIUsageEvent 为空,仪表盘 AI 用量区块显示"暂无数据"
- **CDC 通道**core-edu 就绪前 Debezium 不启动,使用 ClickHouse 批量导入模拟数据
- **DataScope**iam GetEffectiveDataScope 未就绪前,按 role 映射默认 DataScope 降级(教师=CLASS学生=SELF管理员=SCHOOL标注 `details.degraded: true`

View File

@@ -2,35 +2,57 @@
> 负责人ai06
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 裁决依据:[coord-final-decisions](../../coord-final-decisions.md) I1-I8、[president-final-rulings](../../president-final-rulings.md) §2.15/§2.16/§5.5
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
> **双入口策略**president §2.16REST 供 gateway 透传 + admin-portal 直连gRPC 供 BFF 聚合调用。同一 Application Service 同时被 REST Controller + gRPC Controller 调用业务逻辑不重复。gateway 保持 HTTP 透传(不改为 gRPC 客户端)。
| Service | RPC | 请求 | 响应 | 端口 |
| ---------- | ----------------------- | ------------------------------ | ---------------------------- | ----- |
| IamService | Register | RegisterRequest | AuthResponse | 50052 |
| IamService | Login | LoginRequest | AuthResponse | 50052 |
| IamService | RefreshToken | RefreshTokenRequest | TokenPair | 50052 |
| IamService | Logout | LogoutRequest | LogoutResponse | 50052 |
| IamService | GetUserInfo | GetUserInfoRequest | UserInfo | 50052 |
| IamService | BatchGetUsers | BatchGetUsersRequest | BatchGetUsersResponse | 50052 |
| IamService | GetEffectivePermissions | GetEffectivePermissionsRequest | EffectivePermissionsResponse | 50052 |
| IamService | GetEffectiveAccess | GetEffectiveAccessRequest | EffectiveAccessResponse | 50052 |
| IamService | GetEffectiveDataScope | GetEffectiveDataScopeRequest | DataScopeResponse | 50052 |
| IamService | GetViewports | GetViewportsRequest | ViewportsResponse | 50052 |
| IamService | GetPublicKey | GetPublicKeyRequest | PublicKeyResponse | 50052 |
| IamService | GetChildrenByParent | GetChildrenByParentRequest | ChildrenResponse | 50052 |
### 1.1 gRPC 接口(供 BFF 聚合调用)
### 1.2 HTTP 端点(如有)
> 端口 50052P2 即启用I1 裁决)。⚠️ 当前 iam.proto 仅 4 RPC待 coord 补全至 12 RPCISSUE-005
无对外 HTTP 端点,仅 gRPC。
| Service | RPC | 请求 | 响应 | 端口 | 消费方 |
| ---------- | ----------------------- | ------------------------------ | ---------------------------- | ----- | ---------------------------------------- |
| IamService | Register | RegisterRequest | AuthResponse | 50052 | api-gatewayREST 透传)/ admin-portal |
| IamService | Login | LoginRequest | AuthResponse | 50052 | api-gatewayREST 透传)/ admin-portal |
| IamService | RefreshToken | RefreshTokenRequest | TokenPair | 50052 | api-gatewayREST 透传) |
| IamService | Logout | LogoutRequest | LogoutResponse | 50052 | api-gatewayREST 透传) |
| IamService | GetUserInfo | GetUserInfoRequest | UserInfo | 50052 | teacher-bff / student-bff / parent-bff |
| IamService | BatchGetUsers | BatchGetUsersRequest | BatchGetUsersResponse | 50052 | teacher-bff / admin-portal |
| IamService | GetEffectivePermissions | GetEffectivePermissionsRequest | EffectivePermissionsResponse | 50052 | teacher-bff / student-bff / parent-bff |
| IamService | GetEffectiveAccess | GetEffectiveAccessRequest | EffectiveAccessResponse | 50052 | teacher-bff / student-bff / parent-bff |
| IamService | GetEffectiveDataScope | GetEffectiveDataScopeRequest | DataScopeResponse | 50052 | teacher-bff / data-ana |
| IamService | GetViewports | GetViewportsRequest | ViewportsResponse | 50052 | teacher-bff / student-bff / parent-bff |
| IamService | GetPublicKey | GetPublicKeyRequest | PublicKeyResponse | 50052 | api-gatewayJWKS 验签) |
| IamService | GetChildrenByParent | GetChildrenByParentRequest | ChildrenResponse | 50052 | parent-bff |
### 1.2 HTTP 端点(供 gateway 透传 + admin-portal 直连)
> REST 与 gRPC 双入口并存president §2.16。REST 路径统一加 `/v1` 前缀I7 裁决。gateway 路由 `/iam/v1/*` → iam 服务 `/v1/iam/*`(透传不改路径)。
| Method | Path | 权限 | 说明 | 消费方 |
| ------ | --------------------------------- | ----------------- | -------------------------------------- | -------------------- |
| POST | `/iam/v1/register` | 公开 | 注册 + 自动分配 teacher 角色 | api-gateway / admin |
| POST | `/iam/v1/login` | 公开 | 登录,返回 accessToken + refreshToken | api-gateway |
| POST | `/iam/v1/refresh` | 公开 | 刷新令牌(轮换 + 旧 token 黑名单) | api-gateway |
| POST | `/iam/v1/logout` | `IAM_USER_READ` | 登出refresh token 加黑名单) | api-gateway |
| GET | `/iam/v1/me` | `IAM_USER_READ` | 当前用户信息 | api-gateway / admin |
| GET | `/iam/v1/viewports` | `IAM_USER_READ` | 当前用户视口L1 导航) | api-gateway |
| GET | `/iam/v1/permissions/effective` | `IAM_USER_READ` | 当前用户有效权限I8 统一路径) | api-gateway |
| GET | `/iam/v1/.well-known/jwks.json` | 公开 | RS256 公钥 JWK SetGateway 拉取验签) | api-gateway |
| GET | `/iam/v1/children` | `IAM_USER_READ` | 当前家长的孩子列表I6 裁决) | api-gateway / parent |
| GET | `/iam/v1/roles` | `IAM_ROLE_MANAGE` | 角色列表(管理端) | admin-portal |
| GET | `/iam/v1/permissions` | `IAM_ROLE_MANAGE` | 权限点列表(管理端) | admin-portal |
| GET | `/healthz` | 无 | liveness | k8s / 监控 |
| GET | `/readyz` | 无 | readiness5 依赖检查) | k8s / 监控 |
| GET | `/metrics` | 无 | Prometheus 指标 | Prometheus |
### 1.3 GraphQL schema如 BFF
不适用。
不适用。iam 是业务服务,不暴露 GraphQLGraphQL 由 BFF 层提供)。
### 1.4 Kafka 事件发布(如有)
@@ -66,16 +88,23 @@
### 3.1 我依赖的上游就绪标志
无上游依赖。
iam 是身份根服务无业务上游依赖。但依赖以下基础设施契约coord 提供):
| 依赖项 | 提供方 | 就绪标志 | 状态 |
| ------ | ------ | -------- | ---- |
| iam.proto 补全至 12 RPC | coord | proto 含 12 RPC + 全部 message | ❌ 仅 4 RPCISSUE-005 |
| events.proto 补全 UserEvent/RoleEvent/AuditEvent | coord | proto 含 3 个 message | ❌ 缺失ISSUE-002 |
| shared-ts Outbox 工具包 | coord | outbox.service.ts 可导入 | ✅ 已就绪 |
### 3.2 我的就绪标志(供下游消费)
- [ ] iam gRPC 50052 启用HealthService.Check 返回 SERVING
- [ ] IamService.Register/Login/RefreshToken/Logout 可调用(返回 AuthResponse/TokenPair
- [ ] IamService 12 RPC 全部可调用(Register/Login/RefreshToken/Logout/GetUserInfo/BatchGetUsers/GetEffectivePermissions/GetEffectiveAccess/GetEffectiveDataScope/GetViewports/GetPublicKey/GetChildrenByParent
- [ ] IamService.GetPublicKey 可用(返回 RS256 PEM 公钥,供 api-gateway 验签)
- [ ] IamService.GetChildrenByParent 可用(供 parent-bff 查孩子列表)
- [ ] edu.iam.user.events / edu.iam.role.events / edu.iam.audit.created topic 可发布
- [ ] JWT RS256 签发链路打通access_token + refresh_token
- [ ] JWT RS256 签发链路打通access_token 15min + refresh_token 7day 轮换
- [ ] /iam/v1/* REST 端点可用(供 gateway 透传 + admin-portal 直连,双入口策略)
---
@@ -83,13 +112,15 @@
### 4.1 我提供的 mock
在 iam 真实服务就绪前为下游api-gateway / 各 BFF提供以下 mock
在 iam 真实服务就绪前为下游api-gateway / 各 BFF / admin-portal)提供以下 mock(双入口)
- **gRPC mock**:使用 grpc-mock 拦截 50052 端口Register/Login 返回固定 AuthResponseuser.id="mock-user-001", tokens.access_token="mock-access-token"
- **gRPC mock**(供 BFF:使用 grpc-mock 拦截 50052 端口Register/Login 返回固定 AuthResponseuser.id="mock-user-001", tokens.access_token="mock-access-token"
- **REST mock**(供 gateway / admin-portalMSW 或 express mock 拦截 /iam/v1/* 路径,返回与 gRPC mock 一致的结构
- **GetPublicKey mock**:返回固定 RS256 公钥 PEM与 mock 私钥配对),供 api-gateway 验签 mock JWT
- **GetChildrenByParent mock**:返回固定 ChildInfo 列表2 个孩子)
- **JWKS mock**GET /iam/v1/.well-known/jwks.json 返回固定 JWK Set
- **Kafka mock**iam 服务就绪前不发布真实事件,下游订阅方使用本地 stub
### 4.2 我消费的 mock
不适用(上游依赖)。
不适用(iam 是身份根服务,无业务上游依赖)。

View File

@@ -1,47 +1,95 @@
# msg 对接契约
> 负责人ai10
> 关联:[matrix.md](./matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](../matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[02-architecture-design.md](../../../services/msg/docs/02-architecture-design.md)
> 仲裁依赖ISSUE-008topic 命名、ISSUE-009RPC 数量、ISSUE-013events.proto 补齐)
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 |
| ----------------------------- | ---------------------- | ----------------------------- | --------------------------- | ----- |
| NotificationService | SendNotification | SendNotificationRequest | Notification | 50056 |
| NotificationService | ListNotifications | ListNotificationsRequest | ListNotificationsResponse | 50056 |
| NotificationService | MarkAsRead | MarkAsReadRequest | MarkAsReadResponse | 50056 |
| NotificationService | SearchNotifications | SearchNotificationsRequest | SearchNotificationsResponse | 50056 |
| NotificationService | RecallNotification | RecallNotificationRequest | RecallNotificationResponse | 50056 |
| NotificationPreferenceService | GetPreference | GetPreferenceRequest | NotificationPreference | 50056 |
| NotificationPreferenceService | UpdatePreference | UpdatePreferenceRequest | NotificationPreference | 50056 |
| NotificationPreferenceService | GetPreferenceByChannel | GetPreferenceByChannelRequest | ChannelPreference | 50056 |
| NotificationPreferenceService | ListPreferences | ListPreferencesRequest | ListPreferencesResponse | 50056 |
| NotificationTemplateService | CreateTemplate | CreateTemplateRequest | NotificationTemplate | 50056 |
| NotificationTemplateService | GetTemplate | GetTemplateRequest | NotificationTemplate | 50056 |
| NotificationTemplateService | ListTemplates | ListTemplatesRequest | ListTemplatesResponse | 50056 |
| NotificationTemplateService | RenderTemplate | RenderTemplateRequest | RenderedTemplate | 50056 |
> 端口50056 | proto 包名:`next_edu_cloud.msg.v1` | proto 文件:[msg.proto](../../../packages/shared-proto/proto/msg.proto)
>
> **⚠️ ISSUE-009 待仲裁**ai-allocation.md 规定 13 RPC02-architecture-design.md 设计 17 RPC。下表列出设计文档完整 17 RPC标注基线 13 RPC✅基线 / 扩展待仲裁。coord 裁决后裁剪或放宽。
### 1.2 HTTP 端点(如有
#### NotificationService9 RPC5 基线 + 4 扩展
无对外 HTTP 端点,仅 gRPC。
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | --------- | ----------------- |
| SendNotification | `SendNotificationRequest{user_id, type, title, content, channel, metadata?, related_entity_type?, related_entity_id?}` | `Notification` | ✅基线 | 单条发送 |
| BatchSendNotification | `BatchSendNotificationRequest{items[], group_id?}` | `BatchSendNotificationResponse{ids[], failed[]}` | ➕扩展 | 批量发送 |
| ListNotifications | `ListNotificationsRequest{user_id, only_unread?, type?, page?, page_size?}` | `ListNotificationsResponse{notifications[], total}` | ✅基线 | 列表(补分页) |
| GetUnreadCount | `GetUnreadCountRequest{user_id}` | `GetUnreadCountResponse{count}` | ➕扩展 | 未读数Redis |
| MarkAsRead | `MarkAsReadRequest{id, user_id}` | `Empty` | ✅基线 | 标记已读 |
| BatchMarkAsRead | `BatchMarkAsReadRequest{ids[], user_id}` | `Empty` | ➕扩展 | 批量已读 |
| MarkAllAsRead | `MarkAllAsReadRequest{user_id, before?}` | `Empty` | ➕扩展 | 全部已读 |
| SearchNotifications | `SearchNotificationsRequest{user_id, query, type?, page?, page_size?}` | `SearchNotificationsResponse{notifications[], total}` | ✅基线 | ES 全文检索 |
| RecallNotification | `RecallNotificationRequest{group_id, reason?}` | `RecallNotificationResponse{recalled_count}` | ✅基线 | 撤回广播 |
### 1.3 GraphQL schema如 BFF
#### NotificationPreferenceService2 RPC 基线
不适用。
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| ------------------ | -------------------------------------------------- | --------------------------------------- | --------- | -------- |
| GetPreferences | `GetPreferencesRequest{user_id}` | `GetPreferencesResponse{preferences[]}` | ✅基线 | 偏好查询 |
| UpdatePreferences | `UpdatePreferencesRequest{user_id, preferences[]}` | `Empty` | ✅基线 | 偏好更新 |
### 1.4 Kafka 事件发布(如有
> **注**02-architecture-design.md 设计 2 RPC原 contract 版本的 GetPreferenceByChannel / ListPreferences 暂移除(待 ISSUE-009 仲裁是否保留
| Topic | Event | 消费方 |
| --------------------------- | ------------------------------------------------------ | ----------------------- |
| edu.msg.notification.events | NotificationEventaction: sent/read/recalled/failed | push-gateway / data-ana |
#### NotificationTemplateService4 RPC4 基线 + 2 扩展)
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| -------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------- | --------- | ---------- |
| CreateTemplate | `CreateTemplateRequest{code, type, title_template, content_template, default_channels, variables}` | `NotificationTemplate` | ✅基线 | 创建模板 |
| GetTemplate | `GetTemplateRequest{id}` | `NotificationTemplate` | ✅基线 | 查询模板 |
| ListTemplates | `ListTemplatesRequest{type?, status?}` | `ListTemplatesResponse{templates[]}` | ✅基线 | 模板列表 |
| UpdateTemplate | `UpdateTemplateRequest{id, ...}` | `NotificationTemplate` | ➕扩展 | 更新模板 |
| DeleteTemplate | `DeleteTemplateRequest{id}` | `Empty` | ➕扩展 | 删除模板 |
| RenderTemplate | `RenderTemplateRequest{code, variables, locale?}` | `RenderedNotification{title, content}` | ✅基线 | 渲染模板 |
> **汇总**:基线 13 RPC9 基线表中 ✅ × 5 + 偏好 ✅ × 2 + 模板 ✅ × 4 = 11... 实际基线 = NotificationService 5 + Preference 2 + Template 4 = 11。**注**:若严格按 ai-allocation 13 RPC基线应为 13此处设计文档 11 基线 + 6 扩展 = 17与 13 预算差 4。待 ISSUE-009 仲裁后最终确认。
### 1.2 HTTP 端点
> msg 对外以 gRPC 为主BFF 通过 gRPC 调用)。以下 REST 端点为过渡期/管理端使用,最终将逐步迁移至 gRPC。
| Method | Path | 权限 | 说明 |
| ------ | ---------------------------------------- | ----------------------- | -------------------- |
| POST | /notifications/send | MSG_NOTIFICATION_SEND | 单条发送 |
| POST | /notifications/batch | MSG_NOTIFICATION_SEND | 批量发送 |
| GET | /notifications/user/:userId | MSG_NOTIFICATION_READ | 用户通知列表 |
| GET | /notifications/user/:userId/unread-count | MSG_NOTIFICATION_READ | 未读数Redis |
| PUT | /notifications/:id/read | MSG_NOTIFICATION_READ | 标记已读 |
| GET | /notifications/search | MSG_NOTIFICATION_READ | ES 全文检索 |
| GET | /preferences/user/:userId | MSG_NOTIFICATION_READ | 用户偏好 |
| PUT | /preferences/user/:userId | MSG_NOTIFICATION_MANAGE | 更新偏好 |
| GET | /templates | MSG_NOTIFICATION_MANAGE | 模板列表 |
| POST | /templates | MSG_NOTIFICATION_MANAGE | 创建模板 |
### 1.3 GraphQL schema
不适用msg 是业务服务,非 BFF
### 1.4 Kafka 事件发布
> **⚠️ ISSUE-008 待仲裁**topic 命名存在三套约定per-event / aggregate / aggregate+action。本表采用 02-architecture-design.md §5.2 约定per-event topic与 004 §7.2 一致。coord 裁决后统一。
| Topic | Event Type | 触发时机 | 消费方 | Outbox |
| ---------------------------- | --------------------------- | -------------- | ------------------------------ | ------ |
| `edu.notification.sent` | NotificationSent | 通知发送成功 | push-gateway / data-ana | ✅ |
| `edu.notification.read` | NotificationRead | 通知被标记已读 | data-ana | ✅ |
| `edu.notification.recalled` | NotificationRecalled | 通知被撤回 | push-gateway删除已推送消息 | ✅ |
| `edu.notification.failed` | NotificationFailed | 通知投递失败 | data-ana监控告警 | ✅ |
> **Producer 幂等**`idempotent=true` + `transactionalId=msg-producer`
> **DLQ**:消费失败超 3 次投递 `edu.notification.dlq`(见 02-architecture-design.md §5待补充
### 1.5 错误码前缀
`MSG_`如 MSG_TEMPLATE_NOT_FOUND、MSG_CHANNEL_DISABLED、MSG_RATE_LIMITED
`MSG_`完整清单见 02-architecture-design.md §6.2
- MSG_VALIDATION_ERROR / MSG_NOT_FOUND / MSG_PERMISSION_DENIED / MSG_CONFLICT / MSG_BUSINESS_ERROR / MSG_DATABASE_ERROR / MSG_INTERNAL_ERROR当前已实现 7 个)
- MSG_ES_UNAVAILABLE / MSG_REDIS_UNAVAILABLE / MSG_PUSH_GATEWAY_UNAVAILABLE / MSG_TEMPLATE_NOT_FOUND / MSG_TEMPLATE_RENDER_ERROR / MSG_RATE_LIMIT_EXCEEDEDP5 新增 6 个)
---
@@ -49,22 +97,39 @@
### 2.1 gRPC 调用(同步)
无主动 gRPC 调用上游。msg 通过 Kafka 事件被动接收业务事件后触发通知。
| 调用方 | 对方服务 | 协议 | RPC | 用途 | 状态 |
| ------------ | ------------ | ---- | -------------------- | ---------------------- | ------------------ |
| msg → push | push-gateway | gRPC | PushService.Push | 实时推送通知到在线用户 | P5 待实现D5 |
> 当前降级fetch POST `/internal/push`(见 [notifications.service.ts](../../../services/msg/src/notifications/notifications.service.ts) L179
> **调用方向澄清**ISSUE-005004 §4.1 表述"push-gateway → Msg"有歧义,实际方向是 msg → push-gateway
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| --------------------------- | --------------------------------------------------------- | --------------- | ------------------------------------------------------- |
| edu.iam.user.events | UserEvent | iam (ai06) | iam 就绪前使用本地用户偏好默认值 |
| edu.exam.events | ExamEventaction: created/updated/deleted | core-edu (ai08) | core-edu 就绪前不订阅,使用本地 stub 事件触发 mock 通知 |
| edu.homework.events | HomeworkEventaction: assigned/submitted/graded | core-edu (ai08) | 同上 |
| edu.grade.events | GradeEventaction: recorded/updated | core-edu (ai08) | 同上 |
| edu.class.events | ClassEventaction: transferred | core-edu (ai08) | 同上 |
| edu.data_ana.mastery.events | MasteryEventaction: mastery.updated/warning.triggered | data-ana (ai11) | data-ana 就绪前不订阅,预警通知使用本地 stub |
> **⚠️ ISSUE-008 待仲裁**topic 命名采用 02-architecture-design.md §5.1 约定per-event topic与 004 §7.2 一致)。原 contract 版本的 aggregate topicedu.iam.user.events / edu.exam.events待 coord 裁决后统一。
### 2.3 HTTP 调用(如有)
| Topic | 来源服务 | 处理逻辑 | 触发通知 | 幂等键 | 阻塞性 |
| ----------------------------------- | ------------- | ---------------------------- | -------------------- | ---------- | ------ |
| `edu.identity.user.created` | iam (ai06) | 发送欢迎通知 | welcome 通知 | `event_id` | 🟡 |
| `edu.identity.user.updated` | iam (ai06) | 用户信息变更,清理缓存 | — | `event_id` | 🟡 |
| `edu.identity.user.deleted` | iam (ai06) | 用户删除,归档通知 | — | `event_id` | 🟡 |
| `edu.identity.user.role_changed` | iam (ai06) | 角色变更通知 | system 通知 | `event_id` | 🟡 |
| `edu.identity.role.created` | iam (ai06) | 角色新增(管理通知) | system 通知 | `event_id` | 🟡 |
| `edu.identity.role.updated` | iam (ai06) | 角色权限变更通知 | system 通知 | `event_id` | 🟡 |
| `edu.teaching.exam.published` | core-edu (ai08) | 推送考试通知给班级学生 | exam 通知fan-out | `event_id` | 🟡 |
| `edu.teaching.assignment.submitted` | core-edu (ai08) | 通知教师有学生提交作业 | homework 通知 | `event_id` | 🟡 |
| `edu.teaching.assignment.graded` | core-edu (ai08) | 通知学生作业已批改 | grade 通知 | `event_id` | 🟡 |
| `edu.teaching.grade.recorded` | core-edu (ai08) | 通知学生成绩已录入 | grade 通知 | `event_id` | 🟡 |
| `edu.teaching.attendance.recorded` | core-edu (ai08) | 出勤异常通知家长 | attendance 通知 | `event_id` | 🟡 |
| `edu.insight.mastery.updated` | data-ana (ai11) | 学情掌握度下降,触发预警 | mastery_alert 通知 | `event_id` | 🟡 |
> **⚠️ ISSUE-013 阻塞**events.proto 当前仅有 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent缺 UserEvent / RoleEvent / MasteryEvent / NotificationEvent 4 类 message。msg 消费需 coord 补齐 protoD1。补齐前用通用 JSON payload 解析
>
> **幂等去重**三层防线L1 Redis SETNX `msg:processed:{event_id}` TTL 7d / L2 msg_idempotency 表 / L3 notifications.event_id UNIQUE INDEX
### 2.3 HTTP 调用
无主动 HTTP 调用上游push-gateway 走 gRPCHTTP 仅为降级)。
---
@@ -72,39 +137,60 @@
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— 用于用户通知偏好查询(可选)
- [ ] core-edu gRPC 50053 启用ai08—— 业务事件来源
- [ ] edu.exam.events / edu.homework.events / edu.grade.events / edu.class.events topic 有事件发布ai08
- [ ] data-ana gRPC 50055 启用ai11—— 预警事件来源
- [ ] edu.data_ana.mastery.events topic 有事件发布ai11
| 标志 | 提供方 | 阻塞性 | 说明 |
| ---- | ------ | ------ | ---- |
| events.proto 补 UserEvent/RoleEvent/MasteryEvent/NotificationEvent | coord | 🔴 阻塞 T9 | D1ISSUE-013 |
| events.proto GradeEvent 补 class_id全部补 student_ids[] | coord | 🔴 阻塞 fan-out | D2ISSUE-006 |
| msg.proto 补 5 扩展 RPC + Preference/Template Service | coord | 🔴 阻塞 gRPC | D3-D4ISSUE-009 |
| push-gateway gRPC PushService.Push | ai02 | 🔴 阻塞 T8 | D5 |
| iam 发布 6 类 user/role 事件 | ai06 | 🟡 集成验证 | D6开发期用 mock |
| core-edu 发布 5 类教学事件 | ai08 | 🟡 集成验证 | D7 |
| data-ana 发布 mastery 事件 | ai11 | 🟡 集成验证 | D8 |
| Redis 集群可用 | infra | 🟡 降级 DB | D9 |
### 3.2 我的就绪标志(供下游消费)
- [ ] msg gRPC 50056 启用HealthService.Check 返回 SERVING
- [ ] NotificationService 5 RPC 可调用
- [ ] NotificationPreferenceService 4 RPC 可调用
- [ ] NotificationTemplateService 4 RPC 可调用(含 RenderTemplate 模板渲染
- [ ] edu.msg.notification.events topic 可发布(供 push-gateway 推送
- [ ] NotificationService RPC 可调用(基线 5 + 扩展 4待 ISSUE-009 仲裁)
- [ ] NotificationPreferenceService 2 RPC 可调用
- [ ] NotificationTemplateService RPC 可调用(基线 4 + 扩展 2含 RenderTemplate
- [ ] `edu.notification.sent/read/recalled/failed` topic 可发布(待 ISSUE-008 仲裁命名
- [ ] /readyz 返回 6 项依赖状态DB/ES/Redis/Kafka producer/Kafka consumer/PushGateway
- [ ] 测试覆盖率 ≥ 80%
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游)
在 msg 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / push-gateway提供以下 mock
在 msg 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / push-gateway提供 mock
- **gRPC mock**使用 grpc-mock 拦截 50056 端口
- NotificationService.ListNotifications 返回固定 10 条未读通知
- NotificationService.MarkAsRead 返回 success=true
- NotificationPreferenceService.GetPreference 返回默认偏好in_app+email 开启sms+push 关闭)
- NotificationTemplateService.RenderTemplate 返回固定 title+content
- **Kafka mock**msg 就绪前不发布真实 NotificationEventpush-gateway 使用本地 stub 推送
- **gRPC mock**grpc-mock 拦截 50056 端口
- ListNotifications 返回固定 10 条未读通知
- MarkAsRead 返回 success=true
- GetPreferences 返回默认偏好in_app + email 开启sms + push 关闭)
- RenderTemplate 返回固定 title + content
- **Kafka mock**msg 就绪前不发布真实通知事件push-gateway 使用本地 stub 推送
### 4.2 我消费的 mock
### 4.2 我消费的 mock(开发期间)
在真实上游就绪前msg 使用以下 mock
- 业务事件core-edu/data-ana 就绪前msg 内置定时器发布本地 stub 事件ExamEvent/HomeworkEvent触发 mock 通知流程
- 用户偏好iam 就绪前使用默认偏好(所有用户 in_app 开启)
- 模板渲染:内置 5 个常用模板exam.created / homework.assigned / grade.recorded / warning.triggered / system.notice
- **业务事件**core-edu / data-ana 就绪前msg 内置定时器发布本地 stub 事件ExamEvent / HomeworkEvent触发 mock 通知流程
- **用户偏好**iam 就绪前使用默认偏好(所有用户 in_app 开启)
- **模板渲染**:内置 5 个常用模板exam.published / homework.graded / grade.recorded / mastery.warning / system.notice
- **Push Gateway**ai02 就绪前用 fetch POST /internal/push 降级(当前实现保留)
---
## §5 待仲裁项汇总
| ISSUE | 主题 | 阻塞性 | 当前采用 |
| ----- | ---- | ------ | -------- |
| ISSUE-008 | Kafka topic 命名per-event vs aggregate | 🟡 | per-event02-architecture-design.md §5 + 004 §7.2 |
| ISSUE-009 | RPC 数量13 vs 17 | 🟡 | 1702-architecture-design.md §4.2),标注基线/扩展 |
| ISSUE-013 | events.proto 缺 4 类 message | 🔴 | 用 JSON payload 降级,待 coord 补齐 |
| ISSUE-006 | events.proto P9 字段描述 | 🟡 | 待 coord 修正 |
| ISSUE-010 | markAsRead 权限点 | 🟡 | 以 02-architecture-design.md §6.1 为准READ |
| ISSUE-011 | DB↔ES 降级方向 | 🟡 | 待 coord 仲裁(双向降级 vs 单向) |

View File

@@ -1,48 +1,98 @@
# parent-bff 对接契约
> 负责人ai05
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)
> 关联:[matrix.md](../matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[02-architecture-design.md](../../../services/parent-bff/docs/02-architecture-design.md)
> 修订2026-07-10 ai05 复审修正仲裁引用、proto 包名、契约缺口标注)
---
## §0 契约基线说明
| 维度 | 内容 |
| --- | --- |
| proto 包名前缀 | 所有 proto 包名统一为 `next_edu_cloud.<domain>.v1`(如 `next_edu_cloud.iam.v1`),引用 proto message 时须带完整包名 |
| GraphQL schema 文件 | `packages/shared-ts/contracts/graphql/parent-bff.graphql`SDL-first 集中管理,对齐 coord ARB-001 模式) |
| 错误码前缀 | `BFF_PARENT_`C1 仲裁BFF 在前) |
| 端口 | HTTP 3010不暴露 gRPCC2 仲裁) |
| API 风格 | GraphQL YogaU3 仲裁P4 直接 GraphQL不走 REST 过渡) |
| 权限校验 | BFF 豁免 @RequirePermissionU4 仲裁),仅校验 x-user-id + ChildGuard 越权防御 |
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
无对外 gRPC。parent-bff 是 GraphQL 聚合层。
无对外 gRPC。parent-bff 是 GraphQL 聚合层,对上游仅暴露 HTTP/GraphQLC2 仲裁)
### 1.2 HTTP 端点(如有)
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 |
| ------ | -------- | ----------------------------------------- | ---------------------- |
| POST | /graphql | 家长 BFF GraphQL 端点 | JWT 必需 + parent 角色 |
| GET | /graphql | GraphQL Playground开发环境 | 开发环境公开 |
| GET | /healthz | 健康检查liveness | 公开 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性 | 公开 |
| Method | Path | 用途 | 认证 | 阶段 |
| --- | --- | --- | --- | --- |
| POST | /graphql | 家长 BFF GraphQL 端点U3 仲裁) | JWT 必需 + parent 角色 | P4 |
| GET | /graphql | GraphQL Playground开发环境) | 开发环境公开 | P4 |
| GET | /healthz | 健康检查liveness,直接返回 ok | 公开 | P4 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性02 §9 #7 | 公开 | P4 |
| GET | /metrics | Prometheus 指标parent_bff_* | 公开 | P4 |
### 1.3 GraphQL schema如 BFF
> 网关路径:`/api/v1/parent/*` → api-gateway 剥离 `/api/v1` 后代理到 parent-bff:3010
> **不实现 REST 业务端点**02 §4.1,仅保留 /healthz /readyz /metrics 基础端点)
GraphQL schema 文件路径:`apps/parent-bff/src/schema/*.graphql`(端口 :3010
### 1.3 GraphQL schema
核心 Query / Mutation 域:
**schema 文件**`packages/shared-ts/contracts/graphql/parent-bff.graphql`SDL-first
**完整 schema 定义**:见 [02-architecture-design.md §4.2](../../../services/parent-bff/docs/02-architecture-design.md) GraphQL Schema 完整定义
- **auth**currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- **children**myChildren聚合 iam.GetChildrenByParent核心依赖 I3 裁决)
- **childSummary**childSummary聚合 data-ana.AnalyticsService.GetParentDashboard
- **childGrades**childGrades聚合 core-edu.GradeService.ListGradesByStudent
- **childAttendance**childAttendance聚合 core-edu.AttendanceService.ListAttendanceByStudent
- **childHomework**childHomework聚合 core-edu.HomeworkService.ListHomeworkByClass
- **childWeakness**childWeakness聚合 data-ana.AnalyticsService.GetStudentWeakness
- **childTrend**childTrend聚合 data-ana.AnalyticsService.GetLearningTrend
- **notifications**myNotifications / markAsRead聚合 msg.NotificationService
核心 Query / Mutation 域(按阶段分级):
### 1.4 Kafka 事件发布(如有)
| 类型 | 字段 | 聚合下游 | ChildGuard | 阶段 |
| --- | --- | --- | --- | --- |
| Query | dashboard | iam + core-edu | 否(聚合所有孩子) | P4 |
| Query | viewports | iam | 否 | P4 |
| Query | me | iam | 否 | P4 |
| Query | children | iam | 否 | P4 |
| Query | child | iam + core-edu | 是 | P4 |
| Query | childGrades | core-edu | 是 | P4 |
| Query | childHomework | core-edu | 是 | P4 |
| Query | childExams | core-edu | 是 | P4 |
| Query | childAnalytics | data-ana | 是 | P4 |
| Query | notifications | msg | 否 | P5 |
| Query | notificationPreferences | msg | 否 | P5 |
| Mutation | selectChild | BFF 内部审计) | 是 | P4 |
| Mutation | markNotificationRead | msg | 否 | P5 |
| Mutation | updateNotificationPreferences | msg | 否 | P5 |
无。parent-bff 不发布事件,仅做 gRPC 聚合。
**统一响应信封**GraphQL 规范data/errors错误扩展字段携带 `extensions.code = "BFF_PARENT_*"`02 §4.5
### 1.4 Kafka 事件发布
无。parent-bff 不发布领域事件BFF 聚合层无业务状态变更02 §5.6)。
### 1.5 错误码前缀
`BFF_PARENT_`如 BFF_PARENT_UPSTREAM_UNAVAILABLE、BFF_PARENT_AGGREGATION_FAILED、BFF_PARENT_NO_CHILDREN、BFF_PARENT_FORBIDDEN
`BFF_PARENT_`C1 仲裁BFF 在前;非 PARENT_BFF_
错误码清单(完整见 02 §6.2
| 错误码 | HTTP | 触发条件 |
| --- | --- | --- |
| BFF_PARENT_VALIDATION_ERROR | 400 | Zod / GraphQL input 校验失败 |
| BFF_PARENT_UNAUTHORIZED | 401 | 缺失 x-user-id 头 |
| BFF_PARENT_CHILD_NOT_BOUND | 403 | ChildGuard 拦截childId 不在家长绑定列表 |
| BFF_PARENT_NOT_FOUND | 404 | 资源不存在 |
| BFF_PARENT_BAD_GATEWAY | 502 | 下游服务返回非 ok 或 gRPC rejected |
| BFF_PARENT_GATEWAY_TIMEOUT | 504 | 下游调用超时 |
| BFF_PARENT_SERVICE_UNAVAILABLE | 503 | 熔断器开启P6 |
| BFF_PARENT_INTERNAL_ERROR | 500 | 未捕获异常 |
**下游错误透传**C3 仲裁core-edu 统一 CORE_EDU_*
| 下游服务 | 错误码前缀 | 示例 |
| --- | --- | --- |
| iam | IAM_ | IAM_USER_NOT_FOUND |
| core-edu | CORE_EDU_ | CORE_EDU_GRADE_NOT_FOUND |
| data-ana | DATA_ANA_ | DATA_ANA_ANALYTICS_NOT_READY |
| msg | MSG_ | MSG_NOTIFICATION_NOT_FOUND |
---
@@ -50,28 +100,59 @@ GraphQL schema 文件路径:`apps/parent-bff/src/schema/*.graphql`(端口 :3
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | ----------------------------------------- | ------------------------ | ------------------------------------------------------ |
| iam (ai06) | IamService.GetUserInfo | 获取当前家长信息 | iam 就绪前返回固定 UserInfoparent 角色) |
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | iam 就绪前返回家长权限集 |
| iam (ai06) | IamService.GetViewports | 家长导航菜单 | iam 就绪前返回固定视口列表 |
| iam (ai06) | IamService.GetChildrenByParent | 查询关联孩子列表(核心) | iam 就绪前返回固定 2 个 ChildInfoI3/ISSUE-047 裁决) |
| core-edu (ai08) | GradeService.ListGradesByStudent | 孩子成绩 | core-edu 就绪前返回固定 5 个 Grade |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | 孩子考勤 | core-edu 就绪前返回固定 10 条 Attendance |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | 孩子作业 | core-edu 就绪前返回固定 3 个 Homework |
| data-ana (ai11) | AnalyticsService.GetParentDashboard | 家长仪表盘 | data-ana 就绪前返回固定仪表盘child_avg_score=85.0 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 孩子薄弱点 | data-ana 就绪前返回固定 3 个 weak_points |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | 孩子学习趋势 | data-ana 就绪前返回固定趋势数据 |
| msg (ai10) | NotificationService.ListNotifications | 家长通知 | msg 就绪前返回固定 10 条通知 |
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | msg 就绪前返回 success=true |
> proto 包名均为 `next_edu_cloud.<domain>.v1`
> **状态标注**:✅ 已有 = proto 已定义;❌ 待补 = proto 缺失;⚠️ 待仲裁 = ai05 提请 coord 仲裁中
### 2.2 Kafka 事件订阅(异步)
| 被调用方 | Service.RPC | proto message 包名 | 用途 | mock 策略 | 状态 |
| --- | --- | --- | --- | --- | --- |
| iam (ai06) | IamService.GetUserInfo | next_edu_cloud.iam.v1.UserInfo | 获取当前家长信息 | 返回固定 UserInfoparent 角色) | ✅ 已有 |
| iam (ai06) | IamService.GetViewports | next_edu_cloud.iam.v1.Viewport[] | 家长导航菜单 | 返回固定视口列表 | ❌ 待 ai06 补coord-cross-review #1 |
| iam (ai06) | IamService.GetEffectivePermissions | next_edu_cloud.iam.v1.EffectivePermissions | 权限校验 | 返回家长权限集 | ❌ 待 ai06 补 |
| iam (ai06) | **IamService.GetChildrenByParent** | next_edu_cloud.iam.v1.Child[] | 查询关联孩子列表(**P0 核心依赖** | 返回固定 2 个 ChildInfo | ❌ 待 ai06 补(**I6 裁决**P0 阻塞) |
| core-edu (ai08) | GradeService.ListGradesByStudent | next_edu_cloud.core_edu.v1.Grade[] | 孩子成绩 | 返回固定 5 个 Grade | ✅ 已有 |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | next_edu_cloud.core_edu.v1.Homework[] | 孩子作业 | 返回固定 3 个 Homework | ✅ 已有 |
| core-edu (ai08) | ExamService.ListExamsByClass | next_edu_cloud.core_edu.v1.Exam[] | 孩子考试 | 返回固定 3 个 Exam | ✅ 已有 |
| core-edu (ai08) | **ClassService.GetClass** | next_edu_cloud.core_edu.v1.Class | 孩子班级信息 | 返回固定 ClassInfo | ❌ 待补(**ISSUE-008**proto 缺 ClassService待 coord 仲裁归属) |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | next_edu_cloud.core_edu.v1.Attendance[] | 孩子考勤P5+ | 返回固定 10 条 | ❌ 待 ai08 补coord-cross-review #5P3 补全) |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | next_edu_cloud.analytics.v1.StudentWeakness | 孩子薄弱点 | 返回固定 3 个 weak_points | ✅ 已有 |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | next_edu_cloud.analytics.v1.LearningTrend | 孩子学习趋势 | 返回固定趋势数据 | ✅ 已有 |
| data-ana (ai11) | AnalyticsService.GetClassPerformance | next_edu_cloud.analytics.v1.ClassPerformance | 班级学情对比 | 返回固定班级数据 | ✅ 已有 |
| data-ana (ai11) | **AnalyticsService.GetParentDashboard** | — | 家长仪表盘聚合(多子女防 N+1 | 返回固定仪表盘 | ⚠️ 待仲裁02 §14 #3ai05 提请proto 未定义) |
| msg (ai10) | NotificationService.ListNotifications | next_edu_cloud.msg.v1.Notification[] | 家长通知 | 返回固定 10 条通知 | ✅ 已有 |
| msg (ai10) | NotificationService.MarkAsRead | next_edu_cloud.msg.v1.Empty | 标记已读 | 返回 success | ✅ 已有 |
| msg (ai10) | **NotificationPreferenceService.*** | — | 通知偏好配置 | 返回固定偏好 | ❌ 待 ai10 补proto + 实现均缺失P5 |
无。parent-bff 不订阅 Kafka 事件,仅做同步 gRPC 聚合。
**类型映射注意**ISSUE-008
### 2.3 HTTP 调用(如有)
- `core_edu.v1.Grade.score``string` 类型GraphQL `Grade.score``Float!`BFF response-mapper 需做 `Number.parseFloat(score)` 转换,转换失败抛 BFF_PARENT_BAD_GATEWAY
- `msg.v1.Notification.is_read` 映射为 GraphQL `Notification.read`(字段名重命名)
- `msg.v1.Notification``child_id` 字段ISSUE-007GraphQL `Notification.childId` 暂从 notification.type+content 解析或置 null待 ai10 补 proto 字段
无。
### 2.2 Kafka 事件订阅异步P5 可选)
> P4 阶段不订阅事件02 §5.1。P5 阶段可选订阅以下 topic 用于实时推送 + 缓存失效02 §5.2)。
| Topic | 事件 | 发布方 | 消费动作 | 幂等性 | 阶段 |
| --- | --- | --- | --- | --- | --- |
| edu.notification.sent | 通知发送 | msg | 推送给家长push-gateway HTTP | event_id SETNX | P5 |
| edu.notification.read | 通知已读 | msg | 失效 bff:parent:notifications:* | event_id SETNX | P5 |
| edu.notification.recalled | 通知撤回 | msg | 失效通知缓存 + 推送撤回 | event_id SETNX | P5 |
| edu.notification.failed | 通知失败 | msg | 记录日志 + 告警 | event_id SETNX | P5 |
| edu.teaching.grade.recorded | 成绩录入 | core-edu | 失效 bff:parent:grades:{childId} + 推送 | event_id SETNX | P5 |
| edu.teaching.homework.graded | 作业批改 | core-edu | 失效 bff:parent:homework:{childId} + 推送 | event_id SETNX | P5 |
| edu.teaching.exam.published | 考试发布 | core-edu | 失效 bff:parent:exams:{childId} + 推送 | event_id SETNX | P5 |
**消费者组**`parent-bff-event-subscriber`02 §5.5
**DLQ**`edu.parent-bff.dlq`
**提交策略**manual commit
### 2.3 HTTP 调用(非 gRPC
| 被调用方 | Method | Path | 用途 | 认证 | 阶段 |
| --- | --- | --- | --- | --- | --- |
| push-gateway (ai02) | POST | /internal/push | 推送给在线家长(**U2 仲裁**push-gateway 豁免 gRPC | X-Internal-Key | P5 |
> push-gateway HTTP 调用在 P5 阶段启用P4 不涉及。
---
@@ -79,43 +160,73 @@ GraphQL schema 文件路径:`apps/parent-bff/src/schema/*.graphql`(端口 :3
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— **核心依赖 GetChildrenByParentI3/ISSUE-047 裁决**
- [ ] iam gRPC 50052 启用ai06—— **核心依赖 GetChildrenByParentI6 裁决P0 阻塞**
- [ ] iam 补 GetViewports / GetEffectivePermissions RPCcoord-cross-review #1
- [ ] iam_student_guardians 表已建立I6 裁决)
- [ ] core-edu gRPC 50053 启用ai08
- [ ] core-edu ClassService.GetClass proto 补全ISSUE-008 待仲裁)
- [ ] data-ana gRPC 50055 启用ai11
- [ ] msg gRPC 50056 启用ai10
- [ ] msg gRPC 50056 启用ai10—— P5
- [ ] msg.proto Notification 补 child_id 字段ISSUE-007 待仲裁)—— P5
- [ ] msg NotificationPreferenceService 补全 —— P5
- [ ] push-gateway /internal/push 启用ai02—— P5
- [ ] api-gateway /parent 路由注册ai01
- [ ] Kafka topic 已创建C5 仲裁)—— P5
- [ ] Redis 已部署且网络可达
- [ ] buf.gen.yaml 补 gRPC TS 插件coord
### 3.2 我的就绪标志(供下游消费)
- [ ] parent-bff GraphQL :3010 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 4 个下游 gRPC 连通性检查)
- [ ] /readyz 返回 200含 4 个下游 gRPC 连通性检查iam + core-edu + data-ana + Redis02 §9 #7
- [ ] GraphQL schema 可内省POST /graphql 返回 schema
- [ ] 核心 Query 可执行:currentUser / myChildren / childSummary / childGrades
- [ ] 核心 Mutation 可执行:markAsRead
- [ ] 数据范围校验生效(家长只能查自己孩子的数据,基于 iam.GetChildrenByParent 返回的 user_id 校验)
- [ ] 核心 Query 可执行:dashboard / children / childGrades / childAnalytics
- [ ] 核心 Mutation 可执行:selectChildP4/ markNotificationReadP5
- [ ] DataScope=CHILDREN 校验生效(家长只能查自己孩子的数据,ChildGuard 基于 iam.GetChildrenByParent 返回的列表校验)
- [ ] /metrics 暴露 parent_bff_* 指标
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游 parent-portal ai15
在 parent-bff 真实就绪前,为下游(parent-portal提供以下 mock
在 parent-bff 真实就绪前,为 parent-portal 提供以下 mock
- **GraphQL mock**:使用 Apollo Server mockProviders 或 MSW 拦截 POST /graphql
- currentUser 返回固定家长id="parent-001", name="王家长", roles=["parent"]
- myChildren 返回固定 2 个孩子id="student-001" 李同学 + id="student-002" 李妹妹)
- childSummary 返回固定仪表盘child_avg_score=85.0, child_class_rank=5
- childGrades 返回固定 5 个成绩
- childAttendance 返回固定 10 条考勤
- myNotifications 返回固定 10 条通知
- **GraphQL mock**:使用 MSW 拦截 POST /graphql 或 Apollo Server mockProviders
- `dashboard` 返回固定家长id="parent-001", name="王家长", roles=["parent"]+ 2 个孩子 + unreadNotifications=3
- `children` 返回固定 2 个孩子id="student-001" 李同学 + id="student-002" 李妹妹)
- `childGrades` 返回固定 5 个成绩(含 score 字段Float 类型
- `childAnalytics` 返回固定学情child_avg_score=85.0, class_rank=5
- `childHomework` 返回固定 3 个作业
- `childExams` 返回固定 3 个考试
- `notifications`P5返回固定 10 条通知(含 childId 字段)
### 4.2 我消费的 mock
### 4.2 我消费的 mock(上游未就绪时)
在真实上游就绪前parent-bff 使用以下 mock详见 §2.1 mock 策略列):
- **iam mock**:固定 UserInfo + 家长权限 + 固定视口 + 固定 2 个 ChildInfo家长-学生关联核心数据)
- **core-edu mock**:固定孩子成绩/考勤/作业
- **data-ana mock**:固定家长仪表盘/孩子薄弱点/趋势
- **msg mock**:固定通知列表 + MarkAsRead success
- **core-edu mock**:固定孩子成绩/作业/考试/班级信息
- **data-ana mock**:固定孩子薄弱点/趋势/班级对比
- **msg mock**P5:固定通知列表(含 childId+ MarkAsRead success + 固定偏好配置
- **push-gateway mock**P5fetch mock 返回 success
- **Redis**Testcontainers 真实 Redis 实例(不用 mock
- **Kafka**P5kafkajs mock + jest.mock
> 关键iam.GetChildrenByParent 的 mock 必须返回与 core-edu mock 数据一致的 student_id,否则数据范围校验会失败。parent-bff 启动时校验 myChildren 返回的 user_id 与下游查询的 student_id 一致性。
> **关键约束**iam.GetChildrenByParent 的 mock 必须返回与 core-edu mock 数据一致的 student_idstudent-001 + student-002否则 ChildGuard 越权校验会失败。parent-bff 启动时校验 myChildren 返回的 user_id 与下游查询的 student_id 一致性。
---
## §5 跨模块契约冲突跟踪
> 以下为 ai05 复审发现的跨模块契约问题,详见 [objections/parent-bff_issue.md](../objections/parent-bff_issue.md)
| ISSUE | 问题 | 影响 | 状态 |
| --- | --- | --- | --- |
| ISSUE-003 | contract.md 仲裁引用编号错误I3 → I6 | 引用勘误,已修正本文档 | 待 coord 确认 |
| ISSUE-004 | 004 §4 + matrix.md §1 未同步 C6 仲裁(缺 DataAna + Msg | 新 AI 误判依赖 | 待 coord 仲裁 |
| ISSUE-005 | parent-portal 01 文档仍按 REST 消费 parent-bff | 跨模块契约冲突 | 待 coord 仲裁 |
| ISSUE-006 | proto 包名引用缺 next_edu_cloud 前缀 | gRPC 代码生成错误,已修正本文档 | 待 coord 确认 |
| ISSUE-007 | msg.proto Notification 缺 child_id 字段 | 按孩子过滤通知失效 | 待 coord 仲裁 |
| ISSUE-008 | core_edu.proto 缺 ClassService + Grade.score 类型不一致 | 班级信息查询 + 类型转换 | 待 coord 仲裁 |

View File

@@ -1,7 +1,9 @@
# parent-portal 对接契约
> 负责人ai15
> 关联:[matrix.md](./matrix.md)
> 关联:[matrix.md](./matrix.md)、[parent-bff_contract.md](./parent-bff_contract.md)、[iam_contract.md](./iam_contract.md)、[push-gateway_contract.md](./push-gateway_contract.md)
> 依据ARB-001BFF GraphQL、ARB-002MF Shell 暴露清单)、[port-allocation.md](../../../infra/port-allocation.md) §4
> 待仲裁ISSUE-001 ~ ISSUE-010见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md)),仲裁前本契约按 ARB-001 GraphQL 方向编写
---
@@ -9,20 +11,20 @@
### 1.1 gRPC 接口(如有)
无。parent-portal 是前端微前端 Remote。
无。parent-portal 是前端微前端 Remote,不提供 gRPC
### 1.2 HTTP 端点(如有)
| Method | Path | 用途 | 认证 |
| ------ | --------------------- | ------------ | ------------------------------------- |
| GET | / | 家长门户首页 | JWT 必需(前端路由守卫) |
| GET | /children | 我的孩子列表 | JWT 必需 |
| GET | /child/:id/summary | 孩子概况 | JWT 必需 + 数据范围校验(仅自己孩子) |
| GET | /child/:id/grades | 孩子成绩 | JWT 必需 + 数据范围校验 |
| GET | /child/:id/attendance | 孩子考勤 | JWT 必需 + 数据范围校验 |
| GET | /child/:id/homework | 孩子作业 | JWT 必需 + 数据范围校验 |
| GET | /child/:id/weakness | 孩子薄弱点 | JWT 必需 + 数据范围校验 |
| GET | /notifications | 通知中心 | JWT 必需 |
无对外 HTTP API 端点。parent-portal 是 Next.js 前端应用MF Remote不对外暴露 REST API。
> **说明**ISSUE-006parent-portal 的页面路由(`/parent/dashboard`、`/parent/grades` 等)是前端 SSR/CSR 路由,不是 HTTP API 端点。页面路由清单见 [01-understanding.md §8 L2 路由表](../../../apps/parent-portal/docs/01-understanding.md#8-l2-路由表)。
>
> parent-portal 仅提供两个内部健康检查端点(非业务 API
| Method | Path | 用途 | 认证 |
| ------ | ------------ | ----------------------- | ---- |
| GET | /api/health | Dockerfile HEALTHCHECK | 无 |
| GET | /api/ready | K8s readinessProbe | 无 |
### 1.3 GraphQL schema如 BFF
@@ -30,19 +32,26 @@
### 1.4 Kafka 事件发布(如有)
无。
无。前端不发布 Kafka 事件。
### 1.5 错误码前缀
无(前端不定义错误码前缀,透传 BFF 错误码)
parent-portal 不产生错误码前缀(前端不定义错误码)。消费侧错误码前缀见 §2.5
### 1.6 微前端架构(补充)
### 1.6 微前端架构
| 角色 | 说明 |
| ---------------------- | ------------------------------------------------ |
| MF Remote | 家长门户是微前端远程模块 |
| 暴露的 remote 模块 | ParentApp家长端完整应用、shared 家长端组件 |
| module federation 配置 | `apps/parent-portal/module-federation.config.ts` |
| 角色 | 说明 |
| ---- | ---- |
| MF 角色 | RemoteShell = teacher-portal :4000 |
| Remote name | `parent_app` |
| remoteEntry 路径 | `static/chunks/remoteEntry.js` |
| 暴露模块 | `./pages`(家长场景页面)、`./ChildSwitcher`(多子女切换组件) |
| MF 配置文件 | `apps/parent-portal/next.config.js`NextFederationPlugin见 [02-architecture-design §1.2](../../../apps/parent-portal/docs/02-architecture-design.md#12-mf-配置parent-portalnextconfigjs-remote-角色) |
| MF sharedsingleton | react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooksARB-002 |
| dev/prod 端口 | 4002[port-allocation.md](../../../infra/port-allocation.md) §4 |
| feature flag | `NEXT_PUBLIC_MF_ENABLED`ARB-002P4 默认开) |
> **注**ISSUE-007MF 配置文件统一为 `next.config.js`,不使用 `module-federation.config.ts`(与 02-architecture-design + teacher-portal Shell 一致)。
---
@@ -56,27 +65,85 @@
无。前端不直接订阅 Kafka。
### 2.3 HTTP 调用(如有
### 2.3 HTTP 调用(非 GraphQL
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ------------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/parent/graphql | 家长 GraphQL 查询(经网关代理到 parent-bff | api-gateway/parent-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 家长登录 | api-gateway 就绪前使用 MSW 返回固定 JWT |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------ | --------------------- | -------- | ------------------------------------------- |
| api-gateway (ai01) | POST /api/v1/iam/login | 家长登录 | api-gateway 就绪前 MSW 返回固定 JWTparent 角色) |
> **注**ISSUE-004
> - 登录端点统一为 `POST /api/v1/iam/login`(与 [matrix.md](./matrix.md) §5 `/api/v1/iam/*` + 01 §3.1 前缀一致)
> - 登录是 parent-portal 唯一走 REST非 GraphQL的端点登录前无 JWTGraphQL endpoint 需鉴权
> - 待 coord 确认登录是否走 REST其余走 GraphQL
### 2.4 GraphQL 查询域(经 api-gateway 代理到 parent-bff
| Query/Mutation | 用途 | mock 策略 |
| ---------------------------- | -------------------- | ----------------------------- |
| currentUser | 当前家长信息 | MSW 返回固定家长 |
| myChildren | 我的孩子列表(核心) | MSW 返回固定 2 个孩子 |
| childSummary | 孩子概况 | MSW 返回固定仪表盘 |
| childGrades | 孩子成绩 | MSW 返回固定 5 个成绩 |
| childAttendance | 孩子考勤 | MSW 返回固定 10 条考勤 |
| childHomework | 孩子作业 | MSW 返回固定 3 个作业 |
| childWeakness | 孩子薄弱点 | MSW 返回固定 3 个 weak_points |
| childTrend | 孩子学习趋势 | MSW 返回固定趋势数据 |
| myNotifications / markAsRead | 通知中心 | MSW 返回固定通知 |
> **依据**ARB-001BFF GraphQL+ [parent-bff_contract.md](./parent-bff_contract.md) §1.3
>
> **端点**`POST /api/v1/parent/graphql`api-gateway 代理 `/api/v1/parent/*` → parent-bff :3010 `/graphql`
> **注**ISSUE-001 / ISSUE-008
> - 01/02 文档描述为 REST 消费,与 ARB-001 冲突,待 coord 仲裁
> - 仲裁前本表按 GraphQL 方向编写(与 parent-bff contract + matrix.md 一致)
> - 路径前缀统一为 `/api/v1/parent/graphql`(与 matrix.md §5 一致,旧版缺 `v1`
| Query/Mutation | 类型 | 用途 | 对应 parent-bff 聚合 | mock 策略 |
| ------------------------------- | -------- | -------------------- | ------------------------------------------- | ---------------------------------- |
| currentUser | Query | 当前家长信息 | iam.GetUserInfo + GetEffectivePermissions + GetViewports | MSW 返回固定家长parent-001 王家长) |
| myChildren | Query | 我的子女列表(核心) | iam.GetChildrenByParentI3/ISSUE-047 裁决) | MSW 返回固定 2 个子女student-001 + student-002 |
| childSummary(childId) | Query | 子女仪表盘概览 | data-ana.GetParentDashboard | MSW 返回固定仪表盘 |
| childGrades(childId) | Query | 子女成绩 | core-edu.ListGradesByStudent | MSW 返回固定 5 个成绩 |
| childAttendance(childId) | Query | 子女考勤 | core-edu.ListAttendanceByStudent | MSW 返回固定 10 条考勤 |
| childHomework(childId) | Query | 子女作业 | core-edu.ListHomeworkByClass | MSW 返回固定 3 个作业 |
| childWeakness(childId) | Query | 子女薄弱点 | data-ana.GetStudentWeakness | MSW 返回固定 3 个 weak_points |
| childTrend(childId) | Query | 子女学习趋势 | data-ana.GetLearningTrend | MSW 返回固定趋势数据 |
| myNotifications | Query | 通知列表P5 | msg.ListNotifications | MSW 返回固定 10 条通知 |
| markAsRead(notificationId) | Mutation | 标记已读P5 | msg.MarkAsRead | MSW 返回 success=true |
| updateNotificationPreferences | Mutation | 更新通知偏好 | msg待 ai05 确认) | MSW 返回 success=true |
| switchChild(childId) | Mutation | 切换当前子女 | 待 ISSUE-009 仲裁确认 | 见 ISSUE-009 |
> **switchChild 说明**ISSUE-009
> - parent-bff_contract.md §1.3 未列 switchChild Mutation
> - 待 coord 仲裁switchChild 是 GraphQL Mutation后端记录当前子女还是纯前端状态localStorage + Zustand
> - 若纯前端:本表移除 switchChild切换逻辑在 `useChildSwitcher` 内直接写 Zustand + localStorage
### 2.5 消费的错误码前缀(前端 i18n 路由)
parent-portal 不产生错误码,仅消费。前端 API 请求层根据 `error.code` 前缀路由到对应 i18n key
| 前缀 | 来源服务 | i18n key 模式 |
| ------------- | ----------- | ------------------------- |
| `IAM_` | iam | `error.iam.{{code}}` |
| `CORE_EDU_` | core-edu | `error.core_edu.{{code}}` |
| `BFF_PARENT_` | parent-bff | `error.bff_parent.{{code}}` |
| `GW_` | api-gateway | `error.gw.{{code}}` |
| `NETWORK_` | 前端网络层 | `error.network.{{code}}` |
> **注**:与 [matrix.md](./matrix.md) §6 错误码前缀矩阵对齐。`BFF_PARENT_` 前缀由 parent-bff 定义(见 [parent-bff_contract.md](./parent-bff_contract.md) §1.5)。
### 2.6 WebSocket 推送P5
| 被调用方 | 协议 | 路径 | 用途 | mock 策略 |
| -------------------- | ----------- | ---- | ---------- | -------------------------------------- |
| push-gateway (ai02) | WebSocket | /ws | 实时推送 | mock-socket 模拟 WS 推送(每 30s 1 条) |
| push-gateway (ai02) | SSE降级 | /sse | SSE 降级 | — |
> WebSocket 连接由 Shell 建立统一连接管理parent-portal 通过 Zustand ui-store 订阅事件流。
### 2.7 消费的 MF Shell 暴露ARB-002
| 暴露模块 | 来源 | 用途 |
| -------- | ---- | ---- |
| AppShell | teacher-portal Shell | 左栏导航 + 主内容区布局 |
| GraphQLProvider | teacher-portal Shell | urql client 单例ARB-002 |
| useAuth | packages/hooks | 会话状态 |
| usePermission | packages/hooks | 权限查询 |
| useGraphQLClient | packages/hooks | urql client 获取 |
| ErrorBoundary | packages/ui-components | React 渲染异常兜底 |
| Loading / Empty | packages/ui-components | 骨架屏 / 空态 |
| RequirePermission | packages/ui-components | L3 组件级视口控制 |
> MF sharedsingletonreact / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooksARB-002 裁决,见 [coord.md](../coord.md) §2
---
@@ -84,19 +151,37 @@
### 3.1 我依赖的上游就绪标志
- [ ] api-gateway HTTP :8080 启用ai01—— 前端请求入口
- [ ] parent-bff GraphQL :3010 启用ai05—— 数据来源
- [ ] push-gateway WebSocket :8081/ws 启用ai02—— 实时通知
| 上游 | 就绪信号 | 提供方 | 状态 |
| ---- | -------- | ------ | ---- |
| api-gateway | HTTP :8080 启用 + JWT 验签 + `/api/v1/parent/*` 代理 | ai01 | ⏳ |
| parent-bff GraphQL | `POST /graphql` :3010 + currentUser/myChildren/childSummary/childGrades Query | ai05 | ⏳ P4 |
| iam GetChildrenByParent | gRPC 50052 + `iam_student_guardians`I3/ISSUE-047 裁决) | ai06 | ⏳ P3 补全 |
| teacher-portal Shell | MF exposesAppShell + GraphQLProvider + hooks + UI 组件)+ shared singleton | ai13 | ⏳ P2 |
| push-gateway | WebSocket :8081/ws | ai02 | ⏳ P5 |
| msg | gRPC 50056 + NotificationService | ai10 | ⏳ P5 |
| shared-ts / contracts | ApiClient / Logger / Permissions 常量 | coord | ⏳ |
| ui-tokens / ui-components / hooks | 三层令牌 + shadcn + usePermission/useAuth | ai07/ai13 | ⏳ P2 收尾 |
> **P0 阻塞**ISSUE-010iam `GetChildrenByParent` 缺失,多子女场景无法落地。补全前用 mock固定 2 个子女 student-001 + student-002开发。
### 3.2 我的就绪标志(供下游消费)
- [ ] parent-portal dev server :4002 启用
- [ ] MF Remote 可被 AppShell 加载(暴露 ParentApp 模块)
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫)
- [ ] 登录流程可用POST /api/auth/login 获取 JWT 存入 cookie
- [ ] GraphQL 查询可执行currentUser / myChildren / childSummary 返回数据)
- [ ] 数据范围校验生效(前端路由守卫校验 child:id 是否在 myChildren 返回列表中)
- [ ] WebSocket 通知可接收
> 与 [matrix.md](./matrix.md) §8 就绪信号跟踪表对齐
| 信号 | 说明 | 阶段 |
| ---- | ---- | ---- |
| parent-portal dev server :4002 启用 | MF Remote 可被 Shell 加载 | P4-1 |
| MF Remote remoteEntry.js 可加载 | Shell 端 `remotes.parent = parent_app@http://localhost:4002/...` 可解析 | P4-1 |
| 独立壳渲染 | 首页 + 导航 + 路由守卫 | P4-1 |
| 登录流程可用 | `POST /api/v1/iam/login` 获取 JWT 存入 httpOnly cookie | P4-2 |
| GraphQL 查询可执行 | currentUser / myChildren / childSummary 返回数据mock 或真实) | P4-2 |
| 多子女切换可用 | ChildSwitcher + invalidate 流程通过 | P4-3 |
| 数据范围校验生效 | 前端路由守卫校验 childId 是否在 myChildren 返回列表中 | P4-3 |
| Dashboard 可访问 | 家长登录 → 看到 Dashboard含子女卡片 | P4-4 |
| 健康检查通过 | `GET /api/health` + `GET /api/ready` 200 | P4-1 |
| 测试覆盖率达标 | 单元 ≥ 85% + 集成 ≥ 75% | P4-9 |
| Docker 镜像可构建 | `docker build` 成功 | P4-10 |
| WebSocket 通知可接收 | push-gateway WS 事件正确处理 | P5-1 |
---
@@ -111,14 +196,38 @@ parent-portal 是前端,无下游消费方。但对开发体验提供:
### 4.2 我消费的 mock
在真实上游就绪前parent-portal 使用以下 mock
在真实上游就绪前parent-portal 使用以下 mock(由 `NEXT_PUBLIC_API_MOCKING=enabled` 控制)
- **HTTP/GraphQL mock**使用 MSW 拦截所有请求
- POST /api/auth/login → 返回固定 JWT + UserInfoparent 角色
- POST /api/parent/graphql → 根据 operationName 返回对应 mock 响应(与 parent-bff mock 数据一致
- myChildren mock 必须返回固定 2 个id="student-001" + "student-002",与其他 child* 查询的 student_id 一致
- **GraphQL mock**MSW 拦截 `POST /api/v1/parent/graphql`
- 按 operationName 返回对应 mock 响应(与 parent-bff mock 数据一致
- currentUser → 固定家长id="parent-001", name="王家长", roles=["parent"]
- myChildren 固定 2 个子id="student-001" 李同学 + id="student-002" 李妹妹
- childSummary → 固定仪表盘child_avg_score=85.0, child_class_rank=5
- childGrades → 固定 5 个成绩
- childAttendance → 固定 10 条考勤
- childHomework → 固定 3 个作业
- myNotifications → 固定 10 条通知
- 所有 mock 响应定义在 `apps/parent-portal/src/mocks/fixtures/*.json`
- **WebSocket mock**:使用 mock-socket 库
- 连接后每 30 秒推送 1 条 mock 通知
- **JWT mock**使用固定 mock JWT存入 httpOnly cookie
- **环境切换**通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`
- **HTTP mock**MSW 拦截 `POST /api/v1/iam/login` → 返回固定 JWT + UserInfoparent 角色)
- **WebSocket mock**mock-socket 库,连接后每 30 秒推送 1 条 mock 通知
- **JWT mock**:固定 mock JWT存入 httpOnly cookie
- **环境切换**`NEXT_PUBLIC_API_MOCKING=enabled`(开发)/ `disabled`(上游就绪后)
- **数据一致性**myChildren mock 必须返回固定 2 个孩子student-001 + student-002与所有 child* 查询的 student_id 一致(否则前端数据范围校验失败)
---
## §5 待协调事项(指向 objections
以下事项已提请 coord 仲裁,仲裁结果可能影响本契约:
| ISSUE | 影响章节 | 当前处理 |
| ----- | -------- | -------- |
| ISSUE-001REST vs GraphQL | §2.4 | 按 GraphQL 编写(依 ARB-001待 coord 确认 |
| ISSUE-004登录端点 | §2.3 | 暂用 `POST /api/v1/iam/login`,待 coord 确认 |
| ISSUE-006HTTP 端点分类) | §1.2 | 已修正为"无对外 HTTP API" |
| ISSUE-007MF 配置文件名) | §1.6 | 已修正为 `next.config.js` |
| ISSUE-008GraphQL 路径前缀) | §2.4 | 已修正为 `/api/v1/parent/graphql` |
| ISSUE-009switchChild Mutation | §2.4 | 列为待仲裁,标注两种方案 |
| ISSUE-010iam GetChildrenByParent 缺失) | §3.1 | P0 阻塞,用 mock 开发 |
详见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md)。

View File

@@ -1,7 +1,9 @@
# push-gateway 对接契约
> 负责人ai02
> 关联:[matrix.md](./matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](./matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[02-architecture-design.md](../../../services/push-gateway/docs/02-architecture-design.md)
> 版本v22026-07-10对齐总裁裁决 ISSUE-053/055/056/058 + 02 文档 + 现码)
> 变更摘要:① topic 改 `edu.notification.requested`ISSUE-053② /internal/send → /internal/push对齐代码 + 总裁 §4.2);③ 鉴权 mTLS → X-Internal-Token对齐总裁 §7.2);④ 移除 /sse待 ISSUE-001 仲裁);⑤ 新增 /internal/online 端点
---
@@ -9,31 +11,83 @@
### 1.1 gRPC 接口(如有)
无对外 gRPC。push-gateway 是 WebSocket/SSE 推送入口。
**无对外 gRPC**。push-gateway 是 WebSocket 推送入口,仅通过 HTTP /internal/* 接收 msg 服务调用
### 1.2 HTTP 端点(如有)
> 协议选型决策coord 已采纳 P1见 [02 文档 §5.4](../../../services/push-gateway/docs/02-architecture-design.md)HTTP /internal/* + Kafka 双通道,不走 gRPC。理由推送结果需同步返回delivered/onlineHTTP 同步响应更直接;广播走 Kafka 解耦。
| Method | Path | 用途 | 认证 |
| ------ | ------------------- | -------------------------------------- | -------------------------------- |
| GET | /ws | WebSocket 升级端点(实时推送通知) | JWT 必需query param 传 token |
| GET | /sse | SSE 推送端点(备选实时通道) | JWT 必需 |
| POST | /internal/broadcast | 内部广播接口msg 服务触发) | 内网 mTLS |
| POST | /internal/send | 内部单推接口msg 服务触发) | 内网 mTLS |
| GET | /healthz | 健康检查liveness | 公开 |
| GET | /readyz | 就绪检查readiness含 Kafka 连通性) | 公开 |
| GET | /metrics | Prometheus 指标端点 | 公开(内网) |
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 | 请求体 | 响应 |
| ------ | --------------------------- | -------------------------------------- | --------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
| GET | /ws | WebSocket 升级端点(实时推送通知) | JWT RS256query `?token=``Authorization` | — | 升级为 WebSocket 长连接 |
| POST | /internal/push | 内部单推接口msg 服务触发) | `X-Internal-Token` 头 | `{user_id, event, data, ttl?}` | `{success, delivered, online}` |
| POST | /internal/broadcast | 内部广播接口msg 服务触发) | `X-Internal-Token` 头 | `{event, data, filter?}` | `{success, reached}` |
| GET | /internal/online/\<userID\> | 查询用户在线状态 | `X-Internal-Token` 头 | — | `{online: bool, instances: []}` |
| GET | /healthz | 健康检查liveness | 公开 | — | `{status:"ok", service, version, connections}` |
| GET | /readyz | 就绪检查readiness | 公开 | — | `{status, degraded?}`Redis/Kafka 软失败时 degraded:true |
| GET | /metrics | Prometheus 指标端点 | 公开(内网) | — | Prometheus 文本格式 |
**待 ISSUE-001 仲裁项**
- ~~`GET /sse`~~02 文档 §10 建议不支持contract v1 列出但与 02 冲突,待 coord 仲裁移除)
**待 ISSUE-002 仲裁项**
- 内部 API 鉴权头命名:本契约采用 `X-Internal-Token`(对齐总裁裁决 §7.2);现码为 `X-Internal-Key`,待仲裁确认后统一
**`X-Internal-Token` 校验机制**
- 启动时从 `INTERNAL_API_TOKEN` 环境变量加载
- 与 msg 服务共享同一密钥K8s Secret 注入)
- DevMode`DEV_MODE=true`)下跳过校验,便于本地联调
- 缺失/不匹配 → 401 + `PUSH_UNAUTHORIZED`
**响应语义**/internal/push
- `delivered: true`:本实例或跨实例成功投递到至少一个连接
- `online: false`用户离线msg 应走离线推送SMS/邮件)
- `delivered: false, online: true`:投递失败(连接满/异常msg 应重试或落库
### 1.3 GraphQL schema如 BFF
不适用。
不适用。push-gateway 非 BFF。
### 1.4 Kafka 事件发布(如有)
。push-gateway 不发布事件,仅消费事件触发推送。
**无**。push-gateway 不发布任何 Kafka 事件,仅消费事件触发推送。推送结果通过 HTTP /internal/push 同步响应返 msg 服务。
### 1.5 错误码前缀
`PUSH_`如 PUSH_CONNECTION_FAILED、PUSH_CHANNEL_CLOSED、PUSH_AUTH_INVALID
`PUSH_`见 [matrix.md](./matrix.md) §6 错误码前缀矩阵
**错误码清单**(对齐 [02 文档 §6.2](../../../services/push-gateway/docs/02-architecture-design.md)
| 错误码 | HTTP | 触发条件 |
| --------------------------- | ---- | ---------------------- |
| `PUSH_UNAUTHORIZED` | 401 | 缺失/无效 token |
| `PUSH_INVALID_REQUEST` | 400 | JSON 解析失败 |
| `PUSH_INVALID_PAYLOAD` | 400 | event/data 字段缺失 |
| `PUSH_TOO_MANY_CONNECTIONS` | 429 | 单用户连接数超限(>5 |
| `PUSH_INTERNAL_ERROR` | 500 | panic / Redis 不可达 |
### 1.6 WebSocket 应用层消息协议
**服务端 → 客户端**
```json
{
"type": "message",
"event": "notification.created",
"data": { ... },
"seq": 12345,
"timestamp": "2026-07-09T..."
}
```
**客户端 → 服务端**
- WebSocket Ping 控制帧心跳30s 间隔,非文本消息)
- 重连时:`GET /ws?token=&session_id=<id>&last_seq=<n>`P6 实现)
**心跳规则**RFC 6455 控制帧):
- 客户端每 30s 发送 Ping
- 服务端自动回 Ponggorilla/websocket 默认)
- 服务端 `SetReadDeadline(60s)`60s 无消息则关闭连接
---
@@ -41,24 +95,49 @@
### 2.1 gRPC 调用(同步)
无主动 gRPC 调用上游。
**无主动 gRPC 调用上游**
> JWT 公钥通过 HTTP `GET iam/.well-known/jwks.json` 拉取(非 gRPC由 `shared-go/auth/jwks` 实现5 分钟缓存刷新。
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| --------------------------- | --------------------------------- | ---------- | --------------------------------------------------------------------------- |
| edu.msg.notification.events | NotificationEventaction: sent | msg (ai10) | msg 就绪前不订阅,使用本地定时器每 30 秒推送 1 条 mock 通知到所有连接客户端 |
| Topic | Event | 发布方 | mock 策略 |
| ------------------------------ | ------------------------ | ---------- | --------------------------------------------------------------------------- |
| `edu.notification.requested` | NotificationRequested | msg (ai10) | msg 就绪前不订阅,使用本地定时器每 30 秒推送 1 条 mock 通知到所有在线客户端 |
> **topic 命名对齐 ISSUE-053 裁决**[president-final-rulings.md](../president-final-rulings.md) §1.5):禁止抽象名 `edu.*.events`,统一 `edu.<domain>.<aggregate>.<action>` 格式。
> 原 contract v1 写的 `edu.msg.notification.events` 已废弃。
**消费语义**
- Consumer Group`push-gateway`
- 至少一次at-least-once消费失败重试 3 次后入死信队列
- 幂等性:基于 `event_id` Redis SETNX 去重TTL 24h
### 2.3 HTTP 调用(如有)
无。
| 调用方 | Method | Path | 用途 | 时机 |
| -------------- | ------ | --------------------------------- | --------------------------------- | ---------- |
| push-gateway | GET | `iam/.well-known/jwks.json` | 拉取 RS256 公钥校验 WebSocket JWT | iam 就绪后 |
### 2.4 内部接口msg 调用 push-gateway
### 2.4 Redis 协议
| 用途 | 数据结构 | Key 模式 | TTL |
| -------------------- | --------------- | ------------------------------------ | --------------- |
| 在线用户所在实例集合 | SET | `edu:push:online:<userID>` | 60s心跳续期 |
| 单连接元数据 | HASH | `edu:push:session:<userID>:<connID>` | 60s |
| 跨实例定向推送 channel | Pub/Sub channel | `edu:push:channel:user:<userID>` | — |
| 跨实例广播 channel | Pub/Sub channel | `edu:push:channel:broadcast` | — |
| 幂等去重 | SETNX | `edu:push:idempotent:<event_id>` | 24h |
> **Hub 启动重建机制**(对齐 ISSUE-058实例启动时遍历内存连接 SADD + EXPIRE 60s先清空 Redis 中本 instanceID 旧成员避免幽灵成员;实例崩溃 SET 自然过期60s
### 2.5 内部接口msg 调用 push-gateway
| 被调用方 | Method.Path | 用途 | 说明 |
| ------------ | ------------------------ | ------------------ | ---------------------------------------- |
| push-gateway | POST /internal/push | msg 服务单用户推送 | msg 渲染模板后定向推送给目标用户 |
| push-gateway | POST /internal/broadcast | msg 服务批量推送 | msg 收到业务事件后渲染模板,调此接口广播 |
| push-gateway | POST /internal/send | msg 服务单用户推送 | msg 渲染后定向推送给目标用户 |
| push-gateway | GET /internal/online/<userID> | msg 查在线状态 | msg 决定走在线推送还是离线推送SMS/邮件) |
---
@@ -66,18 +145,22 @@
### 3.1 我依赖的上游就绪标志
- [ ] msg gRPC 50056 启用ai10—— 通知事件来源
- [ ] edu.msg.notification.events topic 有事件发布ai10
- [ ] iam gRPC 50052 启用ai06—— WebSocket 连接时 JWT 验签可选push-gateway 可独立验签)
- [ ] **shared-go 包骨架**coord 批次 0.14`packages/shared-go` 含 tracer/logger/jwks/env 4 模块
- [ ] **iam JWT RS256 + JWKS 端点**ai06 批次 1iam gRPC 50052 + `/.well-known/jwks.json` 可访问
- [ ] **msg gRPC + Kafka topic**ai10 批次 4msg gRPC 50056 + `edu.notification.requested` topic 有事件发布
- [x] **Redis 基础设施**coord P1Redis 7.x 可访问 ✅ 已就绪
- [x] **Kafka 基础设施**coord P1Kafka 可访问 ✅ 已就绪
- [ ] **ISSUE-001~007 仲裁**coord[coord.md](../coord.md) 追加 ARB-003+ 仲裁章节
### 3.2 我的就绪标志(供下游消费)
- [ ] push-gateway HTTP :8081 启用(/healthz 返 200
- [ ] /readyz 返 200Kafka 连通性检查通过
- [ ] WebSocket /ws 端点可升级连接JWT 鉴权后建立长连接)
- [ ] SSE /sse 端点可建立 EventStream
- [ ] /internal/broadcast + /internal/send 接收 msg 推送并下发到在线客户端
- [ ] Kafka consumer edu.msg.notification.events 订阅成功
- [ ] push-gateway HTTP :8081 启用(`GET /healthz` 返 200
- [ ] /readyz 返 200Redis/Kafka 软失败检查,`degraded` 字段
- [ ] WebSocket /ws 端点可升级连接JWT RS256 鉴权后建立长连接)
- [ ] /internal/push + /internal/broadcast 接收 msg 推送并下发到在线客户端
- [ ] /internal/online/<userID> 查在线状态可调用
- [ ] Kafka consumer `edu.notification.requested` 订阅成功Consumer Group `push-gateway` lag=0
- [ ] /metrics 暴露 8+ 自定义指标(`push_gateway_*` 系列)
---
@@ -88,14 +171,41 @@
在 push-gateway 真实就绪前,为下游(各前端 portal提供以下 mock
- **WebSocket mock**:前端开发期使用 mock-socket 库模拟 WS 连接
- 连接成功后每 30 秒推送 1 条 mock 通知type="system", title="测试通知"
- **SSE mock**前端使用 EventSource polyfill本地定时推送 mock 事件
- **HTTP mock**/internal/* 接口返回 200 success
- 连接成功后每 30 秒推送 1 条 mock 通知(`type="system"`, `event="notification.created"`, `data={title:"测试通知"}`
- **HTTP mock**/internal/* 接口返回 `{success:true, delivered:true, online:true}`
### 4.2 我消费的 mock
在真实上游就绪前push-gateway 使用以下 mock
- **NotificationEvent mock**msg 就绪前push-gateway 内置定时器每 30 秒生成 1 条 mock NotificationEventaction=sent,推送到所有在线客户端
- **JWT 验签**iam 就绪前使用本地固定 mock RS256 公钥验签 WebSocket 连接的 token
- **Kafka 订阅**msg 就绪前不启动 Kafka consumer使用本地定时器替代
- **NotificationEvent mock**msg 就绪前push-gateway 内置定时器每 30 秒生成 1 条 mock NotificationRequested 事件,推送到所有在线客户端
- **JWT 验签 mock**iam 就绪前使用本地固定 mock RS256 公钥验签 WebSocket 连接的 token(或 DevMode `dev-token` 跳过)
- **Kafka 订阅 mock**msg 就绪前不启动 Kafka consumer使用本地定时器替代
- **JWKS fetcher mock**iam 就绪前 shared-go/jwks 返回硬编码公钥
---
## §5 与 02 文档、现码、总裁裁决的对齐说明
| 维度 | 现 code | 02 文档 | 总裁裁决 | 本 contract 采用 | 对齐任务 |
| ---- | ------- | ------- | -------- | ---------------- | -------- |
| 内部端点路径 | `/internal/push` | `/internal/push` | §4.2 as-is 采纳 02 §4.2 | `/internal/push` | ✅ 已对齐v1 写 `/internal/send` 已修正) |
| 鉴权头名 | `X-Internal-Key` | `X-Internal-Token` | §7.2 "X-Internal-Token 重命名" | `X-Internal-Token` | 待 ISSUE-002 仲裁后改代码 |
| 鉴权环境变量 | `INTERNAL_API_KEY` | `INTERNAL_API_TOKEN` | §7.2 | `INTERNAL_API_TOKEN` | 待 ISSUE-002 仲裁后改代码 |
| 鉴权机制 | 共享密钥 | 共享密钥 | — | 共享密钥v1 写 mTLS 已废弃) | ✅ 已对齐 |
| Kafka topic | —(未实现) | `edu.notification.events` | §1.5 ISSUE-053 → `edu.notification.requested` | `edu.notification.requested` | ✅ 已对齐 ISSUE-053 |
| /readyz Redis 失败策略 | —(仅返连接数) | 返 503 硬失败 | §4.3 ISSUE-058 "仅告警不阻塞" | 软失败 200 + `degraded:true` | 待 ISSUE-006 仲裁最终策略 |
| /readyz Kafka 失败策略 | — | 未描述 | §3.3 ISSUE-055 软失败 | 软失败 200 + `degraded:true` | ✅ 已对齐 ISSUE-055 |
| /sse 端点 | 无 | §10 建议不支持 | — | 不提供(待 ISSUE-001 仲裁) | 待 ISSUE-001 仲裁 |
| 容量目标 | — | 10w+ | — | 10w+ | 待 ISSUE-003 仲裁更新 modules/README |
| 错误码前缀 | 无前缀 | `PUSH_*` | — | `PUSH_*` | 待 P5 实现时统一 |
| 心跳协议 | 文本 ping/pong | RFC 6455 控制帧 | — | RFC 6455 控制帧 | 待 P5 任务 4.3 重构 |
---
## §6 变更历史
| 版本 | 日期 | 变更内容 | 变更依据 |
| ---- | ---------- | ------------------------------------------------------------------------ | ------------------------------------- |
| v1 | 2026-07-09 | 初始版本coord 生成) | — |
| v2 | 2026-07-10 | topic 改 `edu.notification.requested`/internal/send → /internal/push鉴权 mTLS → X-Internal-Token移除 /sse待仲裁新增 /internal/online新增 §5 对齐表 | ISSUE-053/055/056/058 + 总裁 §4.2/§7.2 |

View File

@@ -1,50 +1,310 @@
# student-bff 对接契约
> 负责人ai04
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[content.proto](../../../packages/shared-proto/proto/content.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)
> 阶段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
> 关联文档:
>
> - [matrix.md](../matrix.md)
> - [coord-final-decisions.md](../../coord-final-decisions.md) §2 BFF 专项裁决 B1-B8
> - [president-final-rulings.md](../../president-final-rulings.md) §2.2 GraphQL schema 仲裁机制
> - [iam.proto](../../../../packages/shared-proto/proto/iam.proto)
> - [core_edu.proto](../../../../packages/shared-proto/proto/core_edu.proto)
> - [content.proto](../../../../packages/shared-proto/proto/content.proto)
> - [analytics.proto](../../../../packages/shared-proto/proto/analytics.proto)
> - [msg.proto](../../../../packages/shared-proto/proto/msg.proto)
---
## §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 gRPC 接口(如有)
### 1.1 服务基础信息
无对外 gRPC。student-bff 是 GraphQL 聚合层。
| 项目 | 值 |
| ------------- | --------------------------------------------- |
| 服务名 | student-bff |
| 服务类型 | BFF 聚合层(无 DB无 Outbox |
| HTTP 端口 | 3009 |
| gRPC 端口 | 不暴露BFF 仅对下游走 gRPC不对外提供 gRPC |
| 路由前缀 | `/student/*`api-gateway 透传) |
| 部署目录 | `services/student-bff/` |
| 角色要求 | `student`JWT 必需 + student 角色) |
### 1.2 HTTP 端点(如有)
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 |
| ------ | -------- | ----------------------------------------- | ----------------------- |
| POST | /graphql | 学生 BFF GraphQL 端点 | JWT 必需 + student 角色 |
| GET | /graphql | GraphQL Playground开发环境 | 开发环境公开 |
| GET | /healthz | 健康检查liveness | 公开 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性) | 公开 |
| 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 |
### 1.3 GraphQL schema如 BFF
> **B1 裁决**P2 起直接 GraphQLGraphQL Yoga + DataLoader禁止 REST → GraphQL 渐进式过渡。
> **B2 裁决**:对下游通信首次实现即 gRPC禁止 HTTP fetch → gRPC 渐进式过渡。
> **F9 裁决**:前端 student-portal 用 urql/apollo 消费 GraphQL。
GraphQL schema 文件路径:`apps/student-bff/src/schema/*.graphql`(端口 :3009
### 1.3 GraphQL schema(核心契约
核心 Query / Mutation 域:
#### 1.3.1 schema 存放路径
- **auth**currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- **myClasses**:我的班级(聚合 core-edu.ClassService.GetClass + ListStudentsByClass
- **myExams**:我的考试列表(聚合 core-edu.ExamService.ListExamsByClass
- **myHomework**:我的作业(聚合 core-edu.HomeworkService.ListHomeworkByClass + SubmitHomework
- **myGrades**:我的成绩(聚合 core-edu.GradeService.ListGradesByStudent
- **myAttendance**:我的考勤(聚合 core-edu.AttendanceService.ListAttendanceByStudent
- **content**textbooks / chapters / learningPath聚合 content.KnowledgeGraphService.GetLearningPath
- **dashboard**studentDashboard聚合 data-ana.AnalyticsService.GetStudentDashboard
- **weakness**myWeakness聚合 data-ana.AnalyticsService.GetStudentWeakness
- **trend**myTrend聚合 data-ana.AnalyticsService.GetLearningTrend
- **notifications**myNotifications / markAsRead聚合 msg.NotificationService
**强制路径**`packages/shared-ts/contracts/graphql/student-bff.schema.graphql`
### 1.4 Kafka 事件发布(如有)
> 依据 president-final-rulings §2.2.13 个 BFF schema 统一存放于 `packages/shared-ts/contracts/graphql/`,由各 BFF 负责 AI 起草、coord 仲裁。student-bff schema 不放在 `services/student-bff/src/` 下,避免前端 AI 难以发现。
无。student-bff 不发布事件,仅做 gRPC 聚合。
#### 1.3.2 起草与仲裁流程
### 1.5 错误码前缀
依据 president-final-rulings §2.2.6
`BFF_STUDENT_`(如 BFF_STUDENT_UPSTREAM_UNAVAILABLE、BFF_STUDENT_AGGREGATION_FAILED、BFF_STUDENT_FORBIDDEN
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-first`schema.graphql` 文件),配合 `graphql-codegen` 生成 TS 类型
#### 1.3.3 schema 设计规范
依据 president-final-rulings §2.2.5
| 规范项 | 规则 |
| -------------- | ----------------------------------------------------------------- |
| 命名风格 | Query/Mutation 用 camelCase`studentDashboard` / `myClasses` |
| 分页规范 | **Relay Cursor Connections**`{ edges, pageInfo, totalCount }` |
| 错误响应 | GraphQL errors 数组 + `extensions.code` + `extensions.traceId` |
| 权限点标注 | 注释形式 `# @permission: DASHBOARD_VIEW`F7 规范) |
| DataScope 标注 | 注释形式 `# @dataScope: OWN`(学生数据隔离 SELF |
| 字段命名 | Type 用 PascalCase字段用 camelCase枚举用 UPPER_SNAKE_CASE |
| 非空与可选 | 必填字段用 `!`,可空字段不标 `!`(避免破坏性变更) |
#### 1.3.4 核心 Query / Mutation 域
> 完整 SDL 定义见 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`ai04 起草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 字段:
```json
{
"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:当下游服务不可用但需返回部分数据时,采用**方案 B**success=true + error=null + data 内 degraded 字段):
```json
{
"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 裁决:
```typescript
// 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 后期接入真实校验后正式上线。
---
@@ -52,79 +312,288 @@ GraphQL schema 文件路径:`apps/student-bff/src/schema/*.graphql`(端口 :
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | ----------------------------------------- | ---------------- | ------------------------------------------- |
| iam (ai06) | IamService.GetUserInfo | 获取当前学生信息 | iam 就绪前返回固定 UserInfostudent 角色) |
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | iam 就绪前返回学生权限集 |
| iam (ai06) | IamService.GetViewports | 学生导航菜单 | iam 就绪前返回固定视口列表 |
| core-edu (ai08) | ClassService.GetClass | 我的班级详情 | core-edu 就绪前返回固定 ClassInfo |
| core-edu (ai08) | ClassService.ListStudentsByClass | 班级同学名单 | core-edu 就绪前返回固定 30 个 StudentInfo |
| core-edu (ai08) | ExamService.ListExamsByClass | 我的考试 | core-edu 就绪前返回固定 2 个 Exam |
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | 我的作业 | core-edu 就绪前返回固定 3 个 Homework |
| core-edu (ai08) | HomeworkService.SubmitHomework | 提交作业 | core-edu 就绪前返回 success=true |
| core-edu (ai08) | GradeService.ListGradesByStudent | 我的成绩 | core-edu 就绪前返回固定 5Grade |
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | 我的考勤 | core-edu 就绪前返回固定 10 条 Attendance |
| content (ai09) | TextbookService.ListTextbooks | 教材列表 | content 就绪前返回固定 5 个教材 |
| content (ai09) | ChapterService.ListChapters | 章节列表 | content 就绪前返回固定章节树 |
| content (ai09) | KnowledgeGraphService.GetLearningPath | 学习路径 | content 就绪前返回固定 8 个知识点推荐顺序 |
| data-ana (ai11) | AnalyticsService.GetStudentDashboard | 学生仪表盘 | data-ana 就绪前返回固定仪表盘 |
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 我的薄弱点 | data-ana 就绪前返回固定 3 个 weak_points |
| data-ana (ai11) | AnalyticsService.GetLearningTrend | 学习趋势 | data-ana 就绪前返回固定趋势数据 |
| msg (ai10) | NotificationService.ListNotifications | 学生通知 | msg 就绪前返回固定 10 条通知 |
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | msg 就绪前返回 success=true |
依据 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 就绪前返回固定 2Exam |
| 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 事件订阅(异步)
无。student-bff 不订阅 Kafka 事件,仅做同步 gRPC 聚合。
依据 B7 裁决:
### 2.3 HTTP 调用(如有
- **P3-P4 阶段****不订阅 Kafka**(仅同步 gRPC 聚合
- **P5 阶段**push-gateway 落地后,评估是否订阅 Kafka`edu.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-bff**DownstreamClient 作为 BFF 模式 v2 标准抽象,回写到 teacher-bff3 个 BFFteacher-bff / student-bff / parent-bff统一使用
2. **抽象位置**`packages/shared-ts/src/bff/downstream-client.ts`coord 维护)
3. **核心能力**
```typescript
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
}
```
4. **student-bff 使用方式**
```typescript
// 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 },
});
}
}
```
5. **回写义务**ai04 在 P3 实现时,将 DownstreamClient 抽象回写到 teacher-bff替换 teacher-bff 现有的散落 fetch 调用),保证 3 个 BFF 统一。
---
## §3 就绪信号
## §3 跨阶段扩展例外规则§2.3 裁决)
### 3.1 我依赖的上游就绪标志
依据 president-final-rulings §2.3
- [ ] iam gRPC 50052 启用ai06
- [ ] core-edu gRPC 50053 启用ai08
- [ ] content gRPC 50054 启用ai09
- [ ] data-ana gRPC 50055 启用ai11
- [ ] msg gRPC 50056 启用ai10
### 3.1 允许扩展(无需 coord 仲裁
### 3.2 我的就绪标志(供下游消费
1. 新增下游 gRPC 调用(新增 RPC 方法到 DownstreamClient
2. 新增下游配置gRPC endpoint 配置)
3. 新增 /readyz 探针(按阶段启用,见 §4
4. Dashboard Query Resolver 内部数据源扩展null 字段 → 真实数据)
5. AuthorizationGuard 内部实现替换DEV_MODE 放行 → 真实 gRPC 校验 + Redis 缓存)
- [ ] student-bff GraphQL :3009 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 5 个下游 gRPC 连通性检查)
- [ ] GraphQL schema 可内省POST /graphql 返回 schema
- [ ] 核心 Query 可执行currentUser / myClasses / studentDashboard / myGrades
- [ ] 核心 Mutation 可执行submitHomework / markAsRead
### 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 Mock 策略
## §4 /readyz 探针按阶段扩展规则§2.4 裁决)
### 4.1 我提供的 mock
依据 president-final-rulings §2.4 + G2 裁决:
在 student-bff 真实就绪前为下游student-portal提供以下 mock
### 4.1 探针列表(按阶段)
- **GraphQL mock**:使用 Apollo Server mockProviders 或 MSW 拦截 POST /graphql
- currentUser 返回固定学生id="student-001", name="李同学", roles=["student"]
- myClasses 返回固定 1 个班级
- studentDashboard 返回固定仪表盘avg_score=85.0, class_rank=5
- myGrades 返回固定 5 个成绩
- myHomework 返回固定 3 个作业1 个待提交)
- myNotifications 返回固定 10 条通知
student-bff 无 DB探针仅检查 Redis + 下游 gRPC 可达性:
### 4.2 我消费的 mock
| 阶段 | 探针列表 | 数量 |
| ---- | ----------------------------------------------------- | ---- |
| 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 过滤):
```typescript
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-yoga``mocking` 配置)或 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 策略列):
- **iam mock**:固定 UserInfo + 学生权限 + 固定视口
- **core-edu mock**:固定班级/同学/考试/作业/成绩/考勤
- **content mock**:固定教材/章节/学习路径
- **data-ana mock**:固定仪表盘/薄弱点/趋势
- **msg mock**:固定通知列表 + MarkAsRead success
| 上游 | mock 实现 |
| -------- | ------------------------------------------------------------ |
| iam | 固定 UserInfo + 学生权限集 + 固定视口 + DataScope=SELF |
| core-edu | 固定班级/同学/考试/作业/成绩/考勤 |
| content | 固定教材/章节/学习路径P4 启用前) |
| data-ana | 固定仪表盘/薄弱点/趋势P4 启用前) |
| msg | 固定通知列表 + GetUnreadCount + MarkAsRead successP5 启用前) |
> 所有上游 mock 通过 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用
> **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. **仅缓存 Query**Mutation 不缓存
2. **缓存粒度**:按 Query + userId 维度缓存(学生数据隔离 SELF
3. **降级策略**Redis 不可用时,直接走 gRPC 调用(不缓存),返回数据 + `degraded: true`(标记缓存降级)
4. **缓存击穿防护**:使用 `ioredis``GETSET` 或单飞模式(同一 key 并发请求只发一个 gRPC 调用)
---
## §8 变更记录
| 日期 | 版本 | 变更 | 负责人 |
| ---------- | ---- | -------------------------------------------------------------------- | ------ |
| 2026-07-09 | v1 | 初始创建 | ai04 |
| 2026-07-10 | v2 | 对齐 coord B1-B8 + president §2.2-2.9 裁决schema 路径/错误响应/越权防御/DownstreamClient/跨阶段扩展/readyz 探针/降级模式/缓存策略) | ai04 |

View File

@@ -1,7 +1,9 @@
# student-portal 对接契约
> 负责人ai14
> 关联:[matrix.md](./matrix.md)
> 关联:[matrix.md](./matrix.md)、[coord.md §1 ARB-001](../coord.md)、[coord.md §2 ARB-002](../coord.md)、[student-bff_contract.md](./student-bff_contract.md)、[teacher-portal_contract.md](./teacher-portal_contract.md)
> 版本v2ai14 接管审计与补全版2026-07-10
> 修订摘要GraphQL endpoint 路径修正为 `/api/v1/student/graphql`(对齐 matrix.md §5+ 补全 24 个 GraphQL query/mutation对齐 02 §4.2+ 补充 ARB-002 MF Shell 暴露清单 + 补充就绪信号明细
---
@@ -18,32 +20,54 @@
| GET | / | 学生门户首页 | JWT 必需(前端路由守卫) |
| GET | /my-classes | 我的班级 | JWT 必需 |
| GET | /my-exams | 我的考试 | JWT 必需 |
| GET | /my-exams/[id]/take | 考试作答页 | JWT 必需 |
| GET | /my-exams/[id]/result | 考试结果页 | JWT 必需 |
| GET | /my-homework | 我的作业 | JWT 必需 |
| GET | /my-homework/[id]/submit | 作业提交页 | JWT 必需 |
| GET | /my-grades | 我的成绩 | JWT 必需 |
| GET | /my-attendance | 我的考勤 | JWT 必需 |
| GET | /learning-path | 学习路径 | JWT 必需 |
| GET | /dashboard | 学生仪表盘 | JWT 必需 |
| GET | /dashboard/weakness | 学情诊断 | JWT 必需 |
| GET | /dashboard/trend | 学习趋势 | JWT 必需 |
| GET | /textbooks | 教材列表 | JWT 必需 |
| GET | /textbooks/[id]/chapters | 章节列表 | JWT 必需 |
| GET | /notifications | 通知中心 | JWT 必需 |
| GET | /ai-tutor | AI 辅助答疑P5 可选) | JWT 必需 |
> **路由前缀**:无 `/student/` 前缀student-portal 作为 MF Remote由 Shell 路由 `/student/*` 加载,内部路由无前缀)。
### 1.3 GraphQL schema如 BFF
不适用。student-portal 消费 student-bff GraphQL自身不提供 schema。
> **消费的 schema 文件**`packages/shared-ts/contracts/graphql/student-bff.graphql`(待 ISSUE-014-02 仲裁后由 ai04 创建,对齐 ARB-001 §1.3 集中管理原则)
### 1.4 Kafka 事件发布(如有)
无。
### 1.5 错误码前缀
无(前端不定义错误码前缀,透传 BFF 错误码)。
无(前端不定义错误码前缀,透传 BFF 错误码 `BFF_STUDENT_*`,见 [matrix.md §6](../matrix.md))。
### 1.6 微前端架构(补充
### 1.6 微前端架构(MF RemoteARB-002 对齐
| 角色 | 说明 |
| ---------------------- | ----------------------------------------------------------------- |
| MF Remote | 学生门户是微前端远程模块,由 teacher-portal AppShell 或独立壳加载 |
| 暴露的 remote 模块 | StudentApp学生端完整应用、shared 学生端组件 |
| module federation 配置 | `apps/student-portal/module-federation.config.ts` |
| 角色 | 说明 |
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
| MF Remote | student-portal 是微前端远程模块P3 首个 RemoteARB-002 §2.3,由 teacher-portal AppShell 加载 |
| 暴露的 remote 模块 | `./StudentApp`(学生端完整应用) |
| module federation 配置 | `apps/student-portal/next.config.js`NextFederationPlugin |
| Shell 暴露清单(复用) | AppShell / GraphQLProvider / useAuth / usePermission / useGraphQLClient / ErrorBoundary / Loading / Empty / RequirePermissionARB-002 §2.2 |
| shared singleton | react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks / @edu/contracts / @edu/shared-tsARB-002 §2.2 |
| feature flag | `NEXT_PUBLIC_MF_ENABLED`P2=false 独立壳P3=true 接入 Shell |
| 登录页 | 不实现,未登录跳转 `http://localhost:4000/login?redirect=student`ARB-002 §2.3 登录由 Shell 独占) |
### 1.7 WebSocket 消费push-gateway
| 端点 | 用途 | 认证 | 事件类型 |
| --------------------- | ----------------------- | ---- | ------------------------------------------------------------------------ |
| `ws://push-gateway:8081/ws` | 实时通知推送 | JWT | 作业通知 / 考试通知 / 成绩通知 / 系统通知 / 考试延长(待 ISSUE-014-06 仲裁) / 考试强制提交(待 ISSUE-014-06 仲裁) |
---
@@ -59,27 +83,59 @@
### 2.3 HTTP 调用(如有)
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ------------------------- | --------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/student/graphql | 学生 GraphQL 查询(经网关代理到 student-bff | api-gateway/student-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 学生登录 | api-gateway 就绪前使用 MSW 返回固定 JWT |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ------------------------------ | --------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/v1/student/graphql | 学生 GraphQL 查询(经网关代理到 student-bff :3009/graphql | api-gateway/student-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 学生登录Shell 独占student-portal 不直接调用,仅跳转) | api-gateway 就绪前由 Shell 处理 |
| api-gateway (ai01) | POST /api/v1/student/upload | 作业附件上传(待 ISSUE-014-05 仲裁) | 待仲裁后实现 |
| push-gateway (ai02) | GET /wsWebSocket | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
> **路径说明**ISSUE-014-01
> - `POST /api/v1/student/graphql` 经 api-gateway 反向代理到 student-bff `POST /graphql`:3009
> - 路径前缀 `/api/v1/student/*` 与 matrix.md §5、teacher-portal `/api/v1/teacher/*` 保持命名一致性
> - 由 ai01api-gateway确认路由配置`/api/v1/student/*` → `student-bff:3009/*`
### 2.4 GraphQL 查询域(经 api-gateway 代理到 student-bff
| Query/Mutation | 用途 | mock 策略 |
| ----------------------------------- | --------------- | -------------------------------------------------- |
| currentUser | 当前学生信息 | MSW 返回固定学生 |
| myClasses | 我的班级 | MSW 返回固定 1 个班级 |
| myExams | 我的考试 | MSW 返回固定 2 个考试 |
| myHomework / submitHomework | 我的作业 + 提交 | MSW 返回固定作业 + submitHomework success |
| myGrades | 我的成绩 | MSW 返回固定 5 个成绩 |
| myAttendance | 我的考勤 | MSW 返回固定 10 条考勤 |
| textbooks / chapters / learningPath | 学习内容 | MSW 返回固定内容 + 学习路径 |
| studentDashboard | 学生仪表盘 | MSW 返回固定仪表盘avg_score=85.0, class_rank=5 |
| myWeakness | 我的薄弱点 | MSW 返回固定 3 个 weak_points |
| myTrend | 学习趋势 | MSW 返回固定趋势数据 |
| myNotifications / markAsRead | 通知中心 | MSW 返回固定通知 |
> 对齐 [02-architecture-design.md v2 §4.2](../../../apps/student-portal/docs/02-architecture-design.md) GraphQL 操作清单(共 24 个)
#### 2.4.1 Query16 个)
| Query | 用途 | mock 策略 |
| ------------------------------ | --------------------- | -------------------------------------------------- |
| currentUser | 当前学生信息 | MSW 返回固定学生id=student-001, roles=[student]|
| myClasses | 我的班级 | MSW 返回固定 1 个班级 |
| myExams | 我的考试列表 | MSW 返回固定 2 个考试 |
| examDetail(id: ID!) | 考试详情(含题目) | MSW 返回固定考试 + 5 道题 |
| myHomework | 我的作业列表 | MSW 返回固定 3 个作业1 个待提交) |
| homeworkDetail(id: ID!) | 作业详情 | MSW 返回固定作业 + 题目 |
| myGrades | 我的成绩 | MSW 返回固定 5 个成绩 |
| myAttendance | 我的考勤 | MSW 返回固定 10 条考勤 |
| textbooks | 教材列表 | MSW 返回固定 5 个教材 |
| chapters(textbookId: ID!) | 章节列表 | MSW 返回固定章节树 |
| learningPath | 学习路径 | MSW 返回固定 8 个知识点推荐顺序 |
| studentDashboard | 学生仪表盘 | MSW 返回固定仪表盘avg_score=85.0, class_rank=5 |
| myWeakness | 我的薄弱点 | MSW 返回固定 3 个 weak_points |
| myTrend | 学习趋势 | MSW 返回固定趋势数据 |
| myNotifications(first: Int, after: String) | 通知列表 | MSW 返回固定 10 条通知 |
| serverTime | 服务器时间(考试倒计时对齐) | MSW 返回当前时间 + 100ms 延迟 |
#### 2.4.2 Mutation8 个)
| Mutation | 用途 | mock 策略 |
| --------------------------------------- | ------------------- | -------------------------------------- |
| submitHomework(input: SubmitHomeworkInput!) | 提交作业 | MSW 返回 success=true |
| submitExam(input: SubmitExamInput!) | 提交考试作答 | MSW 返回 success=true + submittedAt |
| saveExamDraft(input: SaveExamDraftInput!) | 保存考试草稿 | MSW 返回 success=true |
| markAsRead(notificationId: ID!) | 标记通知已读 | MSW 返回 success=true |
| markAllAsRead | 全部标记已读 | MSW 返回 success=true |
| recordExamViolation(input: RecordExamViolationInput!) | 记录防作弊违规(待 ISSUE-014-03 仲裁) | MSW 返回 success=true |
| recordPasteEvent(input: RecordPasteEventInput!) | 记录粘贴事件(待 ISSUE-014-04 仲裁) | MSW 返回 success=true |
| updateNotificationPreference(input: UpdateNotificationPreferenceInput!) | 更新通知偏好P5 | MSW 返回 success=true |
> **DataScope L0 强制执行**ISSUE-014-07
> - 所有学生端 Query 不传 `studentId` 参数,由 student-bff 在 Resolver 层从 JWT `x-user-id` 提取并强制过滤
> - 前端无法绕过 L0 边界(前端篡改 JWT 无效gRPC 层会重新校验)
---
@@ -87,18 +143,31 @@
### 3.1 我依赖的上游就绪标志
- [ ] api-gateway HTTP :8080 启用ai01—— 前端请求入口
- [ ] student-bff GraphQL :3009 启用ai04—— 数据来源
- [ ] push-gateway WebSocket :8081/ws 启用ai02—— 实时通知
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
| --------------------------------- | ------------------------------------------------------------------------- | -------- | ------------ |
| packages 骨架ai13 批次 0.15 | ui-tokens / ui-components / hooks 可 import | P2 启动 | ✅ 已就绪 |
| teacher-portal MF Shellai13 P2| exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
| api-gateway HTTP :8080ai01 P3 | `/api/v1/student/*` 反向代理 student-bff 可用 | P3 启动 | ⏳ 待 ai01 P3 |
| student-bff GraphQLai04 P3 | `POST /graphql` :3009 + 核心 Query/Mutation 可执行 | P3 启动 | ⏳ 待 ai04 P3 |
| student-bff GraphQL schema | `packages/shared-ts/contracts/graphql/student-bff.graphql` 创建(待 ISSUE-014-02 仲裁) | P3 启动 | ⏳ 待仲裁 |
| core-edu gRPC 50053ai08 P3 | ExamService/HomeworkService/GradeService/AttendanceService/ClassService | P3 启动 | ⏳ 待 ai08 P3 |
| iam gRPC 50052ai06 P2 | GetUserInfo + GetEffectivePermissions + GetViewports | P3 启动 | ⏳ 待 ai06 P2 |
| content gRPC 50054ai09 P4 | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
| data-ana gRPC 50055ai11 P4 | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| msg gRPC 50056ai10 P5 | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
| ai 服务 gRPC 50057ai12 P5可选 | AiService.ChatSSE 流式) | P5 启动 | ⏳ 待 ai12 P5 |
### 3.2 我的就绪标志(供下游消费)
- [ ] student-portal dev server :4001 启用
- [ ] MF Remote 可被 AppShell 加载(暴露 StudentApp 模块)
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫)
- [ ] 登录流程可用(POST /api/auth/login 获取 JWT 存入 cookie
- [ ] GraphQL 查询可执行currentUser / myClasses / studentDashboard 返回数据)
- [ ] WebSocket 通知可接收
- [ ] student-portal dev server :4001 启用`pnpm dev` 可访问)
- [ ] MF Remote 可被 AppShell 加载(暴露 `./StudentApp` 模块teacher-portal Shell 可加载
- [ ] 独立壳渲染(`NEXT_PUBLIC_MF_ENABLED=false`首页 + 导航 + 路由守卫独立可用
- [ ] 登录流程可用(未登录跳转 Shell `/login`,登录后回跳 student
- [ ] GraphQL 查询可执行currentUser / studentDashboard / myClasses 返回数据)
- [ ] 考试作答链路通(进入作答 → 自动保存 → 提交 → 跳转结果页)
- [ ] WebSocket 通知可接收(通知中心实时更新)
- [ ] lint + typecheck 零错误
---
@@ -108,18 +177,78 @@
student-portal 是前端,无下游消费方。但对开发体验提供:
- **Storybook**:各组件独立 story
- **Storybook**:各组件独立 story`apps/student-portal/.storybook/`
- **MSW handlers**`apps/student-portal/src/mocks/handlers.ts`,拦截所有 GraphQL/HTTP 请求
- **Mock fixtures**`apps/student-portal/src/mocks/fixtures/*.json`,与 student-bff mock 数据一致
### 4.2 我消费的 mock
在真实上游就绪前student-portal 使用以下 mock
- **HTTP/GraphQL mock**:使用 MSW 拦截所有请求
- POST /api/auth/login → 返回固定 JWT + UserInfostudent 角色)
- POST /api/student/graphql → 根据 operationName 返回对应 mock 响应(与 student-bff mock 数据一致
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
- **WebSocket mock**:使用 mock-socket 库
- 连接后每 30 秒推送 1 条 mock 通知
- **JWT mock**:使用固定 mock JWT存入 httpOnly cookie
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`
#### 4.2.1 HTTP / GraphQL mockMSW
- `POST /api/v1/student/graphql` operationName 返回对应 mock 响应(见 §2.4
- `POST /api/auth/login` → 返回固定 JWT + UserInfostudent 角色)由 Shell 处理
- `POST /api/v1/student/upload` → 返回固定 signed URL待 ISSUE-014-05 仲裁后实现)
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
#### 4.2.2 WebSocket mockmock-socket
- 连接 `ws://localhost:8081/ws` 后每 30 秒推送 1 条 mock 通知
- 通知类型轮询:作业通知 / 考试通知 / 成绩通知 / 系统通知
- 支持模拟考试延长事件(待 ISSUE-014-06 仲裁后实现)
#### 4.2.3 JWT mock
- 使用固定 mock JWT`eyJhbGciOiJSUzI1NiIs...`payload 含 `sub=student-001, roles=[student], dataScope=SELF`
- 存入 httpOnly cookie由 Shell 登录流程设置)
#### 4.2.4 环境切换
- 通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制
- 上游就绪后设为 `disabled`,切换到真实请求
- MF 切换:`NEXT_PUBLIC_MF_ENABLED=false`P2 独立壳)→ `true`P3 接入 Shell
#### 4.2.5 IDB 草稿恢复(真实 idb-keyval
- 考试作答草稿使用真实 `idb-keyval` 存储(前端可独立测试断网恢复逻辑)
- 不需要 mockIDB 在浏览器原生支持
---
## §5 与 student-bff 契约对齐核查
> 参考 [student-bff_contract.md](./student-bff_contract.md)ai04 维护)
| 对齐项 | student-portal 期望 | student-bff 提供 | 状态 |
| ----------------------- | ---------------------------------------------------- | ----------------------------------------------------------------- | ---- |
| GraphQL endpoint | `POST /api/v1/student/graphql`(经 api-gateway 代理)| `POST /graphql` :3009 | ✅ 对齐api-gateway 代理) |
| GraphQL schema 文件 | `packages/shared-ts/contracts/graphql/student-bff.graphql` | `apps/student-bff/src/schema/*.graphql`(待 ISSUE-014-02 仲裁) | ⏳ 待仲裁 |
| Query 域16 个) | 见 §2.4.1 | 见 student-bff §1.3auth/myClasses/myExams/myHomework/myGrades/myAttendance/content/dashboard/weakness/trend/notifications | ⏳ 待 ai04 确认 serverTime/examDetail/homeworkDetail |
| Mutation 域8 个) | 见 §2.4.2 | student-bff §1.3 仅列 submitHomework + markAsRead | ⏳ 待 ai04 补全 submitExam/saveExamDraft/recordExamViolation/recordPasteEvent/updateNotificationPreference |
| 错误码前缀 | 透传 `BFF_STUDENT_*` | `BFF_STUDENT_`student-bff §1.5 | ✅ 对齐 |
| ActionState 信封 | success/errors/data + extensions.degraded | 待 ai04 实现ARB-001 §1.3 原则) | ⏳ 待 ai04 |
| DataLoader 防 N+1 | 依赖 student-bff 实现 | 待 ai04 实现ARB-001 §1.3 原则) | ⏳ 待 ai04 |
| DataScope L0 强制执行 | student-bff Resolver 层从 JWT 提取 studentId | 待 ISSUE-014-07 仲裁 | ⏳ 待仲裁 |
---
## §6 异议引用
> 详见 [objections/student-portal_issue.md](../objections/student-portal_issue.md)
| 编号 | 标题 | 影响 |
| ------------ | -------------------------------------------------------- | --------------------------------------------- |
| ISSUE-014-01 | GraphQL endpoint 路径不一致 | 影响 §2.3 路径配置 |
| ISSUE-014-02 | student-bff GraphQL schema 存放位置不一致 | 影响 §1.3 schema 文件路径 |
| ISSUE-014-03 | 考试作答页全屏策略与防作弊检测边界 | 影响 §2.4.2 recordExamViolation mutation 实现 |
| ISSUE-014-04 | 主观题粘贴策略(防作弊 vs 学生体验) | 影响 §2.4.2 recordPasteEvent mutation 实现 |
| ISSUE-014-05 | 作业附件上传协议GraphQL mutation vs REST multipart | 影响 §2.3 `/api/v1/student/upload` 端点 |
| ISSUE-014-06 | 考试延长/题目重排等实时事件命名未确认 | 影响 §1.7 WebSocket 事件类型 |
| ISSUE-014-07 | 学生端 DataScope L0 边界的强制执行层 | 影响 §2.4 GraphQL 查询域参数设计 |
---
**AI Agent**: ai14student-portal
**Branch**: feat-review-student-portal-docs-9yN6Av
**Coordinator**: coord-ai

View File

@@ -1,7 +1,8 @@
# teacher-bff 对接契约
> 负责人ai03
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../packages/shared-proto/proto/core_edu.proto)、[content.proto](../../../packages/shared-proto/proto/content.proto)、[analytics.proto](../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[ai.proto](../../../packages/shared-proto/proto/ai.proto)
> 版本v22026-07-10对齐 president §2.7/§2.17/§5.1 + coord B5/B8 + port-allocation §5
> 关联:[matrix.md](../matrix.md)、[iam.proto](../../../../packages/shared-proto/proto/iam.proto)、[core_edu.proto](../../../../packages/shared-proto/proto/core_edu.proto)、[content.proto](../../../../packages/shared-proto/proto/content.proto)、[analytics.proto](../../../../packages/shared-proto/proto/analytics.proto)、[msg.proto](../../../../packages/shared-proto/proto/msg.proto)、[ai.proto](../../../../packages/shared-proto/proto/ai.proto)、[coord-final-decisions.md §2 B1-B8](../../coord-final-decisions.md)、[president-final-rulings.md §2.7/§2.17/§5.1](../../president-final-rulings.md)
---
@@ -9,42 +10,77 @@
### 1.1 gRPC 接口(如有)
无对外 gRPC。teacher-bff 是 GraphQL 聚合层。
无对外 gRPC。teacher-bff 是 GraphQL 聚合层B1 裁决P2 起直接 GraphQL
### 1.2 HTTP 端点(如有)
| Method | Path | 用途 | 认证 |
| ------ | -------- | ----------------------------------------- | ----------------------- |
| POST | /graphql | 教师 BFF GraphQL 端点 | JWT 必需 + teacher 角色 |
| GET | /graphql | GraphQL Playground开发环境 | 开发环境公开 |
| GET | /healthz | 健康检查liveness | 公开 |
| GET | /readyz | 就绪检查readiness含下游 gRPC 连通性 | 公开 |
| Method | Path | 用途 | 认证 | 裁决依据 |
| ------ | -------- | ----------------------------------------- | ----------------------- | -------- |
| POST | /graphql | 教师 BFF GraphQL 端点 | JWT 必需 + teacher 角色 | B1 |
| GET | /graphql | GraphQL Playground开发环境,生产关闭 | 开发环境公开 | ARB-001 |
| GET | /healthz | 健康检查liveness | 公开 | G3 |
| GET | /readyz | 就绪检查readiness按阶段扩展下游探针 | 公开 | G2 + §2.4 |
### 1.3 GraphQL schema如 BFF
GraphQL schema 文件路径:`apps/teacher-bff/src/schema/*.graphql`(端口 :3003
**SDL-first**president §2.17 裁决schema 文件路径:
```
packages/shared-ts/contracts/graphql/teacher-bff.schema.graphql
```
> 文件命名遵循 president §2.17 统一规范 `<bff-name>.schema.graphql`,由 ai03 起草、coord 在批次 1 启动前仲裁第一版。
> 前端 AIai13/ai16通过 graphql-codegen 生成 TS 类型消费。
核心 Query / Mutation 域:
- **auth**currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- **classes**myClasses聚合 core-edu.ClassService.GetClassesByTeacher
- **students**classStudents聚合 core-edu.ClassService.ListStudentsByClass + iam.BatchGetUsers 补用户名)
- **exams**classExams / createExam / updateExam聚合 core-edu.ExamService
- **homework**classHomework / assignHomework聚合 core-edu.HomeworkService
- **grades**studentGrades / recordGrade聚合 core-edu.GradeService
- **attendance**classAttendance / recordAttendance聚合 core-edu.AttendanceService
- **content**textbooks / chapters / knowledgePoints / questions聚合 content 4 个 Service
- **dashboard**teacherDashboard聚合 data-ana.AnalyticsService.GetTeacherDashboard
- **notifications**myNotifications / markAsRead聚合 msg.NotificationService
- **ai**aiChat / generateQuestion / generateLessonPlan聚合 ai.AiService
| 域 | Query / Mutation | 聚合下游 | 阶段 |
| -------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ |
| **auth** | currentUser | iam.GetUserInfo + GetEffectivePermissions + GetViewports | P2 |
| **dashboard** | teacherDashboard | P2: iam gRPC用户基础信息下游字段返 null + extensions.warning<br>P3+: data-ana.GetTeacherDashboard | P2 null → P4 真实 |
| **classes** | myClasses | P2: iam gRPC按 president §3.5P2 班级列表来自 iam 数据)<br>P3+: core-edu.ClassService.GetClassesByTeacher | P2 iam → P3 core-edu |
| **students** | classStudents | core-edu.ClassService.ListStudentsByClass + iam.BatchGetUsers | P3+ |
| **exams** | classExams / createExam / updateExam / deleteExam | core-edu.ExamService | P3+ |
| **homework** | classHomework / assignHomework | core-edu.HomeworkService | P3+ |
| **grades** | studentGrades / recordGrade | core-edu.GradeService | P3+ |
| **attendance** | classAttendance / recordAttendance | core-edu.AttendanceServiceC4 裁决 P3 补全) | P3+ |
| **content** | textbooks / chapters / knowledgePoints / questions | content 4 个 Service | P4+ |
| **notifications** | myNotifications / markAsRead | msg.NotificationService | P5+ |
| **ai** | aiChat / generateQuestion / generateLessonPlan | ai.AiServiceA4 裁决 P5 补全 GenerateLessonPlan | P5+ |
| **admin.\*** | admin.schoolStats / admin.listClasses / admin.listTeachers 等 | admin 命名空间,复用 teacher-bff endpointpresident §5.1 | P2 预留 schema / P6 实现 |
**admin 命名空间预留**president §5.1 + §7.3 强制):
- **P2 预留**schema 文件中预留 `admin.*` Query/Mutation 命名空间占位(含类型定义但 Resolver 返 null
- **P6 实现**ai16 admin-portal 复用 teacher-bff GraphQL endpointai03 在 P6 实现 admin Resolver 真实数据
- **理由**admin 操作低 QPS无需独立 admin-bff 服务;减少 ai16 工作量
### 1.4 Kafka 事件发布(如有)
无。teacher-bff 不发布事件,仅做 gRPC 聚合。
无。teacher-bff 不发布事件,仅做 gRPC 聚合B7 裁决P2-P4 不订阅 KafkaP5 push-gateway 落地后再订阅)
### 1.5 错误码前缀
`BFF_TEACHER_`如 BFF_TEACHER_UPSTREAM_UNAVAILABLE、BFF_TEACHER_AGGREGATION_FAILED、BFF_TEACHER_FORBIDDEN
`BFF_TEACHER_`B5 + G14 裁决,统一 BFF_ 前缀)。
**BFF 越权防御 3 类错误码**president §2.7 裁决):
| 错误码 | HTTP | 场景 | i18n key |
| -------------------------------- | ---- | ---------------------------------------------------- | ------------------------------------- |
| `BFF_TEACHER_UNAUTHORIZED` | 401 | x-user-id 缺失或无效 | `error.bffTeacher.unauthorized` |
| `BFF_TEACHER_FORBIDDEN_RESOURCE` | 403 | teacherId 与资源无归属关系(场景 A | `error.bffTeacher.forbidden_resource` |
| `BFF_TEACHER_IDENTITY_MISMATCH` | 403 | JWT teacherId 与请求 body teacherId 不一致(场景 B | `error.bffTeacher.identity_mismatch` |
**其他 BFF_TEACHER_ 错误码**(聚合层):
| 错误码 | HTTP | 场景 |
| ------------------------------------- | ---- | -------------------------------------- |
| `BFF_TEACHER_UPSTREAM_UNAVAILABLE` | 502 | 下游 gRPC 不可达 |
| `BFF_TEACHER_AGGREGATION_FAILED` | 500 | 聚合多下游时部分失败且无降级数据 |
| `BFF_TEACHER_VALIDATION_FAILED` | 400 | 输入参数校验失败 |
| `BFF_TEACHER_INTERNAL_ERROR` | 500 | 兜底内部错误 |
**B3 裁决澄清**BFF 豁免 `@RequirePermission` 指不做"功能权限决策",但必须做"数据权限防御"B4 越权防御,见 §3.3)。
---
@@ -52,38 +88,115 @@ GraphQL schema 文件路径:`apps/teacher-bff/src/schema/*.graphql`(端口 :
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| --------------- | ------------------------------------- | ---------------- | ----------------------------------------------- |
| iam (ai06) | IamService.GetUserInfo | 获取当前教师信息 | iam 就绪前返回固定 UserInfoteacher 角色) |
| iam (ai06) | IamService.BatchGetUsers | 批量补全学生姓名 | iam 就绪前返回固定用户名("学生001"~"学生030" |
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | iam 就绪前返回全权限(放行) |
| iam (ai06) | IamService.GetViewports | 教师导航菜单 | iam 就绪前返回固定视口列表 |
| core-edu (ai08) | ClassService.GetClassesByTeacher | 教师班级列表 | core-edu 就绪前返回固定 3 个 ClassInfo |
| core-edu (ai08) | ClassService.ListStudentsByClass | 班级学生名单 | core-edu 就绪前返回固定 30 个 StudentInfo |
| core-edu (ai08) | ExamService.* | 考试管理 | core-edu 就绪前返回固定考试数据 |
| core-edu (ai08) | HomeworkService.* | 作业管理 | core-edu 就绪前返回固定作业数据 |
| core-edu (ai08) | GradeService.* | 成绩管理 | core-edu 就绪前返回固定成绩数据 |
| core-edu (ai08) | AttendanceService.* | 考勤管理 | core-edu 就绪前返回固定考勤数据 |
| content (ai09) | TextbookService.ListTextbooks | 教材列表 | content 就绪前返回固定 5 个教材 |
| content (ai09) | ChapterService.ListChapters | 章节列表 | content 就绪前返回固定章节树 |
| content (ai09) | KnowledgeGraphService.* | 知识图谱 | content 就绪前返回固定知识点 |
| content (ai09) | QuestionService.SearchQuestions | 题库检索 | content 就绪前返回固定 20 题 |
| data-ana (ai11) | AnalyticsService.GetTeacherDashboard | 教师仪表盘 | data-ana 就绪前返回固定仪表盘数据 |
| data-ana (ai11) | AnalyticsService.GetClassPerformance | 班级成绩分析 | data-ana 就绪前返回固定分析数据 |
| data-ana (ai11) | AnalyticsService.GetWarningList | 预警列表 | data-ana 就绪前返回固定 5 条预警 |
| msg (ai10) | NotificationService.ListNotifications | 教师通知列表 | msg 就绪前返回固定 10 条通知 |
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | msg 就绪前返回 success=true |
| ai (ai12) | AiService.Chat | AI 对话 | ai 就绪前返回固定回复 |
| ai (ai12) | AiService.GenerateQuestion | AI 出题 | ai 就绪前返回固定题目 |
| ai (ai12) | AiService.GenerateLessonPlan | AI 备课 | ai 就绪前返回固定教案 |
**B2 裁决**:首次实现即 gRPC 调用下游,禁止 REST fetch 过渡。
**B8 裁决**:通过 `DownstreamClient` 抽象统一封装BFF 模式 v2 标准抽象3 个 BFF 共用)。
> **RPC 现状标注说明**
> - ✅ = proto 中已定义(按 packages/shared-proto/proto/ 现状核对)
> - ❌ = proto 中未定义,待 coord 补全(已在裁决中明确补全时机)
#### 2.1.1 iam (ai06) — gRPC 50052
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| --------------------------------- | ---------------- | ---- | ---- | ------------------------------------------- |
| IamService.GetUserInfo | 获取当前教师信息 | ✅ | P2 | 返回固定 UserInfoteacher 角色) |
| IamService.GetViewports | 教师导航菜单 | ❌ | P2 | 返回固定视口列表(待 coord 补 proto |
| IamService.GetEffectivePermissions | 权限校验 | ❌ | P2 | 返回全权限(放行) |
| IamService.BatchGetUsers | 批量补全学生姓名 | ❌ | P3+ | 返回固定用户名("学生001"~"学生030" |
| IamService.GetEffectiveDataScope | 数据范围校验 | ❌ | P3+ | 返回 OWN 范围 |
| IamService.GetChildrenByParent | parent-bff 用)| ❌ | P4 | teacher-bff 不调用 |
> iam.proto 现状仅 4 RPCRegister/Login/RefreshToken/GetUserInfomatrix.md §2 标称 12 RPC待 ai06 + coord 按 I1-I8 裁决补全。
#### 2.1.2 core-edu (ai08) — gRPC 50053
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| ------------------------------------ | -------------- | ---- | ---- | ---------------------------------- |
| ExamService.CreateExam | 创建考试 | ✅ | P3 | 返回固定 examId |
| ExamService.GetExam | 获取考试详情 | ✅ | P3 | 返回固定考试数据 |
| ExamService.ListExamsByClass | 按班级列考试 | ✅ | P3 | 返回固定 5 场考试 |
| ExamService.UpdateExam | 更新考试 | ✅ | P3 | 返回 success=true |
| ExamService.DeleteExam | 删除考试 | ✅ | P3 | 返回 success=true |
| HomeworkService.AssignHomework | 布置作业 | ✅ | P3 | 返回固定 homeworkId |
| HomeworkService.GetHomework | 获取作业详情 | ✅ | P3 | 返回固定作业数据 |
| HomeworkService.ListHomeworkByClass | 按班级列作业 | ✅ | P3 | 返回固定 5 份作业 |
| HomeworkService.SubmitHomework | 提交作业 | ✅ | P3 | 返回 success=truestudent-bff 用)|
| GradeService.RecordGrade | 录入成绩 | ✅ | P3 | 返回 success=true |
| GradeService.GetGrade | 获取成绩 | ✅ | P3 | 返回固定成绩数据 |
| GradeService.ListGradesByStudent | 按学生列成绩 | ✅ | P3 | 返回固定 10 条成绩 |
| GradeService.ListGradesByExam | 按考试列成绩 | ✅ | P3 | 返回固定 30 条成绩 |
| GradeService.ListGradesByHomework | 按作业列成绩 | ✅ | P3 | 返回固定 30 条成绩 |
| ClassService.GetClassesByTeacher | 教师班级列表 | ❌ | P3 | 返回固定 3 个 ClassInfo |
| ClassService.ListStudentsByClass | 班级学生名单 | ❌ | P3 | 返回固定 30 个 StudentInfo |
| ClassService.BatchGetClasses | 批量获取班级 | ❌ | P3 | 返回固定班级数据 |
| AttendanceService.RecordAttendance | 记录考勤 | ❌ | P3 | 返回 success=trueC4 裁决补全) |
| AttendanceService.GetClassAttendance | 查询考勤 | ❌ | P3 | 返回固定考勤数据C4 裁决补全) |
> core_edu.proto 现状 14 RPCExamService 5 + HomeworkService 4 + GradeService 5matrix.md §2 标称 22 RPC 5 Service待 coord 按 C4/C5 裁决补 AttendanceService + ClassServiceGetClassesByTeacher / ListStudentsByClass / BatchGetClasses
#### 2.1.3 content (ai09) — gRPC 50054
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| ------------------------------------ | ------------ | ---- | ---- | ------------------------------- |
| TextbookService.ListTextbooks | 教材列表 | ✅ | P4 | 返回固定 5 个教材 |
| TextbookService.GetTextbook | 获取教材详情 | ✅ | P4 | 返回固定教材数据 |
| TextbookService.CreateTextbook | 创建教材 | ✅ | P4 | 返回固定 textbookId |
| KnowledgeGraphService.GetPrerequisites | 知识点前置 | ✅ | P4 | 返回固定知识点依赖 |
| KnowledgeGraphService.GetLearningPath | 学习路径 | ✅ | P4 | 返回固定学习路径 |
| ChapterService.ListChapters | 章节列表 | ❌ | P4 | 返回固定章节树N5 裁决补全) |
| ChapterService.GetChapter | 章节详情 | ❌ | P4 | 返回固定章节N5 裁决补全) |
| QuestionService.SearchQuestions | 题库检索 | ❌ | P4 | 返回固定 20 题N3 裁决补全) |
> content.proto 现状 5 RPCTextbookService 3 + KnowledgeGraphService 2matrix.md §2 标称 18 RPC 4 Service待 coord 按 N3/N5 裁决补 ChapterService + QuestionService。
#### 2.1.4 data-ana (ai11) — gRPC 50055
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| ------------------------------------ | -------------- | ---- | ---- | ---------------------------------- |
| AnalyticsService.GetClassPerformance | 班级成绩分析 | ✅ | P4 | 返回固定分析数据 |
| AnalyticsService.GetStudentWeakness | 学生薄弱点 | ✅ | P4 | 返回固定 3 个薄弱知识点 |
| AnalyticsService.GetLearningTrend | 学习趋势 | ✅ | P4 | 返回固定 12 个月趋势 |
| AnalyticsService.GetTeacherDashboard | 教师仪表盘 | ❌ | P4 | 返回固定仪表盘数据D4 裁决补全) |
| AnalyticsService.GetWarningList | 预警列表 | ❌ | P4 | 返回固定 5 条预警D4 裁决补全) |
| AnalyticsService.GetMasteryDistribution | 知识掌握分布 | ❌ | P4 | 返回固定分布数据D4 裁决补全) |
| AnalyticsService.SubscribeMasteryUpdate (stream) | 掌握度订阅 | ❌ | P5+ | 流式 mockD4 裁决补全) |
> analytics.proto 现状 3 RPCmatrix.md §2 标称 12 RPC待 coord 按 D4 裁决补全 4 端 Dashboard + Warning + MasteryDistribution + SubscribeMasteryUpdate Stream RPC。
#### 2.1.5 msg (ai10) — gRPC 50056
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| ---------------------------------------- | ------------ | ---- | ---- | ---------------------------------- |
| NotificationService.SendNotification | 发送通知 | ✅ | P5 | 返回固定 notificationId |
| NotificationService.ListNotifications | 教师通知列表 | ✅ | P5 | 返回固定 10 条通知 |
| NotificationService.MarkAsRead | 标记已读 | ✅ | P5 | 返回 success=true |
| NotificationService.SearchNotifications | 搜索通知 | ✅ | P5 | 返回固定搜索结果 |
| NotificationPreferenceService.* | 通知偏好 | ❌ | P5 | 返回默认偏好(待 coord 补 proto |
| NotificationTemplateService.* | 通知模板 | ❌ | P5 | 返回固定模板(待 coord 补 proto |
> msg.proto 现状 4 RPCNotificationServicematrix.md §2 标称 13 RPC 3 Service待 coord 补 NotificationPreferenceService + NotificationTemplateService。
#### 2.1.6 ai (ai12) — gRPC 50058
| Service.RPC | 用途 | 现状 | 阶段 | mock 策略 |
| --------------------------------- | -------- | ---- | ---- | ------------------------------- |
| AiService.Chat | AI 对话 | ✅ | P5 | 返回固定回复 |
| AiService.StreamChat (stream) | 流式对话 | ✅ | P5 | 流式 mockSSE |
| AiService.GenerateQuestion | AI 出题 | ✅ | P5 | 返回固定题目 |
| AiService.OptimizeExpression | 表达优化 | ✅ | P5 | 返回固定优化结果 |
| AiService.GenerateLessonPlan | AI 备课 | ❌ | P5 | 返回固定教案A4 裁决补全) |
| AiService.StreamGenerateQuestion (stream) | 流式出题 | ❌ | P5 | 流式 mockA4 裁决补全) |
> ai.proto 现状 4 RPCmatrix.md §2 标称 6 RPC待 coord 按 A4 裁决补 GenerateLessonPlan + StreamGenerateQuestion。
> **端口说明**ai 服务端口为 **50058**port-allocation.md §5 最终值push-gateway 50057 豁免释放后 50058 让给 ai
### 2.2 Kafka 事件订阅(异步)
无。teacher-bff 不订阅 Kafka 事件,仅同步 gRPC 聚合。
无。teacher-bff 不订阅 Kafka 事件B7 裁决P2-P4 不订阅,仅同步聚合P5 push-gateway 落地后再订阅,届时评估订阅 edu.notification.* 用于实时通知推送)
### 2.3 HTTP 调用(如有)
无。
无。B2 裁决:首次实现即 gRPC禁止 REST fetch。
---
@@ -91,21 +204,53 @@ GraphQL schema 文件路径:`apps/teacher-bff/src/schema/*.graphql`(端口 :
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06
- [ ] core-edu gRPC 50053 启用ai08
- [ ] content gRPC 50054 启用ai09
- [ ] data-ana gRPC 50055 启用ai11
- [ ] msg gRPC 50056 启用ai10
- [ ] ai gRPC 50057 启用ai12
| 上游 | AI | 就绪信号 | 阶段 |
| ---------- | ---- | ------------------------------------------------ | ---- |
| iam | ai06 | gRPC 50052 + 12 RPC + HealthService SERVING | P2 |
| core-edu | ai08 | gRPC 50053 + 22 RPC + HealthService SERVING | P3 |
| content | ai09 | gRPC 50054 + 18 RPC + HealthService SERVING | P4 |
| data-ana | ai11 | gRPC 50055 + 12 RPC + HealthService SERVING | P4 |
| msg | ai10 | gRPC 50056 + 13 RPC + HealthService SERVING | P5 |
| ai | ai12 | gRPC 50058 + 6 RPC + HealthService SERVING | P5 |
| coord | coord | shared-ts DownstreamClient 抽象包就绪B8 裁决)| P2 启动前 |
| coord | coord | teacher-bff.schema.graphql 第一版仲裁完成(含 admin namespace| P2 启动前 |
### 3.2 我的就绪标志(供下游消费)
- [ ] teacher-bff GraphQL :3003 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 6 个下游 gRPC 连通性检查)
- [ ] GraphQL schema 可内省POST /graphql 返回 schema
- [ ] 核心 Query 可执行currentUser / myClasses / teacherDashboard
- [ ] 核心 Mutation 可执行createExam / assignHomework / recordGrade
- [ ] admin namespace 可用(供 admin-portal 消费)
| 信号 | 阶段 | 消费方 |
| --------------------------------------------- | ---- | ------------------- |
| teacher-bff GraphQL :3003 启用(/healthz 200| P2 | teacher-portal |
| /readyz 返回 200按阶段扩展探针见 §3.3 | P2+ | api-gateway |
| GraphQL schema 可内省POST /graphql | P2 | teacher-portal / admin-portal |
| 核心 Query 可执行currentUser / myClasses / teacherDashboard | P2 | teacher-portal |
| 核心 Mutation 可执行createExam / assignHomework / recordGrade | P3 | teacher-portal |
| admin namespace 预留schema 含 admin.* 类型P6 实现 Resolver| P2 预留 / P6 实现 | admin-portal |
| DownstreamClient 抽象落地3 BFF 共用B8 | P2 | student-bff / parent-bff |
### 3.3 /readyz 探针按阶段扩展president §2.4 + G2
**DownstreamHealthCheck 注册表模式**,每个下游注册独立探针,按阶段启用:
| 阶段 | 探针列表 | 项数 |
| ---- | --------------------------------------------------------------------- | ---- |
| P2 | Redis PING + iam gRPC 50052 | 2 |
| P3 | + core-edu gRPC 50053 | 3 |
| P4 | + content gRPC 50054 + data-ana gRPC 50055 | 5 |
| P5 | + ai gRPC 50058 + msg gRPC 50056 | 7 |
> 总裁裁决 §2.4:探针列表扩展属"跨阶段扩展例外"president §2.3),允许新增探针但禁止修改已有探针检查项。
> **软失败规则**必需依赖Redis / 已启用 gRPC 下游)失败返 503可选依赖Kafka 消费 / 未启用 gRPC 下游)失败仅告警返 200 + `degraded: true`。
### 3.4 越权防御B4 + president §2.9
**AuthorizationGuard** 实现 teacherId 与资源归属校验:
| 阶段 | 实现方式 | 裁决依据 |
| ---- | --------------------------------------------------------------------- | -------- |
| P2 | DEV_MODE 放行(环境变量 TEACHER_BFF_DEV_MODE=true+ 日志告警 | §2.9 |
| P3+ | 接入 core-edu gRPC 真实校验GetClassesByTeacher 比对 teacherId | §2.9 |
> B3 裁决澄清BFF 豁免 `@RequirePermission` 不做"功能权限决策",但必须做"数据权限防御"B4。错误码见 §1.5。
---
@@ -124,7 +269,7 @@ GraphQL schema 文件路径:`apps/teacher-bff/src/schema/*.graphql`(端口 :
### 4.2 我消费的 mock
在真实上游就绪前teacher-bff 使用以下 mock详见 §2.1 mock 策略列):
在真实上游就绪前teacher-bff 使用以下 mock详见 §2.1 各表的 mock 策略列):
- **iam mock**:固定 UserInfo + 全权限 + 固定视口
- **core-edu mock**:固定班级/学生/考试/作业/成绩/考勤数据
@@ -133,4 +278,4 @@ GraphQL schema 文件路径:`apps/teacher-bff/src/schema/*.graphql`(端口 :
- **msg mock**:固定通知列表 + MarkAsRead success
- **ai mock**:固定 AI 回复/题目/教案
> 所有上游 mock 通过 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用。
> 所有上游 mock 通过 gRPC client 拦截器实现DownstreamClient 抽象内置 mock 开关B8 裁决),上游就绪后移除拦截器切换真实调用。

View File

@@ -60,11 +60,11 @@
### 2.3 HTTP 调用(如有)
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ------------------------- | --------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/teacher/graphql | 教师 GraphQL 查询(经网关代理到 teacher-bff | api-gateway/teacher-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 教师登录 | api-gateway 就绪前使用 MSW 返回固定 JWT |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------- | ---------------------------- | --------------------------------------------- | ----------------------------------------------------------------- |
| api-gateway (ai01) | POST /api/v1/teacher/graphql | 教师 GraphQL 查询(经网关代理到 teacher-bff,对齐 matrix.md §5 | api-gateway/teacher-bff 就绪前使用 MSW 拦截返回 mock GraphQL 响应 |
| api-gateway (ai01) | POST /api/auth/login | 教师登录(非 GraphQL认证前提F12: JWT 存 localStorage | api-gateway 就绪前使用 MSW 返回固定 JWT |
| push-gateway (ai02) | GET /ws | WebSocket 实时通知 | push-gateway 就绪前使用 mock-socket 模拟 WS 推送 |
### 2.4 GraphQL 查询域(经 api-gateway 代理到 teacher-bff
@@ -97,8 +97,8 @@
- [ ] teacher-portal dev server :4000 启用
- [ ] MF Shell 可加载(首页渲染 AppShell + 导航)
- [ ] 子应用路由可访问(/classes /exams /homework 等子页面渲染)
- [ ] 登录流程可用POST /api/auth/login 获取 JWT 存入 cookie
- [ ] GraphQL 查询可执行(currentUser / myClasses 返回数据
- [ ] 登录流程可用POST /api/auth/login 获取 JWT 存入 localStorageF12P6 迁移 httpOnly cookie
- [ ] GraphQL 查询可执行(dashboard / viewports / me / classes / class QueryARB-001
- [ ] WebSocket 通知可接收push-gateway 推送 → 前端通知中心更新)
---
@@ -118,9 +118,9 @@ teacher-portal 是最前端,无下游消费方。但对开发体验提供:
- **HTTP/GraphQL mock**:使用 MSWMock Service Worker拦截所有请求
- POST /api/auth/login → 返回固定 JWT + UserInfo
- POST /api/teacher/graphql → 根据 operationName 返回对应 mock 响应(与 teacher-bff mock 数据一致)
- POST /api/v1/teacher/graphql → 根据 operationName 返回对应 mock 响应(与 teacher-bff mock 数据一致,对齐 §2.3
- 所有 mock 响应定义在 `apps/teacher-portal/src/mocks/fixtures/*.json`
- **WebSocket mock**:使用 mock-socket 库
- 连接 ws://localhost:8081/ws 后每 30 秒推送 1 条 mock 通知
- **JWT mock**:使用固定 mock JWT与 api-gateway mock 公钥配对),存入 httpOnly cookie
- **JWT mock**:使用固定 mock JWT与 api-gateway mock 公钥配对),存入 localStorageF12P6 迁移 httpOnly cookie
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制是否启用 MSW上游就绪后设为 `disabled`

View File

@@ -1,24 +1,109 @@
# admin-portal 问题记录
> 负责人ai16
> 关联:[coord.md](../coord.md)、[contracts/admin-portal_contract.md](../contracts/admin-portal_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/admin-portal_contract.md](../contracts/admin-portal_contract.md)、[matrix.md](../matrix.md)、[ai-allocation.md §5 ai16](../../ai-allocation.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## §0 已有仲裁核查ai16 复核)
> 任务要求:对 coord 已有仲裁进行核查。以下为 ai16 对照 admin-portal 实际职责逐条复核结论。
### 0.1 ARB-001teacher-bff GraphQL schema 第一版)— 核查结论:✅ 通过,但存在依赖缺口
- **核查点**ARB-001 §1.3 裁决"admin 命名空间 P2 不包含P6 admin-portal 阶段新增 admin 命名空间"。
- **核查结论**裁决方向正确admin-portal 复用 teacher-bff + admin schema 命名空间,与 ai-allocation §5 ai16 设计重点一致)。
- **发现的缺口**teacher-bff 当前 [02-architecture-design.md](../../../services/teacher-bff/docs/02-architecture-design.md) **未定义 admin 命名空间的 GraphQL schema**(全文仅 1 处 audit 提及,无 adminUsers/adminRoles/auditLogs/adminDashboard 等 Query。admin-portal P6 的全部业务查询依赖该 schema属前置依赖缺失。
- **建议**:请 coord 仲裁 admin 命名空间 schema 的归属与时间点——是否由 ai03teacher-bff在 P6 启动前补齐 `packages/shared-ts/contracts/graphql/teacher-bff.graphql` 的 admin 命名空间部分(参照本模块 [contract §2.4](../contracts/admin-portal_contract.md) 的 Query 清单)。
- **状态**:待 coord 仲裁(见新异议 ISSUE-005
### 0.2 ARB-002MF Shell 暴露清单)— 核查结论:✅ 通过,但 01/02 文档未跟进
- **核查点**ARB-002 §2.2 Shell 暴露清单含 `GraphQLProvider` / `useGraphQLClient` / `useAuth` / `usePermission` / `AppShell` / `ErrorBoundary` / `Loading` / `Empty` / `RequirePermission`**不含** `useApi` / `ApiClient`
- **核查结论**裁决正确。admin-portal 应通过 `useGraphQLClient()` 消费 teacher-bff GraphQL而非 REST ApiClient。
- **发现的问题**:本模块 [01-understanding.md](../../../apps/admin-portal/docs/01-understanding.md) 与 [02-architecture-design.md](../../../apps/admin-portal/docs/02-architecture-design.md) 仍基于 REST `useApi()` / `ApiClient` 编写,未跟进 ARB-002。属本模块文档与仲裁不同步见新异议 ISSUE-003
- **状态**:本模块文档待修订(见 ISSUE-003
### 0.3 未仲裁但影响 admin-portal 的关键项
ARB-001/ARB-002 均未明确仲裁以下三项,而它们直接影响 admin-portal 实现,建议 coord 补充裁决:
| 待裁决项 | 当前依据 | 影响 |
| -------- | -------- | ---- |
| admin-portal 端口 | matrix.md / ai-allocation.md = 4003 | 01/02 文档误用 3003与 teacher-bff 冲突) |
| admin-portal 是否消费 push-gateway WebSocket | 同类 portalparent-portal契约 = 消费 | 01/02 文档误声明"不消费推送" |
| 审计日志消费机制iam Kafka → teacher-bff → GraphQL auditLogs | matrix.md §4 列 admin-portal 为 edu.iam.audit.created 消费方 | 前端不直连 Kafka需经 teacher-bff 聚合matrix.md 表述不精确 |
---
## 问题列表
<!--
追加条目格式:
### ISSUE-001-ai1601/02 模块文档归属错误ai07 → ai16
### ISSUE-[编号]-[AI标识][标题]
- **提请方**ai16
- **日期**2026-07-10
- **类型**:编号冲突 / 文档归属
- **描述**[01-understanding.md](../../../apps/admin-portal/docs/01-understanding.md) 与 [02-architecture-design.md](../../../apps/admin-portal/docs/02-architecture-design.md) 头部均标注"AIai07TS/React · 管理场景域前端 remote",分支名 `docs/admin-portal-stage1-stage2-design-ai07`。但 [ai-allocation.md §5](../../ai-allocation.md) 第 54/97/118/159/278 行明确 admin-portal 归属 **ai16**ai07 实际负责 classes → core-edu 交接(见 [workline.md §4.7](../workline.md))。
- **建议方案**:将 01/02 文档头部 AI 标识与分支命名更正为 ai16ai07 在 admin-portal 的产出视为历史草稿,由 ai16 接管修订。
- **状态**:待 coord 仲裁
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
### ISSUE-002-ai1601/02 文档端口错误3003 → 4003且 3003 与 teacher-bff 冲突
(暂无问题)
- **提请方**ai16
- **日期**2026-07-10
- **类型**:契约不明确 / 编号冲突
- **描述**01 §1 与 02 §12.1 声明 admin-portal 端口 3003并称"与 [full-stack-runbook](../../../docs/standards/full-stack-runbook.md) 端口矩阵对齐"。但 full-stack-runbook §2.1 中 **3003 = teacher-bff**admin-portal 未列入该 runbook。coord 维护的 [matrix.md §1](../matrix.md) 与 ai-allocation.md 统一采用 4000 段teacher-portal :4000 / student-portal :4001 / parent-portal :4002 / admin-portal :4003。
- **建议方案**:确认 admin-portal 端口为 **4003**;同步更新 full-stack-runbook §2.1 补齐 4 个 portal 的 4000 段端口(消除 runbook 与 matrix.md 的端口双轨制)。
- **状态**:待 coord 仲裁
### ISSUE-003-ai1601/02 文档通信协议与 ARB-001/ARB-002 不一致REST → GraphQL
- **提请方**ai16
- **日期**2026-07-10
- **类型**:契约不明确 / 前置依赖缺失
- **描述**01 §3.1 / 02 §1、§4 全文基于 REST`/api/v1/iam/*` + `/api/v1/admin/*` + `useApi()` + `ApiClient`)。但 ARB-001 已裁决 admin-portal 复用 teacher-bff GraphQL **admin 命名空间**ARB-002 已裁决 Shell 暴露 `GraphQLProvider` + `useGraphQLClient`(不含 `useApi`。02 §11.3 仍将"GraphQL vs REST"列为未决与仲裁结论冲突。根因01/02 文档参照的 teacher-portal 02 文档(同样基于 REST、将 GraphQL 列为未决)早于 ARB-001/0022026-07-09未跟进仲裁。
- **建议方案**admin-portal 通信协议统一为 GraphQL`POST /api/admin/graphql` → teacher-bff admin 命名空间);删除 `useApi`/`ApiClient` 依赖,改用 `useGraphQLClient()`02 §11.3 移除已裁决项。本模块 [contract.md](../contracts/admin-portal_contract.md) 已按 GraphQL 编写,作为修订基准。
- **状态**:待 coord 仲裁
### ISSUE-004-ai1601/02 文档遗漏审计日志与学校设置ai-allocation §5 明确职责)
- **提请方**ai16
- **日期**2026-07-10
- **类型**:工作量超批 / 契约不明确
- **描述**[ai-allocation.md §5 ai16](../../ai-allocation.md) 第 282 行明确 admin-portal 设计重点含"用户管理 + 角色权限管理 + 学校设置 + 组织管理 + **审计日志消费**"。但 01 §2.1/§L1 导航/§L2 路由表均**无审计日志、无学校设置**(仅有 dashboard/users/roles/permissions/viewports/organization/monitoring 7 个视口)。本模块 [contract.md §1.2/§2.4](../contracts/admin-portal_contract.md) 已含 audit-logs / system 路由与 auditLogs Query与 01/02 不一致。
- **建议方案**admin-portal 视口补齐为 9 个dashboard / users / roles / permissions / viewports / organization / classes / teachers / students / audit-logs / system按 ai-allocation §5 + contract §1.2 对齐);审计日志经 teacher-bff GraphQL `auditLogs` Query 消费(聚合 iam `AuditEvent`)。
- **状态**:待 coord 仲裁
### ISSUE-005-ai16teacher-bff 缺 admin 命名空间 GraphQL schema前置依赖缺失
- **提请方**ai16
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**ARB-001 裁决 P6 新增 admin 命名空间,但 teacher-bff [02-architecture-design.md](../../../services/teacher-bff/docs/02-architecture-design.md) 未定义该 schema无 adminUsers / adminRoles / adminClasses / adminTeachers / adminStudents / auditLogs / adminDashboard 等 Query/Mutation。admin-portal P6 全部业务查询依赖此 schema且需 SDL-first 存放于 `packages/shared-ts/contracts/graphql/teacher-bff.graphql`ARB-001 §1.3)。
- **建议方案**:请 coord 仲裁——由 ai03 在 P6 启动前补齐 teacher-bff admin 命名空间 schema参照本模块 contract §2.4 Query 清单),作为 admin-portal P6 的前置就绪信号;并更新 [matrix.md §3](../matrix.md) teacher-bff 行的 schema 文件状态。
- **状态**:待 coord 仲裁
### ISSUE-006-ai1601/02 文档推送策略与同类 portal 契约不一致(轮询 → WebSocket
- **提请方**ai16
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**01 §3.3 / 02 §5 声明 admin-portal"不消费 WebSocket/SSE采用轮询"。但同类 portal 契约([parent-portal_contract.md §2.3](./parent-portal_contract.md))消费 push-gateway `GET /ws`,本模块 [contract.md §2.3](../contracts/admin-portal_contract.md) 亦声明消费 WebSocket 实时通知。matrix.md §5 列 push-gateway WS 消费方含全部 portal。管理端审计告警/异常登录等场景对实时性有合理需求。
- **建议方案**admin-portal 接入 push-gateway WebSocket与同类 portal 一致用于审计告警、异常登录、系统异常等实时通知保留轮询仅用于监控指标60s与统计5min这类天然适合轮询的低频数据。
- **状态**:待 coord 仲裁
### ISSUE-007-ai16matrix.md §4 将 admin-portal 列为 Kafka 直消费方,与前端层级矛盾
- **提请方**ai16
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**[matrix.md §4](../matrix.md) 第 111 行将 admin-portal 列为 `edu.iam.audit.created` 的消费方。但前端不直连 Kafka[contract.md §2.2](../contracts/admin-portal_contract.md) 已明确审计日志经 GraphQL 查询。实际链路应为iam → Kafka → **teacher-bff** 消费 → GraphQL `auditLogs` Query → admin-portal。
- **建议方案**matrix.md §4 该行消费方更正为 **teacher-bff**admin-portal 经 teacher-bff 间接消费),避免误导架构分层。
- **状态**:待 coord 仲裁
---
## §1 历史问题
(暂无已裁决问题)

View File

@@ -1,13 +1,130 @@
# ai 问题记录
> 负责人ai12
> 关联:[coord.md](../coord.md)、[contracts/ai_contract.md](../contracts/ai_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/ai_contract.md](../contracts/ai_contract.md)、[matrix.md](../matrix.md)、[port-allocation.md](../../../../infra/port-allocation.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## §0 已有仲裁核查ai12 复核2026-07-10
> 任务要求对已有的仲裁进行核查。coord.md 当前仅含 ARB-001 / ARB-002另在 [port-allocation.md](../../../../infra/port-allocation.md) §7、coord-final-decisions.md、president-final-rulings.md 中存在涉及 ai 的历史仲裁。逐项核查如下。
| 仲裁编号 / 来源 | 主题 | 涉及 ai | 核查结论 | 状态 |
| -------------------------- | ----------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| ARB-001coord.md §1 | teacher-bff GraphQL schema 第一版 | ❌ 否 | 与 ai 无关,不适用。 | — |
| ARB-002coord.md §2 | MF Shell 暴露清单 | ❌ 否 | 与 ai 无关,不适用。 | — |
| port-allocation.md §7 | 50058 让给 aipush-gateway 豁免 gRPC | ✅ 是 | **仲裁有效**port-allocation.md §3/§5 已登记 ai = HTTP 3008 / gRPC 50058。ai 的 01/02 文档已按 50058 设计,一致。**但 [matrix.md](../matrix.md) §2 gRPC 接口提供方矩阵、§8 就绪信号跟踪表仍写 50057未同步** → 提请 coord 同步(见 ISSUE-01 | ⚠️ 仲裁有效但未同步 |
| coord-final-decisions §1-§4 | iam P2 契约 / BFF 设计 / Gateway / content | ❌ 否 | 历史仲裁,与 ai 无直接约束。ai 仅消费 content/data-ana gRPC需待其就绪。 | — |
| president-final-rulings §2.3 | Temporal 用于 AI 编排 | ✅ 是 | **部分仲裁**004 §2.3 列出 Temporal。ai12 已在 02-architecture-design.md §8.3 建议简单 4 步工作流用 BackgroundTasks + Redis长运行评估 Temporal。需 coord 在 P6 决策点确认(见 ISSUE-06。 | ⚠️ 部分仲裁 |
| 01/02 文档引用"004 §7.2 已确认 topic edu.insight.ai.usage" | ai 用量事件 topic | ✅ 是 | **核查不通过**grep 004 全文未发现 `ai.usage` / `insight.ai` / `AIUsage` 任何字样004 §7.2 并未确认 ai 的 topic。该引用无法证实topic 命名实际处于三义未决状态(见 ISSUE-02。 | ❌ 引用失实 |
| 01/02 文档引用"004 §1.2 端口矩阵 HTTP+10050 规则" | ai gRPC 端口推导规则 | ✅ 是 | **核查不通过**004 全文无端口矩阵;`HTTP+10050` 公式不成立3008+10050=13058≠50058。端口唯一源是 port-allocation.md顺序分配ai=50058。ai 文档应改引 port-allocation.md。 | ❌ 引用失实 |
**核查总结**coord.md 现有 2 项仲裁ARB-001/002均与 ai 无关;涉及 ai 的真实仲裁在 port-allocation.md50058 已定)与 004 §2.3Temporal 部分仲裁。ai 文档对"004 §7.2/§1.2"的两处引用失实,需修正引用源。
---
## 问题列表
### ISSUE-01-ai12matrix.md 端口 50057 与 port-allocation.md 50058 不同步
- **提请方**ai12
- **日期**2026-07-10
- **类型**:编号冲突 / 文档不同步
- **描述**[matrix.md](../matrix.md) §2 gRPC 接口提供方矩阵写 "ai (ai12) | 50057"§8 就绪信号跟踪表写 "ai gRPC 50057"。但 [port-allocation.md](../../../../infra/port-allocation.md) §3/§5/§7 明确 ai = HTTP 3008 / gRPC 500582026-07-09 coord 仲裁"push-gateway 豁免 gRPC释放 5005750058 让给 ai")。两份 coord 文件矛盾matrix.md 过时。ai 01/02 文档与 workline.md §1/§4.12 已正确采用 50058。
- **建议方案**coord 将 matrix.md §2 ai 行 gRPC 端口 50057 → 50058§8 就绪信号"ai gRPC 50057"→ 50058。ai12 同步将 contracts/ai_contract.md 全部 50057 改 50058。
- **状态**:待 coord 仲裁(实为同步操作,仲裁已存在于 port-allocation.md §7
### ISSUE-02-ai12ai 用量事件 Kafka topic 命名三义未决
- **提请方**ai12
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**ai 用量计费事件 topic 在三份文档中命名不一致,且无权威源:
- 01-understanding.md §3.2 / 02-architecture-design.md §5.2`edu.insight.ai.usage`(自称"004 §7.2 已确认",但 004 中不存在)
- matrix.md §4 Kafka 事件发布方矩阵:`edu.ai.usage`,事件 `AIUsageEvent`
- contracts/ai_contract.md §1.4`edu.ai.usage.events`
events.proto 现状无 `AIUsageEvent` message004 无 topic 记录。三义导致 data-ana 消费方无法对齐。
- **建议方案**ai12 建议采用 `edu.ai.usage`(与 matrix.md 一致,最简短,符合 `edu.<domain>.<action>` 简洁约定)。请 coord 裁定并在 004 §7.2 补登 + events.proto 补 `AIUsageEvent` messageschema 见 02-architecture-design.md §3.3。ai12 据裁决同步 01/02 文档与 contract。
- **状态**:待 coord 仲裁
### ISSUE-03-ai12ai.proto 待补全(备课工作流 RPC + 字段扩展)
- **提请方**ai12
- **日期**2026-07-10
- **类型**:契约不明确 / 前置依赖缺失
- **描述**ai.proto 现状仅 4 RPCChat / StreamChat / GenerateQuestion / OptimizeExpression`GenerateQuestionRequest` 仅 prompt/subject/difficulty 三字段。P5 交付需要:
- 新增 RPC`GenerateLessonPlan``StreamGenerateQuestion`ai-allocation §5 "题目逐字生成"
- `GenerateQuestionRequest` 扩展字段grade / knowledge_point_ids / question_type / count
- `ChatRequest` 扩展可选字段user_id / session_id / data_scope
关于 RPC 总数存在分歧matrix.md §2 与 contract 写 6 RPC02-architecture-design.md §4.2 单方面扩到 8 RPC追加 GetLessonPlanStatus / ConfirmLessonPlan。需 coord 裁定 P5 目标 RPC 清单。
- **建议方案**ai12 建议 P5 目标 6 RPCChat / StreamChat / GenerateQuestion / StreamGenerateQuestion / OptimizeExpression / GenerateLessonPlan。备课工作流的"查询状态/确认入库"用 HTTP 端点(`GET /ai/v1/lesson/preparation/{id}` + `POST .../confirm`)实现,避免 RPC 膨胀;如 coord 认为查询/确认也需 gRPC则定为 8 RPC。请 coord 在 P5 启动前升级 ai.proto 到 v1 完整版。
- **状态**:待 coord 仲裁
### ISSUE-04-ai12events.proto 缺 AIUsageEvent message
- **提请方**ai12
- **日期**2026-07-10
- **类型**:契约不明确 / 前置依赖缺失
- **描述**004 §12.2 + §15.3 #6 仲裁 ai 用量事件豁免 Outbox派生数据但 events.proto 现状仅有 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent**无 AIUsageEvent**。data-ana 消费方无 schema 可循。01/02 文档已提请A3matrix.md §4 已列事件名,但 proto 未落地。
- **建议方案**coord 在 events.proto 新增 `AIUsageEvent` message建议 schema 见 02-architecture-design.md §3.3(含 event_id / user_id / school_id / provider / model / operation / prompt_tokens / completion_tokens / total_tokens / latency_ms / success / degraded / metadata。与 ISSUE-02 一并裁决。
- **状态**:待 coord 仲裁
### ISSUE-05-ai12contracts/ai_contract.md 与设计文档多处矛盾ai12 自查自纠清单)
- **提请方**ai12
- **日期**2026-07-10
- **类型**:契约不明确 / 文档不同步
- **描述**:现有 contracts/ai_contract.mdcoord 模板ai12 接管前未细化)与 01/02 设计文档存在 5 处矛盾:
1. §1.1 gRPC 端口 50057应为 50058见 ISSUE-01
2. §1.2 "无对外 HTTP 端点,仅 gRPC"——错误。api-gateway 代理 `/api/v1/ai/*` → ai HTTPmain.py 已实现 /ai/* 端点 + SSE 流式HTTP 保留作 Gateway 直连降级
3. §1.4 topic `edu.ai.usage.events`(三义,见 ISSUE-02
4. §1.5 错误码示例 `AI_PROVIDER_UNAVAILABLE` / `AI_TOKEN_LIMIT_EXCEEDED` / `AI_CONTENT_FILTERED` 与 02-architecture-design.md §6.2 清单(`AI_LLM_UNAVAILABLE` / `AI_QUOTA_EXCEEDED` / `AI_CONTENT_MODERATION_REJECTED`)命名不一致
5. §2.2 列出 ai 消费 2 个 content Kafka 事件,与 01/02 §5.1 "ai 不消费任何事件(无状态)"矛盾
- **建议方案**ai12 在本次工作中直接重写 contracts/ai_contract.md 对齐设计文档(端口 50058 / 补 HTTP 端点 / topic 待 ISSUE-02 裁决后填 / 错误码对齐 02 §6.2 / 删除消费事件或标注 P6+ 评估)。仅 RPC 数与 topic 命名待 coord 裁决后最终定稿。
- **状态**ai12 自纠中(依赖 ISSUE-01/02/03 裁决的字段待补)
### ISSUE-06-ai12备课工作流是否引入 TemporalP6 决策点)
- **提请方**ai12
- **日期**2026-07-10
- **类型**:工作量超批 / 前置依赖缺失
- **描述**004 §2.3 列出 Temporal 用于"AI 编排"属部分仲裁。ai12 在 02-architecture-design.md §8.3 建议P5 用 FastAPI BackgroundTasks + Redis24h TTL实现 4 步备课工作流P6 评估迁移 Temporal支持长运行跨天审核 + 复杂状态机)。需 coord 在 P6 决策点确认是否引入,避免 P5 实现被推翻重做。
- **建议方案**P5 采用 BackgroundTasks + Redis02-architecture-design.md §2.4 状态机已设计P6 由 coord 评估 Temporal 引入时机。请 coord 在 roadmap 标注 P6 决策点。
- **状态**:待 coord 仲裁P6 决策点)
### ISSUE-07-ai12iam GetEffectiveDataScope gRPC RPC 待 P4 补全
- **提请方**ai12
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**ai 多租户用量配额校验需调用 `IamService.GetEffectiveDataScope` 查询用户 DataScope。01/02 文档已提请A4coord 已仲裁 P4 补全§15.3 #5),但 iam.proto 现状未见此 RPC。ai P5 实施时依赖,若 P4 未补全将阻塞配额校验功能。
- **建议方案**:请 coord 确认 iam (ai06) 在 P4 已补全 `GetEffectiveDataScope` RPCai12 在 P5 实施时调用Redis 缓存 5min。若 P4 未补全ai 降级为"仅按 user_id 配额,不按 school_id"。
- **状态**:待 coord 确认 P4 补全情况
### ISSUE-08-ai12proto package 命名不符合 project_rules §5
- **提请方**ai12
- **日期**2026-07-10
- **类型**:契约不明确 / 架构约束
- **描述**project_rules §5 规定 proto 包名规范 `edu.<domain>.v1`(如 `edu.iam.v1``edu.core_edu.v1`)。但 ai.proto 现状 package 为 `next_edu_cloud.ai.v1`events.proto 为 `next_edu_cloud.events.v1`均不符合规范。01/02 文档未指出此偏离。此为全局 proto 命名问题(涉及全部 proto 文件),非 ai 独有,但 ai12 在审查中发现需提请。
- **建议方案**:请 coord 裁定是否统一迁移 proto package 至 `edu.<domain>.v1`(全局变更,需 buf breaking 评估或保留现状作为历史包袱。ai12 在 ai.proto 补全 RPC 时遵循最终裁定。
- **状态**:待 coord 仲裁(全局 proto 命名)
### ISSUE-09-ai12响应信封偏离 ActionState 强制整改P0
- **提请方**ai12
- **日期**2026-07-10
- **类型**:架构约束
- **描述**004 §11.5 强制响应信封为 ActionState`{success, data, error:{code,message,details,traceId}}`。ai 当前 main.py 违反:返回 `{success:true, data:..., degraded:false}` 顶层 degraded 字段(见 main.py:89/201-210/233-244。01/02 文档已提请A5contracts 未记录。P5 实施必须整改。
- **建议方案**P5 实施时所有 HTTP 端点 + gRPC RPC 返回值改为 ActionStatedegraded 作为 `error.details.degraded` 子字段。请 coord 在 known-issues §2.9 ai 分区记录此约束01 文档已提请,需 coord 落地 known-issues
- **状态**:待 coord 在 known-issues 记录约束(整改由 ai12 P5 执行)
---
<!--
追加条目格式:
@@ -20,5 +137,3 @@
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)

View File

@@ -1,24 +1,3 @@
# api-gateway 问题记录
> 负责人ai01
> 关联:[coord.md](../coord.md)、[contracts/api-gateway_contract.md](../contracts/api-gateway_contract.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## 问题列表
<!--
追加条目格式:
### ISSUE-[编号]-[AI标识][标题]
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)
>

View File

@@ -1,24 +1,187 @@
# content 问题记录
> 负责人ai09
> 关联:[coord.md](../coord.md)、[contracts/content_contract.md](../contracts/content_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/content_contract.md](../contracts/content_contract.md)、[../../services/content/docs/01-understanding.md](../../../services/content/docs/01-understanding.md)、[../../services/content/docs/02-architecture-design.md](../../../services/content/docs/02-architecture-design.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## 问题列表
## §0 已有仲裁核查2026-07-10 复核)
<!--
追加条目格式:
> 复核依据:[coord-final-decisions.md §3.3](../../coord-final-decisions.md) N1-N5、[01-understanding.md §A](../../../services/content/docs/01-understanding.md) ai09 复核记录
### ISSUE-[编号]-[AI标识][标题]
### 0.1 coord-final-decisions.md N1-N5 核查
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
| 编号 | 仲裁结论 | 02-architecture-design.md 落实位置 | 核查结果 |
| ---- | ----------------------------------------------------------------------- | ------------------------------------------------ | -------- |
| N1 | P4 首次实现即启用 gRPC server 50054 | §1.2 入口 HTTP 3005 / gRPC 50054§4.2 gRPC API | ✅ 已落实 |
| N2 | 首次实现即检查 DB/Neo4j/Kafka | §6.6 /readyz 多依赖检查 | ✅ 已落实 |
| N3 | P4 即补全 QuestionService proto不等到 P5 | §4.2.4 QuestionService 6 RPC | ✅ 已落实 |
| N4 | 首次实现即对齐 ActionState | §4.3 错误响应结构 success/error 信封 | ✅ 已落实 |
| N5 | P4 首次实现即补全 ChapterService | §4.2.2 ChapterService 3 RPC | ✅ 已落实 |
(暂无问题)
### 0.2 01-understanding.md §A 已裁决项核查
| 原编号 | 仲裁结论 | 02-architecture-design.md 落实位置 | 核查结果 |
| ------ | --------------------------------------------------------------------- | ----------------------------------------- | -------- |
| C2 | P4 必须引入 Outbox004 §12.2 强制条款) | §3.1.5 content_outbox_events 表 + §5.4 Outbox Publisher | ✅ 已落实 |
| C4 | P4 必须实现 gRPC controller | §4.2 gRPC API4 个 Service | ✅ 已落实 |
| C10 | proto 包名保持 `next_edu_cloud.content.v1` | —(保持现状) | ✅ 已落实 |
### 0.3 核查结论
N1-N5 与 C2/C4/C10 共 8 项已有仲裁**全部在 02-architecture-design.md 中正确落实**,无遗漏、无偏离。
---
## §1 新提请异议2026-07-10 ai09 复审)
### ISSUE-001-ai09REST 端点设计文档与现有实现不一致
- **提请方**ai09
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**02-architecture-design.md §4.1 列出的 REST API 与现有源码实现存在三处偏差:
1. **knowledge-points 列表**:设计文档为 `GET /knowledge-points?chapterId=`query 参数),源码 [knowledge-points.controller.ts](../../../services/content/src/knowledge-points/knowledge-points.controller.ts) 实现为 `GET /knowledge-points/chapter/:chapterId`path 参数)
2. **knowledge-points 删除前置**:设计文档列 `DELETE /knowledge-points/:id/prerequisites/:prereqId`,源码未实现该端点
3. **chapters 列表**:设计文档为 `GET /chapters?textbookId=`,源码实现为 `GET /chapters/textbook/:textbookId`
- **建议方案**以设计文档为目标态query 参数 + 补 DELETE prerequisite 端点),在 P4 重构时统一对齐。但需 coord 确认是否允许 API 路径变更(影响 teacher-bff 消费方)。
- **状态**:待 coord 仲裁
### ISSUE-002-ai09content 发布事件 topic 命名策略与契约文档不一致
- **提请方**ai09
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:事件 topic 命名存在两种策略冲突:
- **02-architecture-design.md §5.1 + §5.3 TOPIC_MAP**:每个事件类型独立 topic`edu.content.textbook.created` / `edu.content.question.published` 等共 12 个 topic
- **contracts/content_contract.md §1.4 + matrix.md §4**:按聚合根聚合 topic`edu.content.knowledge_point.events` / `edu.content.question.events` 共 2 个 topic事件类型用 `action` 字段区分)
- **events.proto**:未定义 KnowledgePointEvent / QuestionEvent message仅 ClassEvent/ExamEvent/HomeworkEvent/GradeEvent
- **建议方案**:采用**聚合 topic + action 字段**策略与契约文档、matrix.md、events.proto ClassEvent 模式一致),原因:
1. 与 core-edu 既有模式edu.exam.events / edu.homework.events 等)一致
2. 减少 topic 数量12 → 4降低 Kafka 集群元数据压力
3. 消费方按 action 字段过滤,订阅灵活性更高
4. 需补 `KnowledgePointEvent` / `QuestionEvent` / `TextbookEvent` / `ChapterEvent` proto message
- **状态**:待 coord 仲裁
### ISSUE-003-ai09Textbook/Chapter 事件在契约文档遗漏
- **提请方**ai09
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**02-architecture-design.md §5.1 列出 4 类 textbook 事件 + 1 类 chapter 事件,但 contracts/content_contract.md §1.4 仅列出 knowledge_point 与 question 两类事件Textbook/Chapter 事件未登记。matrix.md §4 也仅列 kp + question。导致下游data-ana无法感知教材/章节变更。
- **建议方案**:在 contract.md 与 matrix.md 补登记 `edu.content.textbook.events`action: created/updated/published/archived`edu.content.chapter.events`action: created/updated/deleted。若 coord 认为教材/章节无需对外发事件,则在 design doc §5.1 删除相关事件。
- **状态**:待 coord 仲裁
### ISSUE-004-ai09gRPC RPC 数量三方文档不一致
- **提请方**ai09
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**content gRPC RPC 数量在三处文档不一致:
| Service | 02-architecture-design.md §4.2 | contracts/content_contract.md §1.1 | matrix.md §2 |
| --------------------- | ------------------------------ | ---------------------------------- | ------------ |
| TextbookService | 5含 Update/Delete 新增) | 3无 Update/Delete | 18总数 |
| ChapterService | 3Create/List/Get | 4含 Update无 Delete | — |
| KnowledgeGraphService | 4 | 4 | — |
| QuestionService | 6无 Publish/Search | 7含 Publish/Search | — |
| **合计** | **18** | **18** | **18** |
- design doc 缺 QuestionService.PublishQuestion / SearchQuestionscontract 有)
- contract 缺 TextbookService.Update/Deletedesign doc 有)
- design doc ChapterService 缺 Updatecontract 有contract ChapterService 缺 Deletedesign doc 也缺)
- **建议方案**:以 contract.md 为契约唯一源(已对齐 matrix.md 18 RPC 总数),反向修正 design doc
1. TextbookService 补 Update/Delete与 contract 对齐)
2. ChapterService 补 Update + Deletedesign doc + contract 都缺 Delete需补
3. QuestionService 补 PublishQuestion + SearchQuestions与 contract 对齐)
4. 最终 RPC 总数TextbookService 5 + ChapterService 5 + KnowledgeGraphService 4 + QuestionService 7 = **21 RPC**(需同步更新 matrix.md §2 的 18 → 21
- **状态**:待 coord 仲裁
### ISSUE-005-ai09core-edu → content 失效事件 topic 无定义
- **提请方**ai09
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**02-architecture-design.md §5.2 列出 content 消费 `edu.teaching.content.invalidated`(待 ai03 确认 topic
1. events.proto 无 ContentInvalidatedEvent message
2. matrix.md §4 未登记该 topic
3. core-edu 设计文档ai08未明确发布该事件
4. 01-understanding.md §5 也标注"具体 topic 待 core-edu ai03 设计确认"——此处 ai03 疑为笔误core-edu 实际由 ai08 负责
- **建议方案**content 不主动消费 core-edu 失效事件content 是上游内容提供方core-edu 是消费方),删除 §5.2 中该条目;若确有联动需求,由 core-edu 主动调用 content gRPC UpdateQuestion 状态变更,而非事件驱动。
- **状态**:待 coord 仲裁
### ISSUE-006-ai09文档结尾"直接 push main"与项目规则冲突
- **提请方**ai09
- **日期**2026-07-10
- **类型**:其他
- **描述**01-understanding.md 末尾与 02-architecture-design.md 末尾均标注 `Branch: 单仓库并行模式(直接 push main`,但 [project_rules §8 Git 工作流](../../../../.trae/rules/project_rules.md) 明确规定:
- §8分支开发AI 不得自行切换/创建/合并分支
- §14.3AI 禁止 `git merge``git push origin main`
- 当前 worktree 分支为 `feat-review-content-module-docs-WAIyMA`
- **建议方案**:删除两份文档末尾"单仓库并行模式(直接 push main"字样,改为 `Branch: feat-review-content-module-docs-WAIyMA分支开发提交后通知人类合并`
- **状态**:待 coord 仲裁
### ISSUE-007-ai09questions 表 created_by 字段迁移风险
- **提请方**ai09
- **日期**2026-07-10
- **类型**:其他
- **描述**02-architecture-design.md §3.1.4 questions 表新增 `created_by varchar(32) NOT NULL`,但现有 [questions.schema.ts](../../../services/content/src/questions/questions.schema.ts) 无该字段,且现有数据无 created_by 值。schema 迁移时 NOT NULL 约束会导致历史数据迁移失败。
- **建议方案**:迁移期间先用 `created_by varchar(32) NULL`,数据回填后再加 NOT NULL 约束;或为新数据强制要求 created_by应用层校验历史数据用 `'system'` 默认值回填。
- **状态**:待 coord 仲裁
### ISSUE-008-ai09设计文档缺缓存策略与 API 版本化策略
- **提请方**ai09
- **日期**2026-07-10
- **类型**:其他
- **描述**02-architecture-design.md 未涉及两个长远架构必备项:
1. **缓存策略**[env.ts](../../../services/content/src/config/env.ts) 已预留 `REDIS_URL`,但 design doc 未设计缓存层(教材树/知识点树是典型读多写少场景,应缓存)
2. **API 版本化**REST 端点无 `/v1/` 前缀matrix.md §5 显示 api-gateway 路由为 `/api/v1/teacher/*`,但 content 自身端点 `/textbooks` 无版本号),未来破坏性变更无版本隔离机制
- **建议方案**
1. P4 在 design doc §6 补"缓存策略"小节:教材树/章节树 Redis 缓存 + 失效策略Outbox 事件触发缓存失效)
2. P4 在 design doc §4 补"API 版本化"说明REST 端点统一加 `/v1/` 前缀gRPC 用 proto package version
- **状态**:待 coord 仲裁
### ISSUE-009-ai09knowledge-points schema 实际缺 difficulty/metadata 字段
- **提请方**ai09
- **日期**2026-07-10
- **类型**:其他
- **描述**02-architecture-design.md §3.1.3 knowledge_points 表列出 `difficulty tinyint NOT NULL DEFAULT 3``metadata json NULL`,但实际 [textbooks.schema.ts](../../../services/content/src/textbooks/textbooks.schema.ts) 中 `knowledgePoints` 表仅含 id/chapterId/title/description 四个字段,无 difficulty 与 metadata。01-understanding.md C7 提到"时间戳缺失"但未提到 difficulty/metadata 缺失。
- **建议方案**P4 schema 迁移时一并补齐 difficulty + metadata + created_at + updated_at与 design doc §3.1.3 对齐)。
- **状态**:待 coord 仲裁
### ISSUE-010-ai09Neo4j Sync Worker 与 ES Sync Worker 在 P4 阶段不必要
- **提请方**ai09
- **日期**2026-07-10
- **类型**:工作量超批
- **描述**02-architecture-design.md §1.1 分层图将 Neo4j Sync Worker 与 ES Sync Worker 并列展示,但:
1. ES 在 P5 才引入ES Sync Worker 在 P4 不必要
2. 当前 [knowledge-points.service.ts](../../../services/content/src/knowledge-points/knowledge-points.service.ts) 的 `safeCreateNode` 是同步双写(业务事务内写 Neo4j与 design doc §0.1 第 3 条"禁止业务事务内同步双写"原则冲突
3. P4 应改为 Outbox 事件驱动异步同步 Neo4j但 design doc §1.1 图中 Neo4j Sync Worker 的输入源同时画了"Kafka Consumer"与"CONSUMER",链路不清晰
- **建议方案**
1. §1.1 图中明确标注 ES Sync Worker 为 P5 组件(虚线或灰显)
2. §1.1 图中 Neo4j Sync Worker 的输入仅来自 content 自身 Outbox 事件(不消费 core-edu 事件)
3. P4 任务 T6 明确"重构 knowledge-points.service.ts移除 safeCreateNode 同步写,改为发 Outbox 事件"
- **状态**:待 coord 仲裁
---
## §2 待 coord 仲裁项汇总
| # | 标题 | 阻塞性 | 状态 |
| ---- | -------------------------------------------- | ----------------------- | ------------ |
| 001 | REST 端点设计与实现不一致 | 🟡 P4 重构时对齐 | 待 coord 仲裁 |
| 002 | 事件 topic 命名策略冲突(独立 vs 聚合) | 🔴 阻塞 Outbox 实现 | 待 coord 仲裁 |
| 003 | Textbook/Chapter 事件在契约文档遗漏 | 🟡 契约完整性 | 待 coord 仲裁 |
| 004 | gRPC RPC 数量三方文档不一致 | 🔴 阻塞 proto 修改 | 待 coord 仲裁 |
| 005 | core-edu → content 失效事件 topic 无定义 | 🟢 建议删除 | 待 coord 仲裁 |
| 006 | "直接 push main"与项目规则冲突 | 🟡 文档修正 | 待 coord 仲裁 |
| 007 | questions.created_by 迁移风险 | 🟡 schema 迁移 | 待 coord 仲裁 |
| 008 | 缓存策略与 API 版本化策略缺失 | 🟢 长远架构 | 待 coord 仲裁 |
| 009 | knowledge-points schema 实际缺字段 | 🟡 P4 schema 迁移 | 待 coord 仲裁 |
| 010 | Sync Worker 链路与 P4 阶段不必要 | 🟡 设计澄清 | 待 coord 仲裁 |

View File

@@ -1,11 +1,34 @@
# core-edu 问题记录
> 负责人ai08
> 关联:[coord.md](../coord.md)、[contracts/core-edu_contract.md](../contracts/core-edu_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/core-edu_contract.md](../contracts/core-edu_contract.md)、[coord-cross-review.md](../../coord-cross-review.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## §0 已有仲裁核查记录ai08 接管后核查)
> 核查日期2026-07-10
> 核查范围01-understanding.md / 02-architecture-design.md 引用的 coord 仲裁结论
> 核查方法:对照 [coord-cross-review.md](../../coord-cross-review.md) 原文 + 实际 proto 文件 + 实际源码
### 0.1 已核查通过的仲裁
| 仲裁编号 | 位置 | 核查结论 |
| -------- | ---- | -------- |
| coord-cross-review §3.1 | topic 命名统一为 `edu.teaching.<aggregate>.<action>` | ✅ 仲裁真实存在结论准确core-edu 01/02 文档引用正确 |
| coord-cross-review §5.5 | 错误码前缀 `CORE_EDU_*` 统一(不再细分 `EXAMS_`/`HOMEWORK_`/`GRADES_` | ✅ 仲裁真实存在,结论准确 |
| coord-cross-review §5.7 | ActionState 信封结构统一 | ✅ 仲裁真实存在core-edu GlobalErrorFilter 已对齐 |
| coord-cross-review §2.1 | proto 包名 `next_edu_cloud.core_edu.v1` | ✅ 仲裁真实存在core_edu.proto L3 符合 |
| coord-cross-review §6 整改 #14 | core_edu.proto 补 AttendanceService | ⚠️ 仲裁真实存在,但状态与实际不符(见 ISSUE-001 |
| coord-cross-review §6 整改 #16 | buf.gen.yaml 补 gRPC 插件 | ⚠️ 待 ai08 核实 buf.gen.yaml 实际状态 |
### 0.2 核查发现的仲裁状态不一致(升级为 ISSUE
详见下方 ISSUE-001 ~ ISSUE-003。
---
## 问题列表
<!--
@@ -21,4 +44,208 @@
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)
### ISSUE-001-ai08core_edu.proto 实际状态与 coord 仲裁声称不一致P0
- **提请方**ai08
- **日期**2026-07-10
- **类型**:契约不明确(仲裁状态与代码实际不符)
- **描述**
[coord-cross-review.md](../../coord-cross-review.md) L312 声称:
> `core_edu.proto 补全 | packages/shared-proto/proto/core_edu.proto | ✅ 5 service+ClassService +AttendanceService`
但 ai08 核查实际文件 `packages/shared-proto/proto/core_edu.proto`**实际只有 3 个 service**
```
service ExamService { ... } // 5 RPC
service HomeworkService { ... } // 4 RPC
service GradeService { ... } // 5 RPC
```
**缺失**
- `ClassService`4 RPCGetClass / GetClassesByTeacher / BatchGetClasses / ListStudentsByClass
- `AttendanceService`4 RPCRecordAttendance / GetAttendance / ListAttendanceByStudent / ListAttendanceByClass
同时 Exam/Homework/Grade message **缺 P3 新增字段**
- Exam 缺 `subject_id`、`school_id`、`status_changed_at`、`status_changed_by`、`archived_at`
- Homework 缺 `subject_id`、`grace_period`、`school_id`
- Grade 缺 `total_score`、`school_id`、`idempotency_key`
- SubmitHomeworkRequest 缺 `answers` 字段02 文档 §4.2 要求含完整 answers
缺失 P3 新增 RPC`PublishExam` / `SubmitExam` / `GradeExam` / `GradeHomework` / `UpdateGrade`。
- **影响**
1. 01-understanding.md L48 仅声称缺 AttendanceService遗漏了 ClassService 也缺失
2. matrix.md §2 声称 core-edu 22 RPC但实际 proto 仅 14 RPC5+4+5
3. 下游 teacher-bff / student-bff / parent-bff 按 22 RPC 设计 mock实际无 proto 定义可生成代码
- **建议方案**
coord 确认 `core_edu.proto` 补全工作的实际负责人coord 自行补全,还是交由 ai08 在 P3 补全)。
- 若 coord 已补全但未提交:请 coord 提交最新 proto
- 若待 ai08 P3 补全coord-cross-review.md L312 的 "✅ 已补全" 状态需更正为 "⏳ 待 ai08 P3 补全"01-understanding.md L48 需补"ClassService 也缺失"
- **状态**:待 coord 仲裁
---
### ISSUE-002-ai08events.proto 未同步 coord topic 命名仲裁P0
- **提请方**ai08
- **日期**2026-07-10
- **类型**:契约不明确(仲裁未落实到 proto
- **描述**
[coord-cross-review.md](../../coord-cross-review.md) §3.1 仲裁"统一为 `edu.teaching.<aggregate>.<action>` 风格",但 `packages/shared-proto/proto/events.proto` 实际状态:
1. **文件头注释L9-13仍是旧 topic 命名**
```
// edu.exam.events <- exam.created / exam.updated / exam.deleted
// edu.homework.events <- homework.assigned / homework.submitted / homework.graded
// edu.grade.events <- grade.recorded / grade.updated
// edu.class.events <- class.transferred
```
未同步 `edu.teaching.exam.created` 等仲裁后命名。
2. **缺 `AttendanceEvent` message**events.proto 仅定义 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent无 AttendanceEvent。02 文档 §5.1 要求发布 `edu.teaching.attendance.recorded` 事件,但 proto 无对应 message。
3. **缺 `schema_version` 字段**:所有 Event messageClassEvent/ExamEvent/HomeworkEvent/GradeEvent均无 `schema_version` 字段。coord §3.1 仲裁 + known-issues §1.3 + 01-understanding.md L67 + 02-architecture-design.md §5.1 均要求事件 payload 含 `schema_version`,但 proto 未定义。
4. **matrix.md §4 与 coord §3.1 仲裁不一致**matrix.md §4 Kafka 事件发布方矩阵仍列 `edu.exam.events` / `edu.homework.events` / `edu.grade.events` / `edu.class.events`,未同步 coord §3.1 仲裁后的 `edu.teaching.*` 命名。
- **影响**
- core-edu 修改 TOPIC_MAP 后,发布到 `edu.teaching.*` topic但 events.proto 注释和 matrix.md 仍记录旧 topic下游 AI 文档被误导
- 缺 AttendanceEvent message 导致 AttendanceService 无事件契约可发布
- 缺 schema_version 字段导致消费端无法按版本处理
- **建议方案**
1. coord 同步更新 events.proto
- 文件头注释改为 `edu.teaching.*` 命名
- 新增 `AttendanceEvent` message
- 所有 Event message 新增 `string schema_version = N;` 字段
2. coord 同步更新 matrix.md §4 为 `edu.teaching.*` 命名
3. ai08 在 core-edu `outbox.publisher.ts` TOPIC_MAP 按 `edu.teaching.*` 命名实现
- **状态**:待 coord 仲裁
---
### ISSUE-003-ai08考试/作业状态命名跨模块不一致P1
- **提请方**ai08
- **日期**2026-07-10
- **类型**:契约不明确(跨模块命名冲突)
- **描述**
02-architecture-design.md 定义的状态命名与 [coord.md](../coord.md) §1.2 GraphQL schema 枚举不一致:
| 维度 | 02-architecture-design.mdcore-edu | coord.md §1.2 GraphQL schemateacher-bff | 不一致点 |
| ---- | ------------------------------------- | ------------------------------------------- | -------- |
| ExamStatus | `draft / published / in_progress / grading / graded / archived / cancelled` | `DRAFT / PUBLISHED / IN_PROGRESS / GRADING / SCORED / ARCHIVED` | core-edu 用 `graded`coord GraphQL 用 `SCORED`core-edu 多 `cancelled` |
| SubmissionStatusexam | `in_progress / submitted / graded` | `NOT_SUBMITTED / SUBMITTED / GRADED` | core-edu 用 `in_progress`coord 用 `NOT_SUBMITTED` |
| SubmissionStatushomework | `draft / submitted / graded` | `NOT_SUBMITTED / SUBMITTED / GRADED` | core-edu exam 用 `in_progress`homework 用 `draft`,自身也不一致 |
02 文档 §3.1.1 exam_submissions.status 注释 `in_progress / submitted / graded`§3.1.2 homework_submissions.status 注释 `draft / submitted / graded`**core-edu 内部 exam 与 homework 的初始状态命名也不一致**`in_progress` vs `draft`)。
- **影响**
- teacher-bff GraphQL schema 枚举值与 core-edu DB status 字段值无法直接映射BFF 需要转换层
- 前端展示需处理两套命名
- coord 仲裁 teacher-bff schema 时未与 core-edu 状态机命名对齐
- **建议方案**
coord 仲裁统一状态命名(建议二选一):
- **方案 A推荐**core-edu DB status 统一为小写动词形式 `draft / published / in_progress / grading / graded / archived / cancelled`GraphQL 枚举映射为大写 `DRAFT / PUBLISHED / IN_PROGRESS / GRADING / GRADED / ARCHIVED / CANCELLED`(去掉 `SCORED`,统一用 `GRADED`。SubmissionStatus 统一为 `NOT_SUBMITTED / SUBMITTED / GRADED`core-edu exam/homework submissions 初始状态统一为 `not_submitted`(不再用 `in_progress` 或 `draft`)。
- **方案 B**:保留 coord GraphQL 现状(`SCORED`core-edu 改 `graded` 为 `scored`。
ai08 倾向方案 A`graded` 是教育领域通用术语,`scored` 歧义大)。
- **状态**:待 coord 仲裁
---
### ISSUE-004-ai08class.transferred 事件 topic 三处不一致P1
- **提请方**ai08
- **日期**2026-07-10
- **类型**契约不明确topic 命名跨文档不一致)
- **描述**
`class.transferred` 事件的 topic 命名在三处文档不一致:
| 文档 | topic 命名 |
| ---- | ---------- |
| 01-understanding.md L64 / 02-architecture-design.md §5.1 / §7 | `edu.org.class.created`(合并后归 org 域) |
| matrix.md §4 | `edu.class.events`ClassEventaction: transferred |
| events.proto L13 注释 | `edu.class.events` |
core-edu 文档声称"classes 合并后归 org 域"用 `edu.org.class.created`,但 coord 维护的 matrix.md 和 events.proto 仍用 `edu.class.events`。
- **影响**
- core-edu 按 `edu.org.class.created` 实现 TOPIC_MAP 后matrix.md 记录的 `edu.class.events` topic 下游消费者订阅不上
- 命名归类不一致org 域 vs class 域)
- **建议方案**
coord 仲裁统一:
- 若 classes 合并到 core-edu 后仍归"教学组织域",则 topic 应为 `edu.teaching.class.transferred`(遵循 §3.1 仲裁的 `edu.teaching.<aggregate>.<action>` 风格)
- 若归"组织域",则为 `edu.org.class.transferred`(注意 action 应为 `transferred` 而非 `created`
ai08 倾向 `edu.teaching.class.transferred`(与 §3.1 仲裁风格一致classes 合并到 core-edu 后属教学域)。
- **状态**:待 coord 仲裁
---
### ISSUE-005-ai08core-edu gRPC RPC 数量统计口径不一致P1
- **提请方**ai08
- **日期**2026-07-10
- **类型**:契约不明确(统计口径冲突)
- **描述**
core-edu gRPC RPC 数量在三处文档统计不一致:
| 文档 | RPC 数 | 包含 ClassService | 包含 P3 新增 RPC |
| ---- | ------ | ----------------- | ---------------- |
| matrix.md §2 | 22 | ✅ 是4 RPC | ❌ 否P2 基线) |
| 02-architecture-design.md §4.2 | 22 | ❌ 否 | ✅ 是PublishExam/SubmitExam/GradeExam/GradeHomework/UpdateGrade |
| core-edu_contract.md §1.1 | 22 | ✅ 是4 RPC | ❌ 否P2 基线) |
P3 全量应为ClassService 4 + ExamService 85+3 新增)+ HomeworkService 54+1 新增)+ GradeService 65+1 新增)+ AttendanceService 4 = **27 RPC**。
- **影响**
- matrix.md §2 声明 22 RPC 但含 ClassService02 文档声明 22 RPC 但不含 ClassService下游 AI 无法判断应实现多少 RPC
- 就绪信号"core-edu gRPC 50053 + 22 RPC"含义模糊
- **建议方案**
coord 统一 RPC 统计口径:
- matrix.md §2 改为 "27 RPCP3 全量ClassService 4 + ExamService 8 + HomeworkService 5 + GradeService 6 + AttendanceService 4"
- 就绪信号改为 "core-edu gRPC 50053 + 27 RPC + HealthService SERVING"
- core-edu_contract.md §1.1 补全 P3 新增 RPC
- **状态**:待 coord 仲裁
---
### ISSUE-006-ai08core-edu 02 文档 §13.3 七项未决决策待仲裁P2
- **提请方**ai08
- **日期**2026-07-10
- **类型**:契约不明确(设计决策未仲裁)
- **描述**
02-architecture-design.md §13.3 列出 7 项未决设计决策ai08 尚未正式提请 coord 仲裁,现集中提请:
| # | 决策点 | ai08 倾向 |
| - | ------ | --------- |
| 1 | classes 服务合并到 core-edu 的时机 | (a) P3 初期合并 |
| 2 | 排课 room_id 是否 P3 实现 | (b) 仅预留字段 |
| 3 | 成绩计算公式 scope 优先级 | (a) class > subject > school |
| 4 | 作业 grace_period 默认值 | (b) 300 秒5 分钟宽限) |
| 5 | P3 是否启用 events.proto schema 强制校验 | (b) 仅文档约束 |
| 6 | exam.submitted 事件是否包含完整 answers | (b) 仅含 submission_id |
| 7 | archived 考试数据是否物理迁移 | (b) P3 仅软删除 |
注:决策 #4 与 02 文档 §3.1.2 homework 表 `gracePeriod: int("grace_period").notNull().default(0)` 默认 0 不一致,仲裁后需统一 schema 默认值。
- **影响**
- 决策未定core-edu P3 实现无法启动
- 决策 #4 schema 默认值与倾向不一致,仲裁前可能实现错
- **建议方案**
coord 逐项仲裁ai08 按倾向方案实现。决策 #4 若采 (b) 300 秒02 文档 §3.1.2 schema 默认值改为 `.default(300)`。
- **状态**:待 coord 仲裁
---
### ISSUE-007-ai08core-edu 01 文档审计表"待核对"项待 coord 确认P2
- **提请方**ai08
- **日期**2026-07-10
- **类型**:其他(审计未完成项)
- **描述**
01-understanding.md 审计表与"黄金模板对齐清单"中 3 项标记"待核对"
1. Dockerfile 多阶段构建L111 / L128 审计表"待核对"
2. ActionState 信封 traceId 验证L116
3. homework/grades controller 权限装饰器覆盖核对L104
ai08 在 P3 实施前需 coord 确认这些项是否作为 P3 验收硬性标准。
- **建议方案**
coord 确认上述 3 项是否纳入 P3 验收标准若纳入ai08 在 P3 实施时补齐。
- **状态**:待 coord 仲裁

View File

@@ -1,24 +1,129 @@
# data-ana 问题记录
> 负责人ai11
> 关联:[coord.md](../coord.md)、[contracts/data-ana_contract.md](../contracts/data-ana_contract.md)
> 关联:[coord.md](../coord.md)、[coord-cross-review.md](../../coord-cross-review.md)、[contracts/data-ana_contract.md](../contracts/data-ana_contract.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## 问题列表
## §0 已有仲裁核查记录2026-07-10
<!--
追加条目格式:
> ai11 在编写 objections 前对 coord.md 与 coord-cross-review.md 中涉及 data-ana 的仲裁逐项核查落实状态。
### ISSUE-[编号]-[AI标识][标题]
### 0.1 coord.md 仲裁核查
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
| 编号 | 主题 | 涉及 data-ana | 核查结论 |
| ------- | ---------------------------- | ------------- | ------------------------------ |
| ARB-001 | teacher-bff GraphQL schema | ❌ 否 | 不涉及,无需 action |
| ARB-002 | MF Shell 暴露清单 | ❌ 否 | 不涉及,无需 action |
(暂无问题)
**结论**coord.md 无 data-ana 直接仲裁。
### 0.2 coord-cross-review.md 仲裁核查8 项涉及 data-ana
| # | 审查章节 | 裁决内容 | 责任方 | 核查结论 |
| -- | ---------------- | ------------------------------------------------------------------ | ------ | ---------------------------------------------------------------------------------------------------- |
| 1 | §2.2 #3 | iam 新增 `GetEffectiveDataScope` RPCP4 补全data-ana gRPC 调用 | iam | ⚠️ **未落实**iam.proto 当前仅 4 RPCRegister/Login/RefreshToken/GetUserInfo无 GetEffectiveDataScope |
| 2 | §2.3 P4 行 | content + data-ana P4 启用 gRPC server | ai11 | ⏳ 未到 P4 阶段,待执行 |
| 3 | §3.2 | 补登 `edu.insight.ai.usage` topic + events.proto 补 AIUsageEvent | coord | ⚠️ **未落实**events.proto 当前仅 4 messageClass/Exam/Homework/GradeEvent缺 AIUsageEvent |
| 4 | §3.3 | Python 服务 Outbox 豁免MasteryUpdated / WarningTriggered | coord | ✅ **已对齐**01/02 文档已声明豁免,引用 coord-cross-review.md §3.3 |
| 5 | §4.3 | data-ana HTTP=3006 / gRPC=50055 | coord | ✅ **已对齐**01/02 文档端口声明一致 |
| 6 | §5.3 | Python 服务信封改为 ActionStatedegraded 放 details 子字段) | ai11 | ✅ **已对齐**02 §4.3 ActionState 实现已修正degraded 移至顶层 details |
| 7 | §6 #4 | 同 #6ai06 修正 data-ana/ai 02 文档 + 代码 | ai11 | ✅ **已对齐(文档)**02 已修正;代码待 P4 实现阶段重构 |
| 8 | §6 #7/#8/#9/#10 | coord 在 004 §7.2 补登 topic + §1.2 端口列 + §4.1 gRPC 矩阵 + §12.2 豁免 | coord | ⚠️ **未落实**004 正文无 §4.2/§7.2 补登段/§11.4/§11.501/02 引用断裂已临时改为引 coord-cross-review.md |
### 0.3 coord-cross-review §8.2 批次 0 产出声明核查
coord-cross-review.md §8.2 声称批次 0 已完成 proto 补全ai11 逐文件核查实际状态:
| 声明产出项 | 声明状态 | 实际文件状态ai11 核查) | 核查结论 |
| -------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | ------------ |
| iam.proto 12 RPC | ✅ 12 RPC | **4 RPC**Register/Login/RefreshToken/GetUserInfo | ⚠️ 严重不符 |
| analytics.proto 扩展 | ✅ 12 RPC含 Stream | **3 RPC**GetClassPerformance/GetStudentWeakness/GetLearningTrend | ⚠️ 严重不符 |
| events.proto 补全 | ✅ 9 message+AuditEvent | **4 message**ClassEvent/ExamEvent/HomeworkEvent/GradeEvent | ⚠️ 严重不符 |
| core_edu.proto 补全 | ✅ 5 service | 未由 ai11 核查(非本模块边界) | — |
| buf.gen.yaml 插件 | ✅ go + python | 未由 ai11 核查coord 维护) | — |
> **核查说明**ai11 仅核查与 data-ana 直接相关的 protoiam/analytics/events。§8.2 声明与实际文件严重不符,可能原因:(a) 声明为计划态但未执行;(b) 执行后未提交到本 worktree 分支;(c) 在其他分支已执行但未合并。无论哪种原因data-ana 的 P4 实现依赖这些 proto 补全,当前实际状态构成 P4 阻塞。
---
## §1 问题列表
### ISSUE-001-ai11iam.proto 缺 GetEffectiveDataScope RPCP4 阻塞)
- **提请方**ai11
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**coord-cross-review.md §2.2 #3 已仲裁"iam P4 补全 `GetEffectiveDataScope` RPCdata-ana gRPC 调用",但 iam.proto 当前仅 4 RPC无此 RPC。data-ana 的 DataScope 6 级过滤SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL依赖此 RPC 解析用户可见数据范围,是 P4 实现的硬阻塞项。
- **建议方案**coord 确认 iam.proto 补全进度。若 iam 侧尚未实现data-ana P4 阶段将使用硬编码 DataScope 降级(按 role 映射默认 scope并标注 `details.degraded: true`,待 iam 就绪后切换。
- **状态**:待 coord 仲裁(核查已有仲裁 §0.2 #1 未落实)
---
### ISSUE-002-ai11events.proto 缺 AIUsageEvent messageP5 阻塞P4 预备)
- **提请方**ai11
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**coord-cross-review.md §3.2 已仲裁"补登 `edu.insight.ai.usage` topic + events.proto 补 `AIUsageEvent` message",但 events.proto 当前仅 4 messageClassEvent/ExamEvent/HomeworkEvent/GradeEvent缺 AIUsageEvent。data-ana 需消费此事件落 `ai_usage_log` 宽表,供管理员仪表盘展示 AI 用量统计。
- **建议方案**coord 在 events.proto 补 `AIUsageEvent` message字段建议`{event_id, request_id, user_id, provider, model, prompt_tokens, completion_tokens, total_tokens, latency_ms, success, cost_cents, occurred_at}`。P5 前补全即可P4 仪表盘 AI 用量区块显示"暂无数据"。
- **状态**:待 coord 仲裁(核查已有仲裁 §0.2 #3 未落实)
---
### ISSUE-003-ai11analytics.proto 仅 3 RPCcoord-cross-review §8.2 声称已扩展至 12 RPC 但实际未落实P4 阻塞)
- **提请方**ai11
- **日期**2026-07-10
- **类型**:前置依赖缺失 + 声明与实际不符
- **描述**coord-cross-review.md §8.2 声称"analytics.proto 扩展 ✅ 12 RPC含 Stream",但实际文件仅 3 RPCGetClassPerformance/GetStudentWeakness/GetLearningTrend。ai-allocation.md §5 与 matrix.md §2 均要求 data-ana 提供 12 RPC02-architecture-design.md §4.2 已设计完整 12 RPC 清单(含 4 端 Dashboard + Warning + Mastery + Server Streaming但 proto 未补全导致无法生成 stub。
- **建议方案**coord 确认 analytics.proto 扩展进度。ai11 可提供 12 RPC 的完整 message 定义提案(见 [02-architecture-design.md §4.2](../../../services/data-ana/docs/02-architecture-design.md)coord 审议后合并到 analytics.proto。若 coord 未补全ai11 在 P4 阶段自行补全 proto本分支内提请 coord 合并。
- **状态**:待 coord 仲裁
---
### ISSUE-004-ai11coord-cross-review §6 整改清单 coord 责任项未落实,导致 004 章节引用断裂
- **提请方**ai11
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**coord-cross-review.md §6 整改清单中标注"coord"责任的 4 项整改未在 004 正文中落实:
- #7004 §7.2 补登 6 个 topic + CDC 命名规范 → 004 正文无对应段落
- #8004 §1.2 新增 HTTP/gRPC 端口两列 → 004 §1.2 服务清单无端口列
- #9004 §4.1 补充 gRPC 启用阶段矩阵 → 004 正文无 §4.2 子节
- #10004 §12.2 补充派生数据事件 Outbox 豁免条款 → 004 §12.2 未补充
这导致 01-understanding.md 和 02-architecture-design.md 中引用 004 §4.2/§11.4/§11.5/§15.3 等章节均断裂004 正文仅到 §14§15 在 004-p6-addendum.md 但内容不同。ai11 已在 v2.1 修订中临时改为引用 coord-cross-review.md 对应裁决章节,但这是过渡方案。
- **建议方案**coord 按整改清单 #7/#8/#9/#10 补全 004 对应章节,使 004 成为可信的架构设计意图唯一源。各 AI 文档随后将引用从 coord-cross-review.md 回切到 004 对应章节。
- **状态**:待 coord 仲裁
---
### ISSUE-005-ai11data-ana 发布的 MasteryEvent topic 命名三处不一致
- **提请方**ai11
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**data-ana 发布的掌握度/预警事件 topic 命名在三个文档中不一致:
| 文档 | topic 命名 |
| -------------------------------------- | ------------------------------------- |
| 01-understanding.md / 02-architecture-design.md | `edu.insight.mastery.updated` + `edu.insight.warning.triggered` |
| matrix.md §4 | `edu.analytics.mastery` |
| contracts/data-ana_contract.md修正前 | `edu.data_ana.mastery.events` |
按 004 §7.2 命名规范 `edu.<domain>.<aggregate>.<action>`data-ana 属于 D6 智能洞察领域domain=insight`edu.insight.mastery.updated` 符合规范。matrix.md 的 `edu.analytics.mastery` 不符合命名规范(缺 action 层级,且 domain 用了服务名而非领域名)。
- **建议方案**coord 裁决统一为 `edu.insight.mastery.updated` + `edu.insight.warning.triggered`coord 修正 matrix.md §4。ai11 已在 contract.md 中采用此命名。
- **状态**:待 coord 仲裁
---
### ISSUE-006-ai11coord-cross-review §8.2 批次 0 产出声明与 proto 实际文件状态严重不符
- **提请方**ai11
- **日期**2026-07-10
- **类型**:其他(声明与实际不符)
- **描述**coord-cross-review.md §8.2 声称批次 0 已完成 iam.proto12 RPC/ analytics.proto12 RPC/ events.proto9 message补全但 ai11 逐文件核查发现实际均未补全(详见 §0.3 核查表)。这影响所有依赖这些 proto 的下游 AI 的排期评估——若 AI 信任 §8.2 声明,会在排期中忽略 proto 补全的等待时间,导致排期失真。
- **建议方案**coord 核实 §8.2 声明真实性。若实际已补全但未合并到各 worktree 分支,请协调合并;若实际未补全,请更新 §8.2 状态为"计划中"或"待执行",并明确补全时间点,以便下游 AI 据此排期。
- **状态**:待 coord 仲裁

View File

@@ -6,6 +6,124 @@
---
## §1 已有仲裁核查
> 对已生效的仲裁coord-final-decisions I1-I8、president-final-rulings 相关条款、coord.md ARB-001/ARB-002逐条核查落地情况。
### 1.1 coord-final-decisions I1-I8iam 专项)核查
| 裁决 | 内容摘要 | 核查结论 | 证据 |
| ---- | -------- | -------- | ---- |
| I1 | P2 即启用 gRPC server 50052REST + gRPC 并存 | ⚠️ **02 文档未回写**[02-architecture-design.md](../../../services/iam/docs/02-architecture-design.md) §1/§7.1/§8.1 决策点 5 仍写"P2 仅 RESTP3 随 core-edu 引入 gRPC"iam.proto 仅 4 RPC 未补全 | [iam.proto](../../../packages/shared-proto/proto/iam.proto) 仅 4 RPC[02 文档 §8.1](../../../services/iam/docs/02-architecture-design.md) 决策点 5 |
| I2 | 直接建 shared-ts Outbox 工具包iam 首次实现即用 | ✅ **工具包已建立**`packages/shared-ts/src/outbox/` 存在 outbox.service.ts + outbox.module.ts 02 文档 §8.1 决策点 4 仍写"iam 自建轻量 Outbox",未回写 | [shared-ts/outbox](../../../packages/shared-ts/src/outbox/) |
| I3 | 首次实现即 DB 驱动 + Redis 缓存,废弃本地 map | ❌ **源码未改造**[permission.guard.ts](../../../services/iam/src/middleware/permission.guard.ts) 第 30-39 行仍用硬编码 `ROLE_PERMISSIONS` mapadmin/teacher02 文档 §8.1 决策点 9 描述为"待改造" | [permission.guard.ts](../../../services/iam/src/middleware/permission.guard.ts) |
| I4 | 首次实现即注册 AuthMiddlewareController 用 @Req() 注入 | ❌ **源码未注册**[app.module.ts](../../../services/iam/src/app.module.ts) 仅注册 PermissionGuard 为 APP_GUARD未在 configure() 消费 AuthMiddleware02 文档 §1.1/§1.2/§8.1 决策点 10 仍写"P2 仍不注册" | [app.module.ts](../../../services/iam/src/app.module.ts) |
| I5 | P2 本地文件 IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATH | ⚠️ **源码未实现**[iam.service.ts](../../../services/iam/src/iam/iam.service.ts) 第 170-186 行仍用 `env.JWT_SECRET`HS256 单密钥02 文档 §8.1 决策点 1 已对齐本地文件→P6 Vault但未落地 | [iam.service.ts](../../../services/iam/src/iam/iam.service.ts) |
| I6 | P2 即补全 iam_student_guardians 表 + GetChildrenByParent RPC + GET /iam/children | ❌ **02 文档表名错误**[02 文档 §3.1.2](../../../services/iam/docs/02-architecture-design.md) 用 `iam_parent_student_relations`,裁决表名为 `iam_student_guardians`;源码未实现 | 02 文档 §3.1.2 |
| I7 | 采用 /iam/v1/* 前缀Gateway 透传 | ❌ **02 文档未回写**[02 文档 §4.1](../../../services/iam/docs/02-architecture-design.md) REST API 清单全部用 `/iam/*``/v1` 前缀;源码 Controller 用 `@Controller("iam")` 无版本前缀 | [iam.controller.ts](../../../services/iam/src/iam/iam.controller.ts)、[rbac.controller.ts](../../../services/iam/src/iam/rbac.controller.ts) |
| I8 | 统一 GET /iam/permissions/effective | ✅ **源码已对齐**[rbac.controller.ts](../../../services/iam/src/iam/rbac.controller.ts) 第 33 行用 `@Get("permissions/effective")` | [rbac.controller.ts](../../../services/iam/src/iam/rbac.controller.ts) |
### 1.2 president-final-rulings 相关条款核查
| 条款 | 内容摘要 | 核查结论 |
| ----- | -------- | -------- |
| §2.15 | /iam/v1/* 版本化规则Controller 加 v1 前缀) | ❌ 同 I7未落地 |
| §2.16 | gRPC 与 REST 双入口策略gateway HTTP 透传 + BFF gRPC 调用) | ⚠️ 02 文档未体现双入口设计,仅描述 REST 单入口contract.md §1.2 写"无对外 HTTP 端点,仅 gRPC"与此冲突 |
| §3.2 | iam P2 拆分 P2.18 RPC 核心)+ P2.2(扩展) | ⚠️ workline.md 仅粗略列出 P2.1P2.2-P6 未细化(见 worklines/iam_workline.md |
| §5.5 | 审计日志归 iamAuditEvent + edu.iam.audit.created topic + user_audit_log 表) | ❌ 02 文档未包含审计日志设计events.proto 未定义 AuditEvent message |
| §5.1 | events.proto 补全 UserEvent/RoleEvent | ❌ events.proto 实际仅含 ClassEvent/ExamEvent/HomeworkEvent/GradeEvent缺 UserEvent/RoleEvent/AuditEventcoord-final-decisions §5.1 标注"✅ 已补全"与实际不符) |
### 1.3 coord.md ARB-001 / ARB-002 核查
| 仲裁 | 内容摘要 | 核查结论 |
| ------ | -------- | -------- |
| ARB-001 | teacher-bff GraphQL schema 第一版5 Query | ✅ schema 中 `me: User!``viewports` 依赖 iam与 iam GetUserInfo/GetViewports RPC 对齐,无冲突 |
| ARB-002 | MF Shell 暴露清单 | ✅ 不涉及 iam 直接交付物,无冲突 |
---
## §2 新提请异议
### ISSUE-001-ai0601/02 文档未回写 I1-I8 裁决,中间过渡方案残留
- **提请方**ai06
- **日期**2026-07-10
- **类型**:契约不明确 / 其他
- **描述**coord-final-decisions §0.1 强制覆盖声明要求各 AI 在 3 个工作日内回写 02 文档,删除所有"中间过渡方案"。president-final-rulings §3.4 也明确要求 ai06 回写 iam 02I1-I8§3.4)。但当前 [01-understanding.md](../../../services/iam/docs/01-understanding.md) 和 [02-architecture-design.md](../../../services/iam/docs/02-architecture-design.md) 仍残留大量过渡方案:
- 02 §1/§7.1/§8.1 决策点 5P2 仅 REST → P3 gRPC违反 I1
- 02 §8.1 决策点 4iam 自建 Outbox违反 I2
- 02 §8.1 决策点 9PermissionGuard 待改造(违反 I3
- 02 §1.1/§1.2/§8.1 决策点 10AuthMiddleware P2 不注册(违反 I4
- 02 §3.1.2:表名 iam_parent_student_relations违反 I6
- 02 §4.1API 路径无 /v1 前缀(违反 I7
- 01 §1teacher-bff HTTP 调用 iam违反 B2应 gRPC
- **建议方案**ai06 立即回写 01/02 文档,删除全部中间过渡方案描述,对齐 I1-I8 + §2.15/§2.16/§5.5 最终方案。
- **状态**:待 coord 仲裁(确认回写范围与验收标准)
### ISSUE-002-ai06events.proto 缺少 UserEvent/RoleEvent/AuditEvent message
- **提请方**ai06
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**coord-final-decisions §5.1 标注"events.proto ✅ 已补全UserEvent/RoleEvent/NotificationEvent/MasteryEvent/AIUsageEvent/KnowledgePointEvent/QuestionEvent"president §5.5 也要求"events.proto 补 AuditEvent 在批次 0.10 完成"。但实际 [events.proto](../../../packages/shared-proto/proto/events.proto) 仅定义 ClassEvent/ExamEvent/HomeworkEvent/GradeEvent 4 个 message**完全缺少** UserEvent/RoleEvent/AuditEvent 等 iam 依赖的事件契约。这直接阻塞 iam Outbox 事件发布iam 无法写入未定义 schema 的事件)。
- **建议方案**coord 立即补全 events.proto至少新增 UserEvent、RoleEvent、AuditEvent 三个 message按 02 文档 §5.2 和 president §5.5 的字段定义)。
- **状态**:待 coord 仲裁
### ISSUE-003-ai06Topic 命名三方不一致edu.identity.* vs edu.iam.*
- **提请方**ai06
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**iam 事件 Topic 命名存在三方不一致:
- [004 §7.2](../../../docs/architecture/004_architecture_impact_map.md) 行 620-621`edu.identity.user.created` / `edu.identity.user.updated`(用 `identity` 域名)
- [matrix.md §4](../matrix.md) / [iam_contract.md §1.4](../contracts/iam_contract.md)`edu.iam.user.events` / `edu.iam.role.events` / `edu.iam.audit.created`(用 `iam` 域名)
- president §5.5`edu.iam.audit.created`(用 `iam` 域名)
- coord-final-decisions G16 规则:`edu.<domain>.<aggregate>.<action>`
- **建议方案**:统一用 `edu.iam.*`(服务名为 iam非 identitycoord 同步修正 004 §7.2 的 `edu.identity.*``edu.iam.*`。同时明确 Topic 粒度matrix.md 用聚合 topic`edu.iam.user.events` 含多 action004 §7.2 用具体动作 topic`edu.iam.user.created`),需统一为一种风格。
- **状态**:待 coord 仲裁
### ISSUE-004-ai06DataScope 枚举三方不一致
- **提请方**ai06
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**DataScope 6 级枚举存在三方不一致:
- [004 §5.3 表](../../../docs/architecture/004_architecture_impact_map.md) 行 463-470SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL
- [004 §5.3](../../../docs/architecture/004_architecture_impact_map.md) 行 505all/grade_managed/class_taught/children/owned + 自定义(语义命名,与表不同)
- president §3.2 P2.2ALL/SCHOOL/GRADE/CLASS/SUBJECT/SELF**SUBJECT 替代 DISTRICT**
- 源码 [iam.schema.ts](../../../services/iam/src/iam/iam.schema.ts) 第 16-25 行self/class/grade/school/district/all与 004 表一致,与 president 不一致)
- **建议方案**coord 统一裁定最终枚举值。若采用 president 的 SUBJECT学科级数据范围K12 场景更实用),需同步修改源码 schema + 004 §5.3 表 + 02 文档;若保留 DISTRICT需修正 president §3.2。同时修正 004 行 505 的语义命名使其与枚举表一致。
- **状态**:待 coord 仲裁
### ISSUE-005-ai06iam.proto 仅 4 RPC未补全至 12 RPC
- **提请方**ai06
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**coord-final-decisions §5.1 要求 iam.proto 在"P2 启动前"补全 8 个新 RPCGetViewports / GetEffectivePermissions / GetEffectiveAccess / Logout / GetPublicKey / BatchGetUsers / GetEffectiveDataScope / GetChildrenByParentpresident §6 批次 0 任务 0.3 也明确"coord 补全 iam.proto 8 RPC"。但实际 [iam.proto](../../../packages/shared-proto/proto/iam.proto) 仍仅 4 RPCRegister/Login/RefreshToken/GetUserInfo未补全。这阻塞 iam P2.1 的 8 RPC 实现proto 是契约先行前提)。
- **建议方案**coord 立即补全 iam.proto 至 12 RPC含对应 message 定义),对齐 [iam_contract.md §1.1](../contracts/iam_contract.md) 的 12 RPC 清单。
- **状态**:待 coord 仲裁
### ISSUE-006-ai0601/02 文档 AI 身份署名错误
- **提请方**ai06
- **日期**2026-07-10
- **类型**:其他
- **描述**[01-understanding.md](../../../services/iam/docs/01-understanding.md) 和 [02-architecture-design.md](../../../services/iam/docs/02-architecture-design.md) 署名"AIai02TS / 身份认证)"、"AI Agent: ai02 (iam-module)"。但 coord-final-decisions §3.1、president §3.2、[matrix.md](../matrix.md)、[workline.md](../workline.md)、[iam_workline.md](../worklines/iam_workline.md)、[iam_contract.md](../contracts/iam_contract.md) 全部指明 iam 由 **ai06** 负责。ai02 实际负责 push-gatewaycoord-final-decisions §3.7)。文档署名错误会导致多 AI 协作时身份混淆。
- **建议方案**:回写时将 01/02 文档署名从 ai02 改为 ai06。
- **状态**:待 coord 仲裁
### ISSUE-007-ai06contract.md §1.2 与双入口策略冲突
- **提请方**ai06
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**[iam_contract.md §1.2](../contracts/iam_contract.md) 写"无对外 HTTP 端点,仅 gRPC"。但 president §2.16 裁决双入口策略REST 供 gateway 透传 + gRPC 供 BFF 聚合调用,"gateway 保持 HTTP 透传"。若 iam 不暴露 HTTP 端点gateway 无法透传gateway 不改为 gRPC 客户端)。
- **建议方案**:修正 contract.md §1.2,补充 REST 端点清单(/iam/v1/* 系列),标明"REST 供 gateway 透传 + admin-portal 直连gRPC 供 BFF 聚合调用"。
- **状态**:待 coord 仲裁
---
## 问题列表
<!--
@@ -21,4 +139,4 @@
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)
见上方 §2 新提请异议ISSUE-001 ~ ISSUE-007

View File

@@ -6,19 +6,189 @@
---
## 问题列表
## §0 已有仲裁核查ai10 复核)
<!--
追加条目格式:
> 本节核查 01-understanding.md / 02-architecture-design.md 中引用的已有仲裁,对照源码/proto 验证准确性。
### ISSUE-[编号]-[AI标识][标题]
### ISSUE-001-ai10M8 ZodError 已修复 — 核查通过
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**待 coord 仲裁 / 已裁决(见 coord.md §X
-->
- **提请方**ai10
- **日期**2026-07-10
- **类型**仲裁核查
- **描述**01-understanding.md M8 称"GlobalErrorFilter 已识别 ZodError 返回 400"。核查 [global-error.filter.ts](../../../services/msg/src/shared/errors/global-error.filter.ts) L32-42`else if (exception instanceof ZodError) { statusCode = 400; ... }`,确实已处理,返回 `MSG_VALIDATION_ERROR` + 400。
- **核查结论**:✅ 仲裁准确M8 标记"已修复"无误
- **状态**已裁决(核查通过,无需处理
(暂无问题)
### ISSUE-002-ai10M12 proto 包名规范 — 核查通过
- **提请方**ai10
- **日期**2026-07-10
- **类型**:仲裁核查
- **描述**01-understanding.md M12 称"实际 `next_edu_cloud.msg.v1` 符合规范"。核查 [msg.proto](../../../packages/shared-proto/proto/msg.proto) L3`package next_edu_cloud.msg.v1;`,与 01-understanding.md 描述一致。
- **核查结论**:✅ 仲裁准确,但存在规则冲突(见 ISSUE-007
- **状态**:已裁决(核查通过)
### ISSUE-003-ai10M2 msg 必须有 Outbox — 核查通过
- **提请方**ai10
- **日期**2026-07-10
- **类型**:仲裁核查
- **描述**01-understanding.md M2 称"004 §7.2 明确 msg 生产 `edu.notification.events`msg 必须有 Outbox"。核查 004 §7.3 L638 `NotificationRequested` 事件Msg 投递通知到多渠道)+ known-issues §msg L346"Outbox 强制"。
- **核查结论**:✅ 仲裁准确msg 必须实现 OutboxP5 强制)
- **状态**:已裁决(核查通过)
### ISSUE-004-ai10core-edu 事件 topic 命名统一 — 核查部分通过
- **提请方**ai10
- **日期**2026-07-10
- **类型**:仲裁核查
- **描述**02-architecture-design.md §7.2 P11 称"coord 已仲裁采用 `edu.teaching.*` 新约定"。核查:
- 004 §7.2 L623-625 使用 `edu.teaching.assignment.submitted` / `edu.teaching.exam.published` / `edu.teaching.grade.recorded`(新约定)✅
- [events.proto](../../../packages/shared-proto/proto/events.proto) L9-13 注释仍用 `edu.exam.events` / `edu.homework.events`(旧约定)❌ 未同步
- [matrix.md](../matrix.md) §4 L112-115 使用 `edu.exam.events` / `edu.homework.events`(旧约定)❌ 未同步
- **核查结论**:⚠️ 仲裁已作出但未全量同步events.proto 注释与 matrix.md 仍用旧约定,需 coord 统一更新
- **状态**:待 coord 同步(见 ISSUE-008
### ISSUE-005-ai10Push Gateway 调用方向歧义 — 核查通过
- **提请方**ai10
- **日期**2026-07-10
- **类型**:仲裁核查
- **描述**02-architecture-design.md §7.2 P10 称"004 §4.1 写 PushGW→Msg实际是 Msg→PushGW"。核查 004 §4.1 L413`push-gateway → Msg | gRPC | 推送通道建立`,方向确实反了。实际流程是 msg 调 push-gateway 的 gRPC PushService.Push见 [notifications.service.ts](../../../services/msg/src/notifications/notifications.service.ts) L179 fetch POST /internal/push 降级实现)。
- **核查结论**:✅ 歧义确认,建议 coord 修正 004 §4.1 表述为"Msg → push-gateway (gRPC)"
- **状态**:待 coord 修正 004
---
## §1 新发现问题ai10 提请)
### ISSUE-006-ai10events.proto P9 字段描述不准确
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**02-architecture-design.md §7.2 P9 称"events.proto ExamEvent / HomeworkEvent / GradeEvent 需补 `class_id` / `student_ids[]` 字段"。核查 events.proto
- `ExamEvent` L32 **已有** `class_id` 字段 ✅
- `HomeworkEvent` L45 **已有** `class_id` 字段 ✅
- `GradeEvent` L50-59 **无** `class_id`(仅有 `student_id`)❌
- 三者均**无** `student_ids[]`(复数,用于 fan-out 广播)❌
- **建议方案**:修正 P9 表述为"`GradeEvent` 需补 `class_id`;全部事件需补 `student_ids[]` 字段msg fan-out 广播通知需要)"
- **状态**:待 coord 仲裁
### ISSUE-007-ai10proto 包名规则冲突project_rules vs 实际)
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**[project_rules §5](../../../.trae/rules/project_rules.md) 规定"包名规范:`edu.<domain>.v1`(如 `edu.iam.v1``edu.core_edu.v1`",但实际所有 proto 文件使用 `next_edu_cloud.<domain>.v1`(如 msg.proto L3 `next_edu_cloud.msg.v1`、events.proto L3 `next_edu_cloud.events.v1`。01-understanding.md 称"coord 已裁决采用 `next_edu_cloud.*`",但 project_rules §5 未同步更新,仍写 `edu.<domain>.v1`
- **建议方案**coord 统一裁决,二选一:
- 方案 A更新 project_rules §5 为 `next_edu_cloud.<domain>.v1`(与实际 proto 一致)
- 方案 B重命名所有 proto package 为 `edu.<domain>.v1`(与规则一致,但改动大)
- **状态**:待 coord 仲裁
### ISSUE-008-ai10Kafka topic 命名三套约定并存
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**msg 相关的 Kafka topic 命名存在三套约定:
- **约定 A004 §7.2per-event topic**`edu.identity.user.created` / `edu.teaching.exam.published` / `edu.notification.sent`
- **约定 Bmatrix.md §4 + events.proto 注释aggregate topic**`edu.iam.user.events` / `edu.exam.events` / `edu.notification.requested`
- **约定 Cmsg_contract.md §1.4aggregate topic + action 字段)**`edu.msg.notification.events`action: sent/read/recalled/failed
- 02-architecture-design.md §5.1/§5.2 采用约定 Amsg_contract.md §1.4 采用约定 Cmatrix.md 采用约定 B。known-issues §全局 L182 已标记此冲突。
- **建议方案**coord 统一为一套约定。ai10 倾向约定 Aper-event topic理由
- 004 §7.2 已采用,是架构设计意图唯一源
- per-event topic 便于消费者按需订阅,避免反序列化无关事件
- 与 NotificationSent / NotificationRead 等事件命名PascalCase对齐
- **状态**:待 coord 仲裁
### ISSUE-009-ai10RPC 数量超预算17 vs 13
- **提请方**ai10
- **日期**2026-07-10
- **类型**:工作量超批
- **描述**[ai-allocation.md §3.2](../../ai-allocation.md) L112 与 [matrix.md](../matrix.md) §2 L90 均规定 msg 为"3 Service 13 RPC"。但 02-architecture-design.md §4.2 列出 17 RPC
- NotificationService 9 RPCSendNotification / BatchSendNotification / ListNotifications / GetUnreadCount / MarkAsRead / BatchMarkAsRead / MarkAllAsRead / SearchNotifications / RecallNotification
- NotificationPreferenceService 2 RPCGetPreferences / UpdatePreferences
- NotificationTemplateService 6 RPCCreateTemplate / GetTemplate / ListTemplates / UpdateTemplate / DeleteTemplate / RenderTemplate
- 而 msg_contract.md §1.1 列出 13 RPC分布不同5+4+4两文档互相不一致
- **建议方案**coord 裁决 RPC 范围,二选一:
- 方案 A维持 13 RPC 预算02-architecture-design.md 裁剪至 13移除 BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / UpdateTemplate / DeleteTemplate降级为 REST only 或合并)
- 方案 B放宽至 17 RPC同步更新 ai-allocation.md + matrix.md + msg_contract.md
- **状态**:待 coord 仲裁
### ISSUE-010-ai10markAsRead 权限点与设计不一致
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**[notifications.controller.ts](../../../services/msg/src/notifications/notifications.controller.ts) L74 markAsRead 使用 `MSG_NOTIFICATION_MANAGE` 权限,但 02-architecture-design.md §6.1 L730 规定 markAsRead 应使用 `MSG_NOTIFICATION_READ`。MANAGE 权限通常给管理员,学生标记自己通知已读不应需要 MANAGE 权限。
- **建议方案**:以 02-architecture-design.md §6.1 为准READP5 实现时修正 controller 权限点
- **状态**:待 coord 确认
### ISSUE-011-ai10DB→ES 降级方向与 ai-allocation §5 相反
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- [ai-allocation.md §5](../../ai-allocation.md) ai10 设计重点要求"ES 降级查询策略(**DB 不可用时走 ES 索引**"——即 DB 故障时 ES 作为读模型兜底
- 02-architecture-design.md §3.2.2 / §3.4 描述"**ES 不可用时降级到 MySQL LIKE 查询**"——即 ES 故障时 DB 兜底
- 01-understanding.md M16 称"无 DB→ES 降级读路径"
- 三处描述方向相反,需统一
- **建议方案**ai10 倾向双向降级(两种故障场景都覆盖):
- ES 故障 → DB LIKE 查询(设计文档已覆盖)
- DB 故障 → ES 只读模式ai-allocation 要求,设计文档需补充)
- 但 DB 故障时写操作无法降级(必须等 DB 恢复),仅读操作可走 ES
- **状态**:待 coord 仲裁
### ISSUE-012-ai10设计文档缺 DLQ 与三层幂等防线
- **提请方**ai10
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**[known-issues §msg](../../../docs/troubleshooting/known-issues.md) L344 / L356 已记录两项 ai10 设计点,但 02-architecture-design.md 未覆盖:
- **三层幂等防线**L344L1 Redis SETNX / L2 msg_idempotency 表 / L3 notifications.source_event_id 唯一索引。设计文档 §5.5 仅描述两层Redis + DB UNIQUE缺中间层 msg_idempotency 表
- **死信队列**L356消费失败超 3 次投递 `edu.notification.dlq`。设计文档 §5 完全未提及 DLQ 设计
- **建议方案**02-architecture-design.md 补充:
- §3.1 补 `msg_idempotency` 表 schema中间层
- §5.5 改为三层幂等防线
- §5 补 DLQ 设计(重试 3 次后投递 `edu.notification.dlq` + 告警)
- **状态**:待 coord 确认非阻塞ai10 自行补充设计文档即可)
### ISSUE-013-ai10events.proto 缺 4 类 message 阻塞 msg 消费
- **提请方**ai10
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**[events.proto](../../../packages/shared-proto/proto/events.proto) 仅有 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent 4 个 message。msg 消费还需要:
- `UserEvent`iam 发布 user.created/updated/deleted/role_changed— 阻塞欢迎通知/角色变更通知
- `RoleEvent`iam 发布 role.created/updated— 阻塞角色变更通知
- `MasteryEvent`data-ana 发布 mastery.updated— 阻塞学情预警通知
- `NotificationEvent`msg 发布 notification.sent/read/recalled/failed— 阻塞 push-gateway 消费 msg 事件
- **建议方案**coord 维护 shared-proto在 P5 启动前补齐这 4 个 message。msg 在 proto 补齐前用通用 JSON payload 解析A6 假设)
- **状态**:待 coord 仲裁(🔴 阻塞 P5 消费链路)
### ISSUE-014-ai10msg_contract.md 与 02-architecture-design.md RPC 清单不一致
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:两文档 RPC 清单存在差异:
- msg_contract.md 独有02 缺GetPreferenceByChannel、ListPreferences
- 02-architecture-design.md 独有contract 缺BatchSendNotification、GetUnreadCount、BatchMarkAsRead、MarkAllAsRead、UpdateTemplate、DeleteTemplate
- 即使忽略 ISSUE-009 的数量问题,两文档的 RPC 组合也不同
- **建议方案**:待 ISSUE-009 仲裁后,统一两文档 RPC 清单
- **状态**:待 coord 仲裁(依赖 ISSUE-009
### ISSUE-015-ai10msg_contract.md Kafka 发布事件与设计文档不一致
- **提请方**ai10
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- msg_contract.md §1.4`edu.msg.notification.events` topic单一 NotificationEvent 含 action 字段
- 02-architecture-design.md §5.24 个 per-event topic`edu.notification.sent` / `edu.notification.read` / `edu.notification.recalled` / `edu.notification.failed`
- matrix.md §4 L118`edu.notification.requested`(第三种命名)
- **建议方案**:待 ISSUE-008 仲裁 topic 命名约定后统一
- **状态**:待 coord 仲裁(依赖 ISSUE-008

View File

@@ -6,8 +6,165 @@
---
## §0 已有仲裁核查结论ai05 复审2026-07-10
> 对 parent-bff 相关的已仲裁决策(用户 U1-U4 + coord C1-C6 + coord-final-decisions I6逐项核查执行情况。
### 0.1 核查通过项(已正确执行)
| 仲裁 | 主题 | 核查结论 |
| --- | --- | --- |
| U2 | push-gateway 豁免 gRPCHTTP /internal/push | ✅ 02 §5.3/§7.1 已执行 HTTP 调用 push-gateway |
| C2 | 端口 3010不暴露 gRPC | ✅ 02 §7.2 + matrix.md §3 已执行 |
| C3 | core-edu 错误码 CORE_EDU_* | ✅ 02 §6.2 已执行 |
| C5 | Kafka topic edu.notification.sent/read/recalled/failed | ✅ 02 §5.2 已执行004 §7.2 同步属 coord 待办 #7,不阻塞 parent-bff |
### 0.2 核查发现的问题(仲裁已裁决但文档未同步/执行有偏差)
以下问题均为"仲裁结论正确,但相关文档未同步"或"文档内部不一致",提请 coord 确认处理方式。
---
## 问题列表
### ISSUE-001-ai0501-understanding.md 未同步 ai05 接手与多项仲裁
- **提请方**ai05
- **日期**2026-07-10
- **类型**:文档同步缺失
- **描述**01-understanding.md 头部仍标 "AI 标识ai04"、"状态:待 coord 审核",未反映 ai05 已正式接手ai-allocation.md §3.2)。同时以下仲裁已裁决但 01 未同步:
- U3GraphQL P2 引入01 §4.1 仍建议"P4 先对齐 teacher-bff REST 现状",与 U3 仲裁冲突
- U4BFF 豁免 @RequirePermission01 §6 表格"权限装饰器"行标"⚠️ 不对齐",未引用 U4 仲裁
- C1错误码前缀 BFF_PARENT_01 §3.3 仍用 `PARENT_BFF_` 旧前缀
- **建议方案**01-understanding.md 头部更新为 ai05 复审版,同步 U3/U4/C1 仲裁结论;或由 coord 确认 01 作为"阶段 1 历史快照"保留原样,以 02 为准
- **状态**:待 coord 仲裁
### ISSUE-002-ai0502 文档内部 ChildGuard 缓存 TTL 不一致
- **提请方**ai05
- **日期**2026-07-10
- **类型**:文档内部不一致
- **描述**02-architecture-design.md 内部 ChildGuard 绑定列表缓存 TTL 三处不一致:
- §3.1.1 Redis 缓存 Schema 表:写 "60s"coord 推断原值)
- §9 #2 ai05 review 结论:调整为 "30s + 主动失效"
- §13 #7 黄金模板对齐表:写 "30s"
§3.1.1 表格未同步 §9 的调整,导致同一文档内 60s 与 30s 并存。
- **建议方案**§3.1.1 表格 ChildGuard 行 TTL 改为 "30s",与 §9 #2 + §13 #7 一致
- **状态**:待 coord 仲裁ai05 建议直接修正,属于文档勘误)
### ISSUE-003-ai05contract.md 仲裁引用编号错误I3 → I6
- **提请方**ai05
- **日期**2026-07-10
- **类型**:契约引用错误
- **描述**contracts/parent-bff_contract.md §2.1 表格 + §3.1 引用 "I3/ISSUE-047 裁决" 作为 GetChildrenByParent 的仲裁依据。但核查 coord-final-decisions.md 发现:
- I3 是 "PermissionGuard 本地 map → DB 驱动" 裁决,与家长-学生关联无关
- I6 才是 "家长-学生关联P2 即补全 iam_student_guardians 表 + GetChildrenByParent RPC" 裁决
- 全仓库未检索到 "ISSUE-047" 编号grep 无结果),疑为虚构编号
- **建议方案**contract.md 将 "I3/ISSUE-047 裁决" 修正为 "I6 裁决coord-final-decisions.md §1"
- **状态**:待 coord 仲裁ai05 建议直接修正,属于引用勘误)
### ISSUE-004-ai05004 §4 服务依赖图与 matrix.md §1 未同步 C6 仲裁
- **提请方**ai05
- **日期**2026-07-10
- **类型**:架构图未同步
- **描述**C6 仲裁将 parent-bff 依赖扩展为 iam + core-edu + data-ana + msg
- 004_architecture_impact_map.md §4 服务依赖图line 376-377仍只画 `PBFF --> IAM` + `PBFF --> CoreEdu`,未加 DataAna + Msg
- matrix.md §1 服务依赖矩阵line 64-65同样只画 `PBFF --> IAM` + `PBFF --> CORE`
- 02 §0.1 C6 行已标注 "004 §4 待 coord 同步更新",但至今未同步
此差异导致新接手的 AI 看 004/matrix 会误以为 parent-bff 不依赖 data-ana/msg与 02 设计冲突。
- **建议方案**coord 在 004 §4 服务依赖图补 `PBFF --> DataAna` + `PBFF --> Msg`matrix.md §1 同步;更新 004 时按 project_rules §1 "改码必同步图" 执行
- **状态**:待 coord 仲裁
### ISSUE-005-ai05parent-portal 01 文档与 parent-bff GraphQL 决策跨模块冲突
- **提请方**ai05
- **日期**2026-07-10
- **类型**:跨模块契约冲突
- **描述**U3 仲裁决定 parent-bff P4 直接用 GraphQL02 §4 已执行),但 parent-portal 01-understanding.md §3.1 仍按 REST 设计消费 parent-bff
- parent-portal §3.1 列 `GET /parent/viewports``GET /parent/dashboard``GET /parent/children` 等 REST 端点
- parent-bff 02 §4.1 明确 "不实现 REST 业务端点(仅保留 /healthz /readyz /metrics"
- parent-portal §3.1 还引用 `GET /iam/effective-permissions`C4 仲裁已改为 `/iam/permissions/effective`
- parent-portal §3.1 标 "BFF 对接parent-bffai04 设计)"ai04 已过时(现 ai05
此冲突若不解决parent-portalai15会按 REST 实现 frontend client与 parent-bff GraphQL 端点不兼容。
- **建议方案**coord 协调 ai15 将 parent-portal 01/02 文档的 parent-bff 消费契约从 REST 改为 GraphQL`POST /api/v1/parent/graphql`),同步 C4 iam 路径仲裁;此属跨模块契约,按 project_rules §14.4 跨模块变更顺序处理
- **状态**:待 coord 仲裁
### ISSUE-006-ai05proto 包名引用不一致(缺失 next_edu_cloud 前缀)
- **提请方**ai05
- **日期**2026-07-10
- **类型**:契约引用错误
- **描述**02-architecture-design.md §3.3 DTO 映射表引用 proto message 为 `iam.v1.UserInfo``core_edu.v1.Grade[]``analytics.v1.StudentWeakness``msg.v1.Notification[]`。但实际 proto 文件包名均带 `next_edu_cloud.` 前缀:
- iam.proto: `package next_edu_cloud.iam.v1;`
- core_edu.proto: `package next_edu_cloud.core_edu.v1;`
- analytics.proto: `package next_edu_cloud.analytics.v1;`
- msg.proto: `package next_edu_cloud.msg.v1;`
引用不一致会导致 gRPC client 代码生成时 package 路径错误。
- **建议方案**02 §3.3 DTO 映射表 proto message 列全部补 `next_edu_cloud.` 前缀;或确认是否统一去掉前缀(需 buf.yaml 配置一致)
- **状态**:待 coord 仲裁
### ISSUE-007-ai05GraphQL Notification.childId 字段在 msg.proto 缺失
- **提请方**ai05
- **日期**2026-07-10
- **类型**:契约缺口
- **描述**02 §4.2 GraphQL schema 定义 `Notification` type 含 `childId: ID` 字段(家长场景需知道通知关联哪个孩子)。但 msg.proto 的 Notification message 无 childId 字段:
```proto
message Notification {
string id = 1;
string user_id = 2;
string type = 3;
string title = 4;
string content = 5;
string channel = 6;
bool is_read = 7;
int64 created_at = 8;
}
```
parent-bff 无法从 msg 服务获取通知关联的孩子 ID影响"按孩子过滤通知"场景。
- **建议方案**coord 协调 ai10 在 msg.proto Notification message 补 `string child_id = 9;` 字段(可选,非家长通知为空);或 parent-bff 从 notification.content 解析(脆弱,不推荐)
- **状态**:待 coord 仲裁
### ISSUE-008-ai05core_edu.proto 缺 ClassService02 §7.1 列为已有
- **提请方**ai05
- **日期**2026-07-10
- **类型**:契约缺口
- **描述**02 §7.1 交互矩阵列 `core-edu ClassService.GetClass`(查孩子班级信息)状态为 "✅ 已有"。但核查 core_edu.proto 实际只有 ExamService / HomeworkService / GradeService 三个 service无 ClassService。matrix.md §2 却声称 core-edu 有 "ClassService + ExamService + HomeworkService + GradeService + AttendanceService" 共 22 RPC。proto 与 matrix.md 不一致,且 02 错误标注为"已有"。
core_edu.proto 的 Grade.score 是 string 类型02 GraphQL Grade.score 是 Float!string→Float 转换规则未在 §3.3 说明。
- **建议方案**
1. coord 确认 ClassService 归属core-edu 还是 classes 服务),补 proto
2. 02 §7.1 ClassService.GetClass 状态从 "✅ 已有" 改为 "❌ 待补"
3. 02 §3.3 补 Grade.score string→Float 转换规则说明
- **状态**:待 coord 仲裁
### ISSUE-009-ai05ai-allocation iam 责任方与 01/02 文档不一致
- **提请方**ai05
- **日期**2026-07-10
- **类型**:责任方引用过时
- **描述**01-understanding.md §7.1/§7.3 多处提"推动 ai02 在 iam 补接口"02 §8.1 P0 阻塞项 + §14.1 P0-1 也标 iam 责任方为 "ai06iam 现归属)" 但 §7.3 #1 仍标 ai02。实际 ai-allocation.md §3.2 确认 iam 归属 ai06。01 文档未同步。
- **建议方案**01 §7.1/§7.3 将 "ai02" 改为 "ai06"02 §7.3 #1 同步
- **状态**:待 coord 仲裁ai05 建议直接修正,属于引用勘误)
### ISSUE-010-ai0502 缺少 ADR / NFR / 容量规划 / 威胁建模(业界规范差距)
- **提请方**ai05
- **日期**2026-07-10
- **类型**:架构文档规范缺失
- **描述**:对照业界通用架构文档规范(如 C4 model + ADR + NFR02-architecture-design.md 存在以下规范差距:
1. **ADR 缺失**:虽有"已仲裁决策"表,但未按 ADR 格式Context/Decision/Consequences记录关键决策如 GraphQL vs REST、ChildGuard 位置、多子女切换方案)。建议补 ADR 索引章节。
2. **NFR 未量化**§10.4 P6 提"SLO 监控"但文档前部未明确非功能性需求P95 延迟、可用性、吞吐量目标)。业界规范要求架构文档开头列 NFR。
3. **容量规划缺失**:未估算家长端 QPS、并发数、数据量家长数 × 孩子数 × 成绩数),无法指导 HPA 副本数和 Redis 容量规划。
4. **安全威胁建模缺失**§6.2 列错误码但未做威胁建模STRIDE。家长场景涉及未成年人数据COPPA/FERPA/PIPL应补威胁模型。
5. **数据流图DFD缺失**§1.2 只有 Dashboard 时序图,缺少 DFD 展示数据跨信任边界流动。
- **建议方案**coord 确认是否在 02 补全上述章节,或作为 P6 硬化阶段补全;当前不阻塞 P4 实施
- **状态**:待 coord 仲裁
---
<!--
追加条目格式:
@@ -20,5 +177,3 @@
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)

View File

@@ -6,6 +6,33 @@
---
## §0 已有仲裁核查ai15 复核 ARB-001 / ARB-002 落地情况)
> ai15 接管 parent-portal 后,核查 coord 已发布的两项仲裁ARB-001 teacher-bff GraphQL schema、ARB-002 MF Shell 暴露清单)在 parent-portal 文档中的落地情况。
### 0.1 ARB-001teacher-bff GraphQL schema 第一版)核查
| 核查项 | ARB-001 结论 | parent-portal 落地情况 | 状态 |
| ------ | ------------ | ---------------------- | ---- |
| BFF 用 GraphQL非 REST | ✅ 已裁决 GraphQL | 01-understanding §3.1 + 02-architecture-design §4.1 全部描述为 REST 消费 | ❌ 未落地 |
| ActionState 信封 | ✅ 已裁决 | 01 §3.2 已对齐 | ✅ |
| 错误码前缀路由 | ✅ 已裁决 | 01 §3.2 + 02 §6.2 已对齐 | ✅ |
**结论**ARB-001 的核心裁决BFF = GraphQL在 parent-portal 的 01/02 文档中**未落地**01 §3.1 与 02 §4.1 仍按 REST 编写,与 [parent-bff_contract.md](../contracts/parent-bff_contract.md) §1.3GraphQL 端点 :3010和 [matrix.md](../matrix.md) §3parent-bff GraphQL直接冲突。提请 ISSUE-001。
### 0.2 ARB-002MF Shell 暴露清单)核查
| 核查项 | ARB-002 结论 | parent-portal 落地情况 | 状态 |
| ------ | ------------ | ---------------------- | ---- |
| Shell 暴露 GraphQLProvider | ✅ 已裁决 | 01 §4 技术栈未列 urql/GraphQL client02 §4.1 用 `useApi()`REST ApiClient而非 `useGraphQLClient()` | ❌ 未落地 |
| MF shared 含 urql/graphql/@edu/* | ✅ 已裁决 | 02 §1.2 `shared` 仅列 react/react-dom/@tanstack/react-query/zustand/nuqs缺 urql/graphql/@edu/ui-tokens/@edu/ui-components/@edu/hooks | ❌ 未落地 |
| Shell 暴露 AppShell | ✅ 已裁决 | 01 §9.1 + 02 §7.1 已对齐 | ✅ |
| feature flag NEXT_PUBLIC_MF_ENABLED | ✅ 已裁决 | 01/02 均未提及 | ❌ 未落地 |
**结论**ARB-002 关于 GraphQL client 与 MF shared 的裁决在 parent-portal 文档中**部分未落地**。提请 ISSUE-002。
---
## 问题列表
<!--
@@ -21,4 +48,164 @@
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)
### ISSUE-001-ai1501/02 文档 REST 消费 parent-bff 与 ARB-001 GraphQL 裁决冲突
- **提请方**ai15
- **日期**2026-07-10
- **类型**:契约不明确(文档与已裁决架构冲突)
- **描述**
- 01-understanding.md §3.1 列出 parent-portal 经 REST 消费 parent-bff`GET /parent/viewports``GET /parent/children``POST /parent/children/:childId/select``GET /parent/notifications``PUT /parent/notification-preferences`
- 02-architecture-design.md §4.1 `useParentApi` 实现全部基于 `api.get()`/`api.post()` REST 调用
- 但 ARB-001coord.md §1已裁决 BFF 用 GraphQL[parent-bff_contract.md](../contracts/parent-bff_contract.md) §1.3 明确 parent-bff 提供 `POST /graphql`:3010[matrix.md](../matrix.md) §3 确认 parent-bff = GraphQL
- parent-portal 自己的 [contract.md](../contracts/parent-portal_contract.md) §2.3-2.4 也写明消费 GraphQL`POST /api/parent/graphql`Query 域currentUser/myChildren/childSummary/childGrades 等)
- **文档内部自相矛盾**01/02 用 RESTcontract.md 用 GraphQL
- **建议方案**
1. coord 确认 parent-portal 消费 parent-bff **统一用 GraphQL**(与 ARB-001、parent-bff contract、matrix.md 一致)
2. ai15 据此修订 01 §3.1(改为 GraphQL Query/Mutation 域、§3.1.1X-Fields 字段裁剪改为 GraphQL query 字段选择、02 §4.1`useParentApi` 改为 GraphQL hooks、§4.2TanStack Query 约定配合 GraphQL operations、§11.3 未决设计决策 #2(移除,已裁决)
3. 若 coord 另有裁决(如 parent-portal 特殊走 REST以 coord 裁决为准
- **状态**:待 coord 仲裁
### ISSUE-002-ai15MF shared 配置缺 urql/graphql/@edu/* 与 ARB-002 冲突
- **提请方**ai15
- **日期**2026-07-10
- **类型**:契约不明确(文档与已裁决 MF 配置冲突)
- **描述**
- 02-architecture-design.md §1.2 MF `shared` 配置仅列:`react``react-dom``@tanstack/react-query``zustand``nuqs`
- ARB-002coord.md §2裁决的 `shared` 应包含:`react``react-dom``urql``graphql``@edu/ui-tokens``@edu/ui-components``@edu/hooks`
- 缺失 `urql`/`graphql` 会导致 Remote 与 Shell 各加载一份 GraphQL client 实例,破坏单例,引发缓存不一致与重复请求
- 缺失 `@edu/*` 会导致设计令牌/UI 组件/Hooks 各加载一份
- 同时 01 §4 技术栈表未列 GraphQL clienturql与 ARB-002 Shell 暴露 GraphQLProvider 矛盾
- **建议方案**
1. coord 确认 parent-portal MF `shared` 必须包含 ARB-002 全部 7 项react/react-dom/urql/graphql/@edu/ui-tokens/@edu/ui-components/@edu/hooks
2. ai15 修订 02 §1.2 `shared` 配置 + 01 §4 技术栈表(新增 urql + GraphQL client 行)
3. 02 §4.1 `useParentApi` 改为从 Shell 暴露的 `useGraphQLClient()` 获取 urql client不再用 REST ApiClient
- **状态**:待 coord 仲裁
### ISSUE-003-ai15switch-child 端点在 01/02 文档间不一致
- **提请方**ai15
- **日期**2026-07-10
- **类型**:契约不明确(文档内部不一致)
- **描述**
- 01-understanding.md §3.1 列 `POST /parent/children/:childId/select`
- 02-architecture-design.md §2.2 + §4.1 用 `POST /api/v1/parent/switch-child`body 携带 childId
- 两处路径与语义均不一致URL param vs body param
- parent-bff contract.md 未列 switch-child其 §1.3 仅列 Query/Mutation 域,未细到 switch-child
- **建议方案**
1. 若走 GraphQL依 ISSUE-001 裁决switch-child 应为 `mutation switchChild(childId: ID!): SwitchChildPayload!`,不存在 REST 路径
2. 若走 REST统一为 `POST /api/v1/parent/switch-child`body 携带 childId与 02 一致),修订 01 §3.1
3. 请 coord 一并明确 parent-bff GraphQL schema 是否包含 `switchChild` Mutation当前 parent-bff contract.md 未列)
- **状态**:待 coord 仲裁
### ISSUE-004-ai15登录端点在 01 / contract.md / matrix.md 间三方不一致
- **提请方**ai15
- **日期**2026-07-10
- **类型**:契约不明确(跨文档不一致)
- **描述**
- 01-understanding.md §3.1 列 `POST /iam/login`
- parent-portal_contract.md §2.3 列 `POST /api/auth/login`
- matrix.md §5 规范 iam 经 api-gateway 代理路径为 `/api/v1/iam/*`
- 三处不一致,且 contract.md 的 `/api/auth/login` 路径在 matrix.md 中不存在
- **建议方案**
1. 统一为 `POST /api/v1/iam/login`(与 matrix.md §5 + 01 §3.1 的 `/api/v1/iam/*` 前缀一致)
2. ai15 修订 contract.md §2.3 路径
3. 注意:登录是 parent-portal 唯一可能走 REST非 GraphQL的端点因登录前无 JWTGraphQL endpoint 需鉴权。请 coord 确认登录是否走 REST `/api/v1/iam/login`,其余走 GraphQL
- **状态**:待 coord 仲裁
### ISSUE-005-ai1502 §11.3 未决设计决策 #2 "GraphQL vs REST" 已由 ARB-001 裁决,应移除
- **提请方**ai15
- **日期**2026-07-10
- **类型**:其他(文档过时)
- **描述**
- 02-architecture-design.md §11.3 第 2 项将 "GraphQL vs REST" 列为未决设计决策,建议 "P4 用 REST后续若 BFF 切 GraphQL 再引入 urql"
- 但 ARB-001coord.md §1已于 2026-07-09 裁决 BFF 用 GraphQL且 parent-bff contract.md 确认 GraphQL
- 此项已过时,会误导后续开发
- **建议方案**
1. coord 确认 ARB-001 适用于 parent-portal即 parent-portal P4 起必须用 GraphQL 消费 parent-bff
2. ai15 移除 02 §11.3 第 2 项,改为 "已裁决:见 ARB-001parent-portal 用 GraphQL 消费 parent-bff"
- **状态**:待 coord 仲裁
### ISSUE-006-ai15contract.md §1.2 将前端页面路由误标为 HTTP 端点
- **提请方**ai15
- **日期**2026-07-10
- **类型**:其他(文档分类错误)
- **描述**
- parent-portal_contract.md §1.2 "HTTP 端点" 列出:`GET /``GET /children``GET /child/:id/summary``GET /child/:id/grades`
- 但 parent-portal 是 Next.js 前端应用,这些是**前端页面路由**SSR/CSR 路由),不是对外 HTTP API 端点
- 将页面路由放在 "HTTP 端点" 表中会误导下游消费方以为这些是 REST API
- 且这些路由与 01 §8 L2 路由表(`/parent/dashboard``/parent/children`路径还不一致contract 用 `/children`01 用 `/parent/children`
- **建议方案**
1. coord 确认 parent-portal 作为前端 Remote不对外提供 HTTP API 端点§1.2 应为"无"
2. ai15 将 contract.md §1.2 改为 "无parent-portal 是前端 Remote不对外提供 HTTP API",页面路由信息保留在 01 §8 L2 路由表中,不进 contract.md
3. 若需保留 MF 暴露信息,归入 §1.6 微前端架构(已有)
- **状态**:待 coord 仲裁
### ISSUE-007-ai15contract.md §1.6 module-federation.config.ts 与 02 next.config.js 不一致
- **提请方**ai15
- **日期**2026-07-10
- **类型**:其他(文档内部不一致)
- **描述**
- parent-portal_contract.md §1.6 列 MF 配置文件为 `apps/parent-portal/module-federation.config.ts`
- 02-architecture-design.md §1.2 MF 配置写在 `next.config.js` 中(用 `NextFederationPlugin`
- 两处文件名与位置不一致
- **建议方案**
1. 统一为 `apps/parent-portal/next.config.js`(与 02 + teacher-portal Shell 一致Next.js 项目 MF 配置应在 next.config.js
2. ai15 修订 contract.md §1.6
- **状态**:待 coord 仲裁
### ISSUE-008-ai15contract.md §2.3 GraphQL 路径前缀与 matrix.md 不一致
- **提请方**ai15
- **日期**2026-07-10
- **类型**:契约不明确(跨文档不一致)
- **描述**
- parent-portal_contract.md §2.3 列 `POST /api/parent/graphql`
- matrix.md §5 规范 api-gateway 代理 parent-bff 路径为 `/api/v1/parent/*`
-`v1` 版本号
- **建议方案**
1. 统一为 `POST /api/v1/parent/graphql`(与 matrix.md §5 一致)
2. ai15 修订 contract.md §2.3 + §2.4
- **状态**:待 coord 仲裁
### ISSUE-009-ai15parent-bff GraphQL schema 是否包含 switchChild Mutation 未明确
- **提请方**ai15
- **日期**2026-07-10
- **类型**:前置依赖缺失(上游契约不全)
- **描述**
- parent-bff_contract.md §1.3 列出的 Query/Mutation 域未包含 "switchChild"(切换当前选中子女)
- 01-understanding.md §2.2 + 02 §2.2 描述 parent-portal 需调用 `POST /parent/switch-child` 切换子女
- 若走 GraphQL依 ISSUE-001parent-bff 需提供 `mutation switchChild(childId: ID!): SwitchChildPayload!`
- 但 parent-bff contract 未列此 Mutation且 iam.GetChildrenByParent 已返回子女列表,切换子女是否需后端记录(还是纯前端 localStorage需明确
- **建议方案**
1. 请 coord 协调 ai05parent-bff确认switchChild 是 GraphQL Mutation 还是纯前端状态localStorage + Zustand
2. 若纯前端01/02 移除 `POST /parent/switch-child` 调用,改为 `useChildSwitcher` 直接写 Zustand + localStorage
3. 若需后端记录:请 ai05 在 parent-bff contract.md §1.3 补充 `switchChild` Mutation
- **状态**:待 coord 仲裁
### ISSUE-010-ai15iam GetChildrenByParent 接口缺失P0 阻塞,跨模块)
- **提请方**ai15
- **日期**2026-07-10
- **类型**:前置依赖缺失(跨模块,承自 parent-bff §7.1
- **描述**
- 01-understanding.md §3.1 注明iam 缺失 "家长-学生关联查询" 接口(`GetChildrenByParent` proto + `GET /iam/children` REST + `iam_student_guardians` 表三缺失)
- parent-bff_contract.md §2.1 也标注 "核心依赖 I3/ISSUE-047 裁决"
- parent-bff_contract.md §3.1 标注 "iam gRPC 50052 启用ai06—— 核心依赖 GetChildrenByParentI3/ISSUE-047 裁决)"
- 此为 parent-portal 多子女场景的 P0 阻塞项ai06iam需在 P3 收尾前补全
- ai15 在此提请,请 coord 跟踪 ai06 进度并确认补全时间点
- **建议方案**
1. coord 确认 ai06 补全 `GetChildrenByParent` 的时间点(应在 P4 启动前)
2. 在补全前parent-portal 用 mock固定 2 个子女开发mock 数据与 parent-bff mock 一致student-001 + student-002
- **状态**:待 coord 仲裁
---
## §1 已裁决问题
(暂无已裁决问题)

View File

@@ -1,24 +1,167 @@
# push-gateway 问题记录
> 负责人ai02
> 关联:[coord.md](../coord.md)、[contracts/push-gateway_contract.md](../contracts/push-gateway_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/push-gateway_contract.md](../contracts/push-gateway_contract.md)、[push-gateway 02 架构设计](../../../services/push-gateway/docs/02-architecture-design.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## 问题列表
## §0 已有仲裁核查ai02 复审 02 文档对总裁裁决的回写情况)
<!--
追加条目格式:
> 本节为 ai02 在批次 0 等待期对 president-final-rulings 已裁决事项的回写核查。
> 裁决来源:[president-final-rulings.md](../../president-final-rulings.md) §1.5 / §3.3 / §4.2 / §4.3 / §4.4 / §7.2 / §3.4
> 核查日期2026-07-10
> 核查结论5 项裁决中 **0 项已完全回写**、**1 项部分回写**、**4 项未回写**
### ISSUE-[编号]-[AI标识][标题]
### 核查矩阵
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
| 裁决编号 | 主题 | 裁决要求(摘要) | 02 文档现状 | 核查结论 | 状态 |
| -------- | ---- | ---------------- | ----------- | -------- | ---- |
| ISSUE-053 | Kafka topic 命名 | 02 §5.1 topic 改为 `edu.notification.requested`,禁止抽象名 `edu.*.events` | §5.1 仍写 `edu.notification.events` / `NotificationRequested` | ❌ 未回写 | 待 ai02 修复 |
| ISSUE-055 | /readyz 软失败 | push-gateway /readyz 对 Kafka 软失败(失败仅告警 + `degraded: true` + 返 200不返 503 | §6.7 仅 Redis PING 硬失败返 503无 Kafka 软失败逻辑 | ❌ 未回写 | 待 ai02 修复 |
| ISSUE-056 | 设计决策记录章节 | 02 §5.4 改名为"设计决策记录gRPC vs HTTP 协议选型coord 已采纳 P1",正文标注"coord 已采纳" | 02 无"设计决策记录"章节 | ❌ 未回写 | 待 ai02 修复 |
| ISSUE-058 | Redis SET 启动重建 | 02 §3.1 补充"Hub 启动时遍历内存连接 SADD + EXPIRE 60s + 清空旧 instanceID 成员"/readyz Redis 失败仅告警不阻塞metrics 暴露 `push_gateway_redis_set_rebuild_total`;文档化 60s 不一致窗口 | §3.1/§8.4 仅描述运行期 SADD/SREM无启动重建§6.7 Redis 硬失败返 503与"仅告警不阻塞"冲突);无重建指标;无 60s 窗口说明 | ❌ 未回写 | 待 ai02 修复 |
| ARB /internal/push 契约§4.2 | 第一版 /internal/push 契约 | coord "as-is" 采纳 ai02 02 §4.2 作为第一版契约,仅在 ai10 异议时调整 | 02 §4.2 已定义 `{user_id, event, data, ttl?}``{success, delivered, online}` | ✅ 已落地 | 无需动作 |
(暂无问题)
### 核查结论
- **ISSUE-053/055/056/058 共 4 项须 ai02 在批次 4 启动前回写到 02 文档**president-final-rulings §3.4 明确"批次 4 启动前"完成回写)
- **ARB /internal/push 契约已落地**,无需动作;但 ai10 若提出异议(如 batch 接口/异步回调coord 会公布差异点
- ISSUE-058 中"/readyz Redis 失败仅告警不阻塞"与 ISSUE-055"软失败规则"形成耦合Redis 作为 push-gateway 必需依赖本应硬失败,但 ISSUE-058 裁决要求"仅告警不阻塞"以避免雪崩 —— **此耦合需 coord 明确优先级**(见下方 ISSUE-006-ai02
---
## §1 问题列表(提请 coord 仲裁)
### ISSUE-001-ai02SSE 端点是否提供(跨文档三方冲突)
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**SSE 端点(`/sse`)在三个文档中存在冲突:
- [push-gateway_contract.md](../contracts/push-gateway_contract.md) §1.2 列出 `GET /sse` 端点,认证 JWT
- [matrix.md](../matrix.md) §5 HTTP 接口矩阵列出 `push-gateway (ai02) | SSE | /sse`
- [02-architecture-design.md](../../../services/push-gateway/docs/02-architecture-design.md) §10 明确建议"不支持 SSEWebSocket 已够用,避免协议膨胀"
- 01-understanding.md 完全未提及 SSE
- 实际代码无 `/sse` 实现,`gin-contrib/sse` 仅为 gin 间接依赖
- **建议方案**:采纳 02 文档建议 —— **push-gateway 不提供 SSE仅 WebSocket**。理由:
1. 单一协议降低维护成本与测试矩阵
2. SSE 单向下行 + 文本协议,不适合未来 reconnect/ack 双向协议
3. 各 portal 已规划 WebSocket 接入([coord-cross-review.md](../../coord-cross-review.md)
- **影响方**ai13/ai14/ai15前端需统一走 WebSocket移除 SSE 兜底、ai10msg 不需调 /sse、coord更新 matrix.md §5 与 contract.md
- **状态**:待 coord 仲裁
### ISSUE-002-ai02内部 API 鉴权命名三方不一致
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:内部 API 鉴权头与环境变量在四处不一致:
- **代码**[handler.go#L30](../../../services/push-gateway/internal/ws/handler.go#L30) + [config.go#L41](../../../services/push-gateway/internal/config/config.go#L41)`X-Internal-Key` 头 + `INTERNAL_API_KEY` 环境变量
- **02 文档** §4.2/§6.1`X-Internal-Token` 头 + `INTERNAL_API_TOKEN` 环境变量
- **ai-allocation.md** §5`X-Internal-Key`
- **president-final-rulings.md** §7.2"X-Internal-Token 重命名"(暗示应改为 Token
- **contract.md** §1.2`内网 mTLS`(第四种方案!)
- **建议方案**:统一为 `X-Internal-Token` + `INTERNAL_API_TOKEN`(对齐总裁裁决 §7.2)。理由:
1. 总裁裁决已明确倾向 Token 命名
2. "Token"语义比"Key"更准确(共享密钥而非公私钥对)
3. mTLS 在 P5 阶段引入成本过高,且 K8s 内网已有 NetworkPolicy 隔离,共享密钥足够
- **影响方**ai10msg 调用方需用相同头名、coord更新 contract.md 与 matrix.md §5 移除 mTLS
- **状态**:待 coord 仲裁
### ISSUE-003-ai02单节点容量目标 50k vs 10w+ 冲突
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:单节点最大连接数目标在两份文档冲突:
- [modules/push-gateway/README.md](../../../docs/modules/push-gateway/README.md) §7"单节点最大连接数 50k超出时拒绝新连接"
- [02-architecture-design.md](../../../services/push-gateway/docs/02-architecture-design.md) §11"单实例最大连接数 10w+"
- [01-understanding.md](../../../services/push-gateway/docs/01-understanding.md) §5 引用 pending-features"单节点支撑 10w+ 连接"
- **建议方案**:统一为 **10w+**(对齐 02 文档与 pending-features。理由
1. 10w+ 是 P5 设计目标pending-features 权威)
2. Go goroutine-per-connection + 64KB send chan 单连接约 20-30KB10w 连接约 2-3GB单节点可承载
3. 50k 目标过于保守,与横向扩展方案不匹配
- **影响方**coord更新 modules/README.md §7、ai0202 §11 已正确)
- **状态**:待 coord 仲裁
### ISSUE-004-ai02contract.md 内部端点路径 /internal/send vs /internal/push 冲突
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:内部单推端点路径在 contract.md 与代码/02 文档冲突:
- [contract.md](../contracts/push-gateway_contract.md) §1.2 + §2.4`POST /internal/send`
- **代码**[main.go#L58](../../../services/push-gateway/main.go#L58)+ **02 文档** §4.2`POST /internal/push`
- 总裁裁决 §4.2 已"as-is 采纳 ai02 02 §4.2",即应使用 `/internal/push`
- **建议方案**contract.md 统一改为 `POST /internal/push`(对齐总裁裁决与代码)。此为 ai02 自主回写范畴,不需 coord 仲裁动作,仅在此登记以便 coord 复核。
- **状态**ai02 自行修复(见 contracts 回写)
### ISSUE-005-ai02审计表6 字段)设计缺失
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**[ai-allocation.md](../../ai-allocation.md) §5 将"审计表6 字段)"列为 ai02 设计重点,但:
- 01-understanding.md §2 明确"不持有业务状态""无 DB"
- 02-architecture-design.md §3 明确"无数据库。所有状态在内存 + Redis"
- 两份文档均无审计表设计
- **疑问**:审计表是否要求 push-gateway 引入 MySQL/PostgreSQL这与"无 DB"定位冲突。可能的解读:
1. push-gateway 引入轻量审计表(如 SQLite/Redis Stream 持久化推送记录)
2. 审计表由 msg 服务维护msg 已落库push-gateway 仅通过 Kafka 事件回流
3. ai-allocation 表述过度,审计需求由 msg 满足
- **建议方案**:方案 2审计由 msg 维护push-gateway 仅同步返结果)。理由:保持 push-gateway 无 DB 定位,避免引入持久化层增加运维复杂度。
- **影响方**ai10msg 需确认审计字段是否覆盖 push-gateway 推送结果、coord澄清 ai-allocation §5 表述)
- **状态**:待 coord 仲裁
### ISSUE-006-ai02ISSUE-058 与 ISSUE-055 对 Redis /readyz 失败策略耦合冲突
- **提请方**ai02
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**:两份裁决对 push-gateway /readyz Redis 检查失败的策略存在表述冲突:
- **ISSUE-055**[president §3.3](../../president-final-rulings.md)):将 Redis 列为"必需依赖",失败返 503 触发 Pod 重启
- **ISSUE-058**[president §4.3](../../president-final-rulings.md)"/readyz Redis 检查失败时仅告警不阻塞,与 ISSUE-055 协调,避免雪崩"
- **冲突点**Redis 是 push-gateway 跨实例广播的必需依赖(必需 → 503但实例重启不能恢复 Redis 故障,且重启会丢失本地连接表加剧雪崩(应仅告警)
- **建议方案**:明确为 **Redis 软失败**(仅告警 + `degraded: true` + 返 200从 ISSUE-055 必需依赖列表中移除 push-gateway → Redis。理由
1. push-gateway 重启不解决 Redis 故障
2. Redis 故障时单实例仍能服务本地连接(仅跨实例广播失效)
3. 雪崩风险高于短暂不一致
- **影响方**coord澄清两裁决优先级
- **状态**:待 coord 仲裁
### ISSUE-007-ai0202 文档缺 ADR / 非功能性需求 / 失败模式章节(不符业界架构文档规范)
- **提请方**ai02
- **日期**2026-07-10
- **类型**:工作量超批
- **描述**02-architecture-design.md 不符合业界架构文档规范arc42 / C4 模型):
1. **无 ADR 章节**[modules/push-gateway/README.md](../../../docs/modules/push-gateway/README.md) §8 提到"待 P5 交付时补充 ADR 记录",但 02 文档未落地。关键决策gorilla/websocket 选型、Redis Pub/Sub vs Stream、心跳间隔 30s/60s 选型、10w 容量依据)无 ADR
2. **无非功能性需求章节**:无可用性 SLO如 99.9%)、安全合规、容量 SLA
3. **无失败模式/混沌工程章节**实例崩溃、Redis 故障、网络分区、Kafka 消费积压场景下的降级策略缺失
4. **§11 容量表无依据**10w 连接的内存/CPU/网络带宽估算缺失
- **建议方案**:在批次 4P5补全 02 文档 §14-§17 四个章节ADR / 非功能性需求 / 失败模式 / 容量估算。预估工作量1-1.5 天。
- **影响方**ai02自主补全
- **状态**:待 coord 确认是否纳入 P5 Must Have
---
## §2 已自主修复的文档偏差ai02 直接修复,不需 coord 仲裁)
> 以下为 01-understanding.md 与现码不符的偏差ai02 在批次 0 自主修复
| # | 位置 | 偏差 | 修复方向 |
| - | ---- | ---- | -------- |
| 1 | 01 §3.2 | `/readyz` 标"无(待实现)",实际已实现(仅未检查 Redis | 改为"已实现,仅返连接数,待补 Redis PING" |
| 2 | 01 §3.2 | `/metrics` 标"无(待实现)",实际已挂载 promhttp | 改为"已实现,待补自定义指标" |
| 3 | 01 §3.2 | `/internal/push` `/internal/broadcast` 标"待补鉴权",实际已实现 X-Internal-Key | 改为"已实现 X-Internal-Key 校验DevMode 跳过)" |
| 4 | 01 §6 + §7 | Dockerfile 标"❌ 单阶段",实际为多阶段(缺非 root/healthcheck/ldflags | 改为"⚠️ 多阶段但缺非 root + healthcheck + ldflags" |
| 5 | 01 §7.1 #6 | 引用 `main.go L44-46` 行号过期,鉴权状态错误 | 更新行号并改为"已实现" |
| 6 | 01 全文 | 未提及 SSE 端点contract.md/matrix.md 列出但 02 建议不支持) | 待 ISSUE-001 仲裁后补充结论 |
| 7 | 01 全文 | 未提及审计表ai-allocation §5 设计重点) | 待 ISSUE-005 仲裁后补充 |
---
## §3 历史问题
(暂无)

View File

@@ -1,24 +1,221 @@
# student-bff 问题记录
> 负责人ai04
> 关联:[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)、[matrix.md](../matrix.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
> 仲裁依据:[coord-final-decisions.md](../../coord-final-decisions.md) §2 BFF 专项裁决B1-B8、[president-final-rulings.md](../../president-final-rulings.md)
---
## 问题列表
## §0 已有仲裁核查总结2026-07-10 审查)
<!--
追加条目格式:
> 本次审查对历史 issues.md 中 ai04 提请的 4 项问题ISSUE-028/029/030/031-ai04逐一核查总裁裁决落地情况。
### ISSUE-[编号]-[AI标识][标题]
| 编号 | 主题 | 总裁裁决章节 | 裁决要点 | 核查结果 |
| ---- | ---- | ------------ | -------- | -------- |
| ISSUE-028-ai04 | 02 文档与 B1+B2 裁决冲突需回写 | president §3.4 | ai04 须在批次 2 启动前回写 student-bff 02B1 GraphQL + B2 gRPC + B8 DownstreamClient | ⚠️ **未执行**02-architecture-design.md 仍为 REST 设计§4 21 个 REST 端点 / §9.2 REST→GraphQL 演进 / §9.3 HTTP→gRPC 演进),违反 B1、B2 |
| ISSUE-029-ai04 | P3 启动前置依赖确认4 项强阻塞) | president §4.1 / §6.1 | 批次 0 完成信号机制 + 批次 2 启动条件:批次 1 P2.1 完成 + ai03 DownstreamClient 抽象就绪 | ✅ **已裁决**:批次时间线 §6.1 明确批次 2 启动条件;前置依赖检查清单机制已建立 |
| ISSUE-030-ai04 | student-bff GraphQL schema 第一版仲裁时机 | president §2.2 | ai04 起草 schema批次 1 等待期coord 在批次 2 启动前仲裁第一版;存放 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` | ✅ **已裁决**schema 仲裁机制已建立§2.2);但 schema 第一版尚未起草,需 ai04 在批次 1 等待期产出 |
| ISSUE-031-ai04 | issues.md 编号冲突 | president §0.4 | 保留原始内容不删除,用 `ISSUE-XXX-<提请AI>` 格式唯一定位,不重新编号 | ✅ **已裁决**:编号规则已生效,本文件即按新流程在 objections/ 下维护 |
- **提请方**aiXX
- **日期**YYYY-MM-DD
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
- **描述**[详细描述问题]
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
### 0.1 核查结论
(暂无问题)
- **唯一未落地项**ISSUE-028-ai0402 文档回写。02-architecture-design.md 当前内容与 B1/B2/B8 裁决严重冲突,须在批次 2 启动前完成回写。
- **schema 第一版未起草**ISSUE-030-ai04 虽已建立仲裁机制,但 ai04 尚未产出 student-bff GraphQL schema 草案,需在批次 1 等待期完成。
- **01-understanding.md 同步问题**:阶段 1 文档同样存在 REST 假设与错误码前缀错误(详见 §2需与 02 文档一并修正。
---
## §1 待 coord 仲裁的新问题(本次审查发现)
### ISSUE-STU-001-ai0401-understanding.md 与 B1/B2/B5 裁决冲突
- **提请方**ai04
- **日期**2026-07-10
- **类型**:裁决冲突(文档未回写)
- **描述**:阶段 1 文档 `services/student-bff/docs/01-understanding.md` 存在 3 项与已裁决规则的冲突:
1. **B1 冲突API 风格)**§3.2 暴露 14 个 REST 端点(`/student/dashboard`§4 / §4.1 明确"先对齐 teacher-bff 现状REST + fetch",建议"P3 阶段先 REST后续统一升级 GraphQL"。与 B1"P2 起直接 GraphQL"冲突,属禁止的"中间过渡方案"。
2. **B2 冲突(下游通信)**§3.1 表述"BFF→Service 走 HTTP fetch当前阶段"§4 技术栈"HTTP fetch当前阶段对齐 teacher-bff 模式)"。与 B2"首次实现即 gRPC 调用下游"冲突。
3. **B5 冲突(错误码前缀)**§3.3 / §6 表格用 `STUDENT_BFF_` 前缀。与 B5"统一 BFF_ 前缀BFF_TEACHER_ / BFF_STUDENT_ / BFF_PARENT_"冲突。
- **建议方案**:与 ISSUE-028-ai04 合并处理01 文档随 02 文档一并回写:
- 删除 REST 端点清单,改为 GraphQL Query/Mutation 清单
- 删除"HTTP fetch 对齐 teacher-bff 现状"表述,改为"gRPC 调用下游(@grpc/grpc-js + @bufbuild/protobuf"
- 错误码前缀统一为 `BFF_STUDENT_`
- §7.2"待 coord 仲裁"项中B1/B2/B3/B5/B6/B7 已裁决,删除重复提请
- **状态**:待 coord 确认回写范围(是否 01 文档也纳入回写义务)
### ISSUE-STU-002-ai0402-architecture-design.md 引用不存在的 004 章节
- **提请方**ai04
- **日期**2026-07-10
- **类型**:契约不明确(文档引用错误)
- **描述**02-architecture-design.md 多处引用"004 §11.4 错误码前缀矩阵"和"004 §11.5 统一响应信封 ActionState",但实际 004_architecture_impact_map.md §11 仅包含:
- §11.1 Protobuf 契约体系
- §11.2 契约规则
- §11.3 BFF 聚合模式
- **不存在 §11.4 和 §11.5**
- **影响**:错误码前缀 BFF_STUDENT_ 的权威来源应改为 [coord-final-decisions.md](../../coord-final-decisions.md) G14 + B5ActionState 信封规范应改为 [coord-final-decisions.md](../../coord-final-decisions.md) G8 + F9
- **建议方案**02 文档回写时修正引用源coord 确认是否需要在 004 补充 §11.4/§11.5 章节,或统一指向 coord-final-decisions
- **状态**:待 coord 仲裁
### ISSUE-STU-003-ai0402 文档 §8.3 列 12 项未决决策,其中 8 项已裁决
- **提请方**ai04
- **日期**2026-07-10
- **类型**:裁决冲突(文档未回写)
- **描述**02-architecture-design.md §8.3"未决设计决策(待 coord 仲裁)"列出 12 项,但其中 8 项已被 coord-final-decisions §2 裁决:
| # | 决策点 | 02 文档建议 | 已裁决结论 | 裁决章节 |
| --- | ------ | ----------- | ---------- | -------- |
| 1 | BFF API 风格 | AP3 REST | **B1P2 起直接 GraphQL** | coord-final-decisions §2 B1 |
| 2 | BFF 是否做权限校验 | A不校验 | **B3BFF 豁免 @RequirePermission** | coord-final-decisions §2 B3 |
| 3 | 自我越权防御 | B | **B4全部 BFF 强制自我越权防御** | coord-final-decisions §2 B4 |
| 4 | /readyz 检查逻辑 | AP3 直接 ok | **G2 + §2.4:按阶段扩展探针,必需依赖失败 503可选依赖软失败** | president §2.4 |
| 5 | Kafka 事件订阅时机 | AP3 不订阅) | **B7P2-P4 不订阅 KafkaP5 后订阅** | coord-final-decisions §2 B7 |
| 6 | 缓存策略 | BRedis 5-30s | **B6Redis 5-30s 短缓存** | coord-final-decisions §2 B6 |
| 8 | 错误码前缀 | BFF_STUDENT_ | **B5BFF_STUDENT_** | coord-final-decisions §2 B5 |
| 9 | DownstreamClient 回写 | B回写 | **B8回写 teacher-bff3 个 BFF 统一** | coord-final-decisions §2 B8 |
仅剩 #7(端口 3009#10CQRS 不引入)、#11SSE 实现)、#12(熔断器引入)属合理的设计决策,但 #12 熔断器 president §2.4 已暗示按阶段评估。
- **建议方案**02 文档回写时删除已裁决项的"待仲裁"标注,改为"已裁决(见 coord-final-decisions §2 BX"
- **状态**:待 coord 确认(与 ISSUE-028-ai04 合并处理)
### ISSUE-STU-004-ai04student-bff GraphQL schema 第一版尚未起草
- **提请方**ai04
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**president §2.2 裁决"ai04 起草 student-bff schema批次 1 等待期coord 在批次 2 启动前仲裁第一版"。当前批次 1 已启动2026-07-10ai04 处于批次 1 等待期,但 schema 草案尚未产出。02-architecture-design.md 仍是 REST 设计,未定义 GraphQL Query/Mutation/Type。
- **影响**:阻塞 ai14student-portalP3 启动(依赖 schema 契约);阻塞 coord 批次 2 启动前仲裁
- **建议方案**ai04 在批次 1 等待期优先产出 student-bff GraphQL schema 草案,存放 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql`,提交 coord 仲裁
- **状态**ai04 自行执行(非 coord 仲裁项),记录待办
---
## §2 01-understanding.md 审查详情
### 2.1 准确性问题
| 位置 | 问题 | 严重度 |
| ---- | ---- | ------ |
| §1 通信方式(入/出) | 表述"HTTP REST当前阶段/ HTTP fetch当前阶段",实际 B1/B2 裁决为 GraphQL + gRPC | 高 |
| §3.1 表格 | 列"当前 REST 端点(实际可用)"列,暗示走 REST但 BFF 是新服务不存在"当前 REST 现状" | 高 |
| §3.2 端点表 | 14 个 REST 端点,违反 B1 GraphQL | 高 |
| §3.3 错误码前缀 | `STUDENT_BFF_` 违反 B5 `BFF_STUDENT_` | 中 |
| §4 技术栈 API 风格 | "HTTP REST当前阶段",违反 B1 | 高 |
| §6 权限装饰器 | "⚠️ 不对齐"结论正确B3 豁免),但理由应补充"已裁决 B3" | 低 |
| §7.2 决策点 1-5 | 列为"待 coord 仲裁",但 B1/B2/B3/B5/B6/B7 均已裁决 | 高 |
### 2.2 遗漏项
| 遗漏内容 | 应补充位置 | 依据 |
| -------- | ---------- | ---- |
| GraphQL schema 设计意图Query/Mutation/Type | §3.2 | B1 + president §2.2 |
| gRPC 下游调用设计(@grpc/grpc-js + @bufbuild/protobuf | §3.1 / §4 | B2 |
| DataLoader 防 N+1 策略 | §4 | 004 §11.3 + B1 |
| B4 自我越权防御userId 强制比对) | §2.3 / §3 | B4 |
| B8 DownstreamClient 抽象(复用 teacher-bff | §4 / §6 | B8 |
| GraphQL schema 存放路径 `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` | §3.2 | president §2.2 |
| GraphQL errors 数组 + extensions.code + extensions.traceId 错误格式 | §3.3 | president §2.2 #5 |
| Relay Cursor Connections 分页规范 | §3.2 | president §2.2 #5 |
---
## §3 02-architecture-design.md 审查详情
### 3.1 架构合理性评估
| 维度 | 评估 | 说明 |
| ---- | ---- | ---- |
| 分层设计§1 | ✅ 合理 | Controller → Service → Aggregator → Cache → DownstreamClient 五层清晰DownstreamClient/Aggregator/Transformer 抽象优于 teacher-bff 现状,符合 B8 |
| 领域模型§2 | ✅ 合理 | "场景聚合视图"概念恰当BFF 无领域模型DataScope=SELF 强制实现§2.3)符合 B4 |
| 缓存设计§3 | ✅ 合理 | Redis Key 规范、TTL 分档、失效策略完整,符合 B6 |
| API 设计§4 | ❌ 严重冲突 | 21 个 REST 端点违反 B1 GraphQL须改为 GraphQL Query/Mutation |
| 事件设计§5 | ⚠️ 部分冲突 | 订阅清单合理,但 B7 裁决 P2-P4 不订阅 Kafka§5 应明确"P5 才落地"且标注 B7 |
| 横切关注点§6 | ✅ 合理 | logger/metrics/tracer/health/优雅关闭对齐黄金模板;错误码 BFF_STUDENT_ 正确§0.2 已修正) |
| 契约矩阵§7 | ⚠️ 需更新 | 下游通信"HTTP→gRPC P3+"表述违反 B2应改为"gRPC 首次实现即用" |
| 风险与假设§8 | ⚠️ 需更新 | §8.3 12 项未决决策中 8 项已裁决(见 ISSUE-STU-003 |
| 演进路线§9 | ❌ 严重冲突 | §9.2 REST→GraphQL、§9.3 HTTP→gRPC 违反"不分阶段"原则president §0.3 |
| 扩展点§10 | ✅ 优秀 | 多端适配/国际化/多角色复用/离线模式/AI 增强/学习路径预留设计前瞻 |
| 性能容量§11 | ✅ 合理 | SLO 分级、容量规划、限流策略完整 |
| 安全合规§12 | ✅ 合理 | 身份认证/授权隔离/输入安全/数据合规/审计完整 |
| 可观测性§13 | ✅ 合理 | 日志规范/span/告警/Grafana 面板完整 |
### 3.2 长远性评估
| 长远性维度 | 评估 | 说明 |
| ---------- | ---- | ---- |
| 多角色复用§9.5 | ✅ | 学习委员/课代表/走读生差异化通过视口扩展,无需改代码 |
| 多端适配§10.1 | ✅ | Transformer 层按 x-client-type 裁剪H5/小程序可扩展 |
| GraphQL 演进§9.2 | ❌ | 规划为"P6+ 可选",但 B1 已裁决 GraphQL 是起点非终点,须移除"可选" |
| 通信协议演进§9.3 | ❌ | 规划"P3 HTTP → P4 gRPC 混合 → P6 Service Mesh",违反 B2"首次实现即 gRPC" |
| 推送通道演进§9.4 | ✅ | P3 无推送 → P5 SSE → P5+ WebSocket → P6+ 移动端推送,渐进合理 |
| AI 答疑增强§10.5 | ✅ | 预留 context 参数,支持多步编排 |
| 国际化§10.2 | ✅ | 预留 I18nContext 接入点 |
### 3.3 业界架构文档规范符合度
| 规范项 | 符合度 | 说明 |
| ------ | ------ | ---- |
| 文档导航/导读 | ✅ | §0.3 文档结构清晰16 章覆盖完整 |
| 设计原则 | ✅ | §0.1 列 P1-P9 九项原则 |
| 架构图C4 模型) | ✅ | §1 物理分层图 + 调用链时序图,符合 C4 Level 2/3 |
| ADR 决策记录 | ⚠️ | §8.3 列决策但未用 ADR 格式,且已裁决项未更新 |
| 非功能性需求 | ✅ | §11 性能 SLO + §12 安全 + §13 可观测性 |
| 演进路线 | ⚠️ | 有 §9 但违反"不分阶段"原则 |
| 实施清单 | ✅ | §14 P3-P6 分阶段清单完整 |
| 风险登记 | ✅ | §8.1 技术风险 + §8.2 外部依赖假设 |
### 3.4 关键遗漏
| 遗漏内容 | 应补充位置 | 依据 |
| -------- | ---------- | ---- |
| GraphQL Schema 完整定义Query/Mutation/Type/Enum | 新增 §4.2 或独立 §5 | B1 + president §2.2 |
| DataLoader 批量策略(哪些 Query 需要 DataLoader | §1.2 或 §6 | 004 §11.3 + B1 |
| gRPC client 设计channel 复用、interceptor、metadata 透传) | §1.2 或 §7 | B2 |
| GraphQL errors 数组扩展 ActionState 字段规范 | §6.2 错误码清单 | president §2.2 #3 |
| Relay Cursor Connections 分页规范 | §4 API 设计 | president §2.2 #5 |
| GraphQL schema 存放路径与 codegen 配置 | §14 实施清单 | president §2.2 #4 |
| AuthorizationGuard 接口设计B4 越权防御 P3 实现方式) | §2.3 或 §6 | president §2.9(参照 teacher-bff ISSUE-033-ai03 |
---
## §4 历史问题归档
> 以下问题原记录在 `docs/issues.md`(旧流程),现按新流程归档至此。总裁裁决详见 [president-final-rulings.md](../../president-final-rulings.md) §10 问题索引表。
### ISSUE-028-ai04ai04 02-architecture-design.md 与 coord B1+B2 裁决冲突需回写
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:裁决冲突(文档回写义务)
- **描述**02-architecture-design.md 与 coord-final-decisions B1P2 起直接 GraphQL+ B2首次实现即 gRPC存在 2 项重大冲突§4 API 设计决策为 REST§9.2 演进路线 REST→GraphQL
- **裁决**president §3.4 — ai04 须在批次 2 启动前回写 student-bff 02B1 GraphQL + B2 gRPC + B8 DownstreamClient
- **状态**:⚠️ 未执行(详见 §0 核查)
### ISSUE-029-ai04ai04 P3 启动前置依赖确认4 项强阻塞)
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:前置依赖未就绪
- **描述**P3 启动依赖 4 项强阻塞前置core_edu.proto 补全 / buf.gen.yaml gRPC 插件 / ai03 DownstreamClient 抽象 / ai08 core-edu gRPC server
- **裁决**president §4.1 / §6.1 — 批次 0 完成信号机制 + 批次 2 启动条件明确
- **状态**:✅ 已裁决
### ISSUE-030-ai04ai04⟷ai14 student-bff GraphQL schema 第一版仲裁时机
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:契约不明确
- **描述**student-bff GraphQL schema 第一版仲裁时机未明确
- **裁决**president §2.2 — ai04 起草 schema批次 1 等待期coord 在批次 2 启动前仲裁第一版
- **状态**:✅ 已裁决schema 第一版待 ai04 起草,见 ISSUE-STU-004
### ISSUE-031-ai04issues.md 编号冲突
- **提请方**ai04student-bff + parent-bff
- **日期**2026-07-09
- **类型**:工作归属不明(文档规范)
- **描述**issues.md ISSUE-024/025 编号冲突ai11 与 ai09 重复)
- **裁决**president §0.4 — 保留原始内容,用 `ISSUE-XXX-<提请AI>` 格式定位,不重新编号
- **状态**:✅ 已裁决

View File

@@ -1,12 +1,208 @@
# student-portal 问题记录
> 负责人ai14
> 关联:[coord.md](../coord.md)、[contracts/student-portal_contract.md](../contracts/student-portal_contract.md)
> 关联:[coord.md](../coord.md)、[contracts/student-portal_contract.md](../contracts/student-portal_contract.md)、[matrix.md](../matrix.md)
> 规则AI 遇到问题时在此追加条目coord 仲裁后更新状态
---
## 问题列表
## §1 已有仲裁核查ARB-001 / ARB-002 对 student-portal 的影响)
### 1.1 ARB-001teacher-bff GraphQL schema 第一版)对 student-portal 的影响核查
| 裁决点 | 对 student-portal 的适用性 | ai14 落实方案 | 状态 |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------ |
| Schema 存放位置 | ✅ 适用原则一致。student-bff GraphQL schema 应存放于 `packages/shared-ts/contracts/graphql/student-bff.graphql`(集中管理,与 teacher-bff 同源) | ai14 在 contract.md §1.3 已标注预期路径;实际由 ai04 创建ai14 仅消费 | ⏳ 待 ai04 |
| P2 Query 范围 | ⚠️ 部分参考。ARB-001 是 teacher-bff 的 P2 范围student-portal 起步于 P3不在 P2因此 student-bff 直接以 P3 全量 Query 起步 | ai14 在 P3 直接消费 student-bff 全量 QuerycurrentUser/myClasses/myExams/myHomework/myGrades/myAttendance/studentDashboard | ✅ 已落实 |
| P2 Mutation 范围 | ⚠️ 部分参考。student-portal P3 起步即需要 submitHomework mutation作业提交是 P3 核心场景) | ai14 P3 即消费 submitHomework mutationai04 P3 必须提供 | ⏳ 待 ai04 |
| DataLoader 防 N+1 | ✅ 适用。student-bff 聚合多 gRPC 时(如 studentDashboard 聚合 iam+core-edu+data-ana必须使用 DataLoader | ai14 不直接实现,但依赖 student-bff 返回结构稳定(无 N+1 慢查询) | ⏳ 待 ai04 |
| 复杂度限制depth ≤ 7 | ✅ 适用。student-portal 发起的 GraphQL query 深度必须 ≤ 7 | ai14 在 [02-architecture-design.md §4.2](../../../apps/student-portal/docs/02-architecture-design.md) 已设计扁平 query | ✅ 已落实 |
| ActionState 信封 | ✅ 适用。student-bff 必须返回 ActionState 信封success/errors/data | ai14 在 GraphQL 请求层02 §3.2)已处理信封解包 + 降级字段识别 | ✅ 已落实 |
| 降级模式(方案 B | ✅ 适用。部分聚合失败时 success=true + data 内 `extensions.degraded: true` | ai14 在 [02 §18.2 降级策略矩阵](../../../apps/student-portal/docs/02-architecture-design.md) 已设计 12 个降级场景 | ✅ 已落实 |
| admin 命名空间 | ❌ 不适用。student-portal 不涉及 admin 命名空间 | - | - |
**核查结论**ARB-001 是 teacher-bff 的 P2 仲裁但其设计原则Schema 集中管理、ActionState 信封、降级模式方案 B、复杂度限制适用于所有 BFFstudent-portal 已在 02-architecture-design.md v2 中全面落实。
---
### 1.2 ARB-002MF Shell 暴露清单)对 student-portal 的影响核查
| 裁决点 | 对 student-portal 的适用性 | ai14 落实方案 | 状态 |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| Shell 身份 | ✅ 适用。teacher-portal 是 MF Shellstudent-portal 是 P3 首个 Remote | ai14 在 [02 §3.2 MF 配置](../../../apps/student-portal/docs/02-architecture-design.md) 已声明 `remotes: { teacher: 'teacher@http://localhost:4000/_next/static/chunks/remoteEntry.js' }` | ✅ 已落实 |
| P3 首个 Remote | ✅ 适用。ARB-002 §2.3 明确 P3 首个 Remote 是 student-portal | ai14 P3 任务启动即接入 MF Remote | ✅ 已落实 |
| GraphQL client 归属 | ✅ 适用。Shell 暴露 GraphQLProviderstudent-portal 复用,**不重复创建 client** | ai14 在 [02 §3.2 GraphQL 请求层](../../../apps/student-portal/docs/02-architecture-design.md) 已使用 `useGraphQLClient()``@edu/hooks` 获取 | ✅ 已落实 |
| MF shared singleton 配置 | ✅ 适用。student-portal 必须将 react/react-dom/urql/graphql/@edu/* 声明为 singleton | ai14 在 [02 §3.2 next.config.js](../../../apps/student-portal/docs/02-architecture-design.md) 已声明全部 singleton | ✅ 已落实 |
| AppShell 复用 | ✅ 适用。student-portal 不重复实现 AppShell复用 Shell 暴露的 AppShell | ai14 在 02 §3.2 已设计 `<AppShell>` 包裹 + 学生端导航覆写 | ✅ 已落实 |
| useAuth / usePermission 复用 | ✅ 适用。student-portal 复用 Shell 暴露的 useAuth/usePermission | ai14 在 [01-understanding.md §6](../../../apps/student-portal/docs/01-understanding.md) 已声明权限校验走 usePermission | ✅ 已落实 |
| ErrorBoundary / Loading / Empty 复用 | ✅ 适用。student-portal 复用 Shell 暴露的共享 UI 组件 | ai14 在 02 §6 组件设计已使用 `@edu/ui-components` | ✅ 已落实 |
| feature flag | ✅ 适用。`NEXT_PUBLIC_MF_ENABLED` 控制是否走 MFP3 默认 true | ai14 在 02 §3.2 已设计独立壳回退MF 关闭时独立渲染) | ✅ 已落实 |
| 登录页 | ✅ 适用。P2 登录页由 Shell 独占student-portal 不实现登录页,未登录跳转 Shell `/login` | ai14 在 02 §3.2 已设计未认证 → 跳转 `window.location.href = 'http://localhost:4000/login?redirect=student'` | ✅ 已落实 |
**核查结论**ARB-002 是 student-portal 接入 MF 的直接依据ai14 已在 02-architecture-design.md v2 中全面落实。无异议。
---
### 1.3 ARB-001 / ARB-002 核查总结
| 仲裁 | 对 student-portal 的影响 | ai14 落实情况 | 异议 |
| ------ | ------------------------ | ------------- | ---- |
| ARB-001 | 设计原则适用GraphQL + ActionState + 降级模式) | ✅ 已落实 | 无 |
| ARB-002 | 直接适用P3 首个 Remote + Shell 暴露清单) | ✅ 已落实 | 无 |
> **ai14 声明**ARB-001 / ARB-002 已在 02-architecture-design.md v2 中全面落实,无需新增仲裁。
---
## §2 新提请异议(待 coord 仲裁)
### ISSUE-014-01-ai14GraphQL endpoint 路径不一致(`/api/student/graphql` vs `/api/v1/student/graphql`
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- `student-portal_contract.md` §2.3 当前写 `POST /api/student/graphql`(无 `/v1/` 前缀)
- `matrix.md` §5 HTTP 接口矩阵明确写 `api-gateway` 反向代理 `student-portal` 的路径是 `/api/v1/student/*`
- `01-understanding.md` v2 §3.1 和 `02-architecture-design.md` v2 §4.1 已统一为 `POST /api/v1/student/graphql`
- 三处不一致,需要 coord 仲裁统一为 `/api/v1/student/graphql`(与 matrix.md §5 对齐,与 teacher-portal `/api/v1/teacher/graphql` 保持命名一致性)
- **建议方案**
- 统一为 `POST /api/v1/student/graphql`
- 由 ai01api-gateway确认路由`/api/v1/student/*``student-bff:3009/*`(即 `/api/v1/student/graphql``student-bff:3009/graphql`
- 由 ai04student-bff确认 GraphQL endpoint 路径为 `POST /graphql`(与 teacher-bff 一致)
- **状态**:待 coord 仲裁
---
### ISSUE-014-02-ai14student-bff GraphQL schema 文件存放位置不一致(集中管理 vs 应用内管理)
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- ARB-001 §1.3 关键裁决明确teacher-bff GraphQL schema 存放于 `packages/shared-ts/contracts/graphql/teacher-bff.graphql`(集中管理,总裁裁决 §2.17 SDL-first + 集中管理)
- `student-bff_contract.md` §1.3 写:`apps/student-bff/src/schema/*.graphql`(应用内管理,与 ARB-001 原则不一致)
- `matrix.md` §3 GraphQL 接口提供方矩阵写:`packages/shared-ts/contracts/graphql/student-bff.graphql`(与 ARB-001 一致)
- 两处不一致,需要 coord 仲裁统一
- **建议方案**
- 统一为 `packages/shared-ts/contracts/graphql/student-bff.graphql`(与 ARB-001 原则对齐,集中管理便于前端 codegen
- ai14 在 student-portal 端使用 `graphql-codegen` 从该 schema 生成 TypeScript 类型
- 由 ai04student-bff创建该 schema 文件并维护
- **状态**:待 coord 仲裁
---
### ISSUE-014-03-ai14考试作答页全屏策略与防作弊检测边界
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- student-portal 02-architecture-design.md §14 设计了防作弊检测visibilitychange/copy/paste/fullscreen/contextmenu但未明确以下边界
1. **全屏 API 强制策略**是否强制全屏Fullscreen API退出全屏是否触发警告/记录?
2. **离开页面策略**visibilitychange hidden 触发时,是仅记录还是自动提交?
3. **多标签检测**BroadcastChannel 检测到多标签时,是警告还是阻止作答?
4. **防作弊事件上报**:前端采集的防作弊事件如何上报?走 student-bff GraphQL mutation 还是 push-gateway WebSocket
- 这些决策影响 ai04student-bff是否需要提供 `recordExamViolation` mutation以及 ai08core-edu是否需要存储违规记录
- **建议方案**
- **全屏策略**P3 推荐但不强制(提示"建议全屏作答"P4 评估是否升级为强制(基于教师反馈)
- **离开页面策略**visibilitychange hidden 触发时仅记录(不自动提交),累计 3 次警告后教师端可见
- **多标签检测**:警告 + 记录,不阻止作答(避免误伤合法场景如查词典)
- **防作弊事件上报**:走 student-bff GraphQL mutation `recordExamViolation(examId, type, payload)`,由 ai04 在 P3 提供
- **状态**:待 coord 仲裁
---
### ISSUE-014-04-ai14主观题粘贴策略防作弊 vs 学生体验)
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- 02-architecture-design.md §14 防作弊检测包含 `paste` 事件拦截,但学生作答主观题时可能需要粘贴(如从草稿本粘贴长文本)
- 策略不明确:全部禁止粘贴?仅主观题允许?仅客观题禁止?
- 影响学生体验和防作弊效果平衡
- **建议方案**
- **客观题**:禁止粘贴(防作弊优先)
- **主观题(简答/论述)**:允许粘贴,但记录粘贴事件 + 粘贴内容长度,教师端批改时可见
- **作文题**:允许粘贴(学生体验优先),不记录
- 由 ai04student-bff在 P3 提供 `recordPasteEvent` mutation或复用 ISSUE-014-03 的 `recordExamViolation`type=`PASTE`
- **状态**:待 coord 仲裁
---
### ISSUE-014-05-ai14作业附件上传协议GraphQL mutation vs REST multipart
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- 学生提交作业时可能需要上传附件(图片/PDF/文档GraphQL mutation 不适合处理大文件上传multipart/form-data
- 当前 contract.md 未明确附件上传协议
- 选项:
- A. 走 api-gateway REST 端点(`POST /api/v1/student/upload` → 对象存储),返回 URL再走 GraphQL mutation 提交 URL
- B. 走 student-bff GraphQL multipartgraphql-upload需要 ai04 支持)
- C. 走独立上传服务(如 push-gateway 扩展或新建 upload-service
- **建议方案**
- **推荐 A**:走 api-gateway REST `POST /api/v1/student/upload` → 对象存储MinIO/OSS返回 signed URL再走 GraphQL `submitHomework(attachmentUrls: [String!])` mutation 提交
- 理由GraphQL 不适合大文件传输REST + 对象存储是业界通用方案api-gateway 已有 JWT 鉴权
- 由 ai01api-gateway确认是否提供 `/api/v1/student/upload` 路由,由 ai04student-bff确认 `submitHomework` mutation 是否接受 `attachmentUrls` 字段
- **状态**:待 coord 仲裁
---
### ISSUE-014-06-ai14考试延长/题目重排等实时事件命名未确认
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- student-portal 02-architecture-design.md §16 设计了 WebSocket 实时通知,但以下事件命名未在 matrix.md §4 Kafka 事件矩阵中确认:
1. **考试延长**(教师延长考试时间):事件名 `ExamExtended`?还是 `ExamUpdated`?由 ai08core-edu发布
2. **题目重排**(教师重排题目顺序):事件名 `ExamQuestionReordered`?是否需要前端实时重排?
3. **考试强制提交**(教师强制收卷):事件名 `ExamForceSubmitted`?前端收到后立即提交?
- 这些事件影响 student-portal 考试作答页的实时响应逻辑
- **建议方案**
- **考试延长**ai08core-edu发布 `ExamExtended` 事件到 `edu.exam.events` topicmsgai10消费后通过 push-gateway 推送student-portal 收到后更新倒计时
- **题目重排**P3 不实现题目顺序固定P4 评估是否需要实时重排
- **考试强制提交**ai08 发布 `ExamForceSubmitted` 事件student-portal 收到后立即触发提交流程
- 由 ai08core-edu确认事件命名由 ai10msg确认推送路径
- **状态**:待 coord 仲裁
---
### ISSUE-014-07-ai14学生端 DataScope L0 边界(仅能查看自己数据)的强制执行层
- **提请方**ai14
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**
- 01-understanding.md §8 和 02-architecture-design.md §6 声明学生 DataScope L0仅能查看自己数据
- 但强制执行层不明确:
- A. student-bff 在 Resolver 层基于 JWT 的 `x-user-id` 强制过滤(推荐,前端无法绕过)
- B. student-portal 在 GraphQL query 中显式传 `studentId`(不安全,前端可篡改)
- 当前 02-architecture-design.md §4.2 的 GraphQL query 设计中,部分 query 显式传 `studentId`(如 `myClasses(studentId: ID!)`),这与 L0 强制执行矛盾
- **建议方案**
- **统一为方案 A**student-bff 在 Resolver 层从 JWT `x-user-id` 提取 studentId强制过滤前端 query 不传 `studentId` 参数
- ai14 修改 02-architecture-design.md §4.2 的 GraphQL query 定义,移除 `studentId` 参数(如 `myClasses` 改为无参 query
- 由 ai04student-bff确认所有学生端 Query 均从 JWT 提取 studentId不接受前端传入
- **状态**:待 coord 仲裁
---
## §3 异议状态汇总
| 编号 | 类型 | 标题 | 状态 |
| ------------- | ------------ | -------------------------------------------------------- | ------------ |
| ISSUE-014-01 | 契约不明确 | GraphQL endpoint 路径不一致 | 待 coord 仲裁 |
| ISSUE-014-02 | 契约不明确 | student-bff GraphQL schema 存放位置不一致 | 待 coord 仲裁 |
| ISSUE-014-03 | 契约不明确 | 考试作答页全屏策略与防作弊检测边界 | 待 coord 仲裁 |
| ISSUE-014-04 | 契约不明确 | 主观题粘贴策略(防作弊 vs 学生体验) | 待 coord 仲裁 |
| ISSUE-014-05 | 契约不明确 | 作业附件上传协议GraphQL mutation vs REST multipart | 待 coord 仲裁 |
| ISSUE-014-06 | 契约不明确 | 考试延长/题目重排等实时事件命名未确认 | 待 coord 仲裁 |
| ISSUE-014-07 | 契约不明确 | 学生端 DataScope L0 边界的强制执行层 | 待 coord 仲裁 |
---
<!--
追加条目格式:
@@ -20,5 +216,3 @@
- **建议方案**[AI 的建议]
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)

View File

@@ -21,4 +21,38 @@
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X
-->
(暂无问题)
### ISSUE-001-ai03admin 命名空间 P2 预留与 ARB-001 "P2 不包含" 冲突
- **提请方**ai03
- **日期**2026-07-10
- **类型**:契约不明确
- **描述**coord.md ARB-001 §1.3 关键裁决表写 "admin 命名空间 | **P2 不包含**P6 admin-portal 阶段新增 `admin` 命名空间";但 president-final-rulings.md §5.1ISSUE-044裁决 "ai03 P2 预留 admin schema 命名空间(如 `admin.*` Query/Mutation",且 §7.3 ai03 工作清单明确 "批次 1P2Must Have 13 项 + DownstreamClient 抽象 + admin schema 命名空间预留"。两处对 P2 admin 命名空间的要求不一致——总裁裁决要求 P2 预留schema 中声明占位coord 仲裁说 P2 不包含。
- **建议方案**:以总裁裁决为准(裁决优先级 president > coord修正 ARB-001 §1.3 为 "P2 预留 admin 命名空间占位schema 中声明 `admin` Query/Mutation 类型骨架,无实际 ResolverP6 admin-portal 阶段实现具体 Resolver"。ai03 在 P2 schema 第一版中预留 `admin` 命名空间类型声明。
- **状态**:待 coord 仲裁
### ISSUE-002-ai03P2 dashboard classes 数据来源与 ARB-001 §1.4 调用链冲突
- **提请方**ai03
- **日期**2026-07-10
- **类型**:前置依赖缺失
- **描述**coord.md ARB-001 §1.4 ai03 执行项第 4 条写 "dashboard Resolver 并行调用 iam3 RPC+ classes1 RPC用 DataLoader 防御 N+1",暗示 P2 dashboard 需 gRPC 调 classes 服务。但存在三重冲突:
1. **classes P2 无 gRPC server**classes.proto 注释 "P1: REST 实现P3 起转 gRPC"matrix.md §2 gRPC 接口提供方矩阵中 core-edu含 classes 合并gRPC 50053 状态为 P3 就绪。classes 服务 P2 阶段未启用 gRPC。
2. **B2 裁决约束**coord-final-decisions.md B2 裁决 "首次实现即 gRPC 调用下游",若 classes P2 无 gRPCteacher-bff 不能用 REST 调 classesB2 禁止 REST 过渡)。
3. **president §3.5 功能范围**president-final-rulings.md §3.5 裁决 P2 实现 "班级列表iam 数据)",即 P2 班级列表数据来自 iam不是 classes 服务。
- **建议方案**:采纳 president §3.5P2 dashboard 的 classes 数据从 iam gRPC 获取iam `GetEffectiveAccess``GetViewports` 返回的关联班级),不调 classes 服务。ARB-001 §1.4 第 4 条修正为 "dashboard Resolver 并行调用 iam gRPCGetUserInfo + GetViewports + GetEffectiveAccessclasses 列表从 iam 返回数据推导P3 core-edu gRPC 就绪后切换为 `GetClassesByTeacher` RPC"。这样 P2 不依赖 classes gRPC与 B2 + president §3.5 一致。
- **状态**:待 coord 仲裁
### ISSUE-003-ai03GraphQL schema 文件命名不一致ARB-001 vs president §2.17
- **提请方**ai03
- **日期**2026-07-10
- **类型**:编号冲突
- **描述**GraphQL schema 文件存放目录两处一致(`packages/shared-ts/contracts/graphql/`),但文件名不一致:
- coord.md ARB-001 §1.3 + §1.4`teacher-bff.graphql`
- president-final-rulings.md §2.17 第 1 条:`teacher-bff.schema.graphql`
ai03 创建文件时无法确定用哪个文件名。
- **建议方案**:以 president §2.17 为准(裁决优先级 president > coord统一为 `teacher-bff.schema.graphql`。coord 修正 ARB-001 §1.3/§1.4 文件名。
- **状态**:待 coord 仲裁
---

View File

@@ -155,6 +155,31 @@
---
### ISSUE-042-ai13仲裁核查 - 01/02 文档未同步已裁决的 GraphQL 架构
- **提请方**ai13
- **日期**2026-07-10
- **类型**:文档同步遗漏(仲裁核查)
- **阶段**P2
- **描述**
- 按"对已有仲裁进行核查"要求,审查 ISSUE-036~041均已裁决在文档中的落地情况
- 核查发现:`apps/teacher-portal/docs/01-understanding.md``02-architecture-design.md` 仍为 ai07 标识 + REST 架构,**未同步**以下已裁决事项:
- F9ISSUE-036P2 起 all-in GraphQL无 REST 过渡 — 01 §1/§3.1 仍写"P2-P3 用 REST 过渡"02 全文基于 RESTApiClient/TanStack Query/契约清单)
- ARB-001ISSUE-037teacher-bff GraphQL schema 第一版 5 Query — 01 §3.1 列 REST 端点02 §4/§10 全 REST
- ARB-002ISSUE-038/039MF Shell 暴露清单GraphQLProvider + hooks + UI 组件 + urql/graphql singleton— 01 未提02 §1.2 exposes 仅 AppShell+shared-deps、P2 配 3 remotes、shared 无 urql
- 总裁 §2.17ISSUE-038GraphQL client 单例方案 A — 02 §11.3.2 仍列"GraphQL vs REST"未决(已裁决)
- 对照已回写的 `03-long-term-architecture.md §1.4`GraphQL 最终方案01/02 严重滞后
- **建议方案**
1. 01-understanding.mdai07→ai13§1 删除 REST 过渡§3.1 REST 端点→GraphQL queriesARB-001§4 技术栈补 urql补 ARB-002 暴露清单
2. 02-architecture-design.mdai07→ai13全文 REST→GraphQL 重写MF 配置对齐 ARB-002API 层改 urql client契约清单改 GraphQL§11.3 未决决策改已决策)
- **状态**:✅ 已回写闭合2026-07-10审查批次
- **coord 裁决**:无需新裁决(复用 ISSUE-036~040 已有裁决),本次为文档同步执行
- **回写执行**
- `01-understanding.md`:已修正 ai07→ai13、§1 REST→GraphQL、§3.1 REST 端点→GraphQL queriesARB-001、§4 补 urql/GraphQL client 技术栈、补 ARB-002 暴露清单、端口对齐、字体令牌描述
- `02-architecture-design.md`:全量重写为 GraphQL 架构§1 MF 图加 GraphQLProvider 层§1.2 MF 配置对齐 ARB-002 exposes/shared/remotes=0§2 领域模型数据源改 GraphQL Query§3 缓存层改 urql cacheExchange§4 API 设计改 urql client 单例 + ARB-001 Query/Mutation§10 契约清单改 GraphQL§11.3 未决决策改已决策表;端口 3000→4000
---
**AI Agent**: ai13teacher-portal
**Branch**: feat/teacher-portal-issues-migrate-ai13
**Coordinator**: coord-ai

View File

@@ -1,45 +1,246 @@
# admin-portal 工作排期
> 负责人ai16
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/admin-portal_contract.md](../contracts/admin-portal_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/admin-portal_contract.md](../contracts/admin-portal_contract.md)、[ai-allocation.md §5 ai16](../../ai-allocation.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 说明:本排期基于 GraphQLARB-001 admin 命名空间)+ 端口 4003 + ai16 归属,已对齐 [matrix.md](../matrix.md) 与 ai-allocation.md。01/02 文档中 REST/3003/ai07 表述待 coord 仲裁后修订(见 [objections/admin-portal_issue.md](../objections/admin-portal_issue.md))。
---
## §1 总览
admin-portal 是管理端微前端,通过 MF Remote 接入主应用,覆盖用户管理角色权限、审计日志等场景。全阶段目标P2 MF Remote 骨架 → P3 用户管理+角色权限+审计日志 → P4-P6 持续优化
admin-portal 是管理端微前端MF Remote),挂载到 teacher-portal Shell,覆盖**用户管理 / 角色权限管理 / 学校设置 / 组织管理 / 审计日志消费**等管理场景ai-allocation §5 ai16。复用 teacher-bff GraphQL endpoint 的 **admin 命名空间**ARB-001admin 权限点使用 `ADMIN_` 前缀ai-allocation §5
全阶段目标:
- **P2**MF Remote 骨架next.config.js + MF 配置 + 独立壳渲染 + MSW mock
- **P3**:用户管理 + 角色权限管理admin 命名空间 Query/Mutation
- **P4**:组织管理 + 学校设置 + 班级/教师/学生全局管理
- **P5**审计日志消费auditLogs Query 聚合 iam AuditEvent+ WebSocket 实时通知
- **P6**硬化A11y WCAG 2.2 AA / Web Vitals / OTel / 测试覆盖率 ≥ 80% / Dockerfile 多阶段)
> 跨阶段:开发期间全部经 MSW mock见 [contract §4](../contracts/admin-portal_contract.md)),上游就绪后逐项切换真实。
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai16 admin-portal 全阶段排期
title ai16 admin-portal 全阶段排期P2-P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a16a, 2026-07-10, Xd
section P2 骨架
16.1 MF Remote 骨架(next.config+独立壳) :crit, a16a, 2026-07-10, 2d
16.2 MSW handlers+fixtures+mock JWT :a16b, after a16a, 2d
16.3 接入Shell共享(GraphQLProvider/AppShell/useAuth) :crit, a16c, after a16a, 2d
section P3 用户/角色权限
16.4 用户管理页(users CRUD+UserManagementTable) :crit, a16d, after a16c, 3d
16.5 角色权限矩阵(RolePermissionMatrix+updateRolePermissions) :a16e, after a16d, 3d
16.6 权限点管理+视口配置(ADMIN_前缀) :a16f, after a16e, 2d
section P4 组织/学校/全局实体
16.7 组织树管理(organization) :a16g, after a16f, 2d
16.8 学校设置(system) :a16h, after a16g, 2d
16.9 班级/教师/学生全局管理(adminClasses/Teachers/Students) :a16i, after a16h, 3d
section P5 审计日志/实时通知
16.10 审计日志页(auditLogs Query+筛选/导出) :crit, a16j, after a16i, 3d
16.11 WebSocket实时通知(审计告警/异常登录) :a16k, after a16j, 2d
16.12 管理仪表盘(adminDashboard聚合) :a16l, after a16k, 2d
section P6 硬化
16.13 A11y WCAG 2.2 AA审计+修复 :crit, a16m, after a16l, 3d
16.14 Web Vitals+OTel browser SDK接入 :a16n, after a16m, 2d
16.15 Vitest单测+Playwright E2E(≥80%) :crit, a16o, after a16n, 3d
16.16 Dockerfile多阶段+/api/health+/api/ready :a16p, after a16o, 2d
```
> **注意**:以上为 coord 初始规划ai16 接管后必须自行细化为完整 P2-P6 排期
> 关键路径crit16.1 → 16.3 → 16.4 → 16.10 → 16.13 → 16.15。总工期约 34 个工作日
---
## §3 详细任务
### 全阶段任务
### P2 阶段
#### 16.1 MF Remote 骨架next.config + 独立壳)
- **负责人**ai16
- **交付物**:⚠️ 由 ai16 自行补充
- **依赖**:见 [contracts/admin-portal_contract.md](../contracts/admin-portal_contract.md)
- **验收标准**:⚠️ 由 ai16 自行补充
- **依赖**teacher-portal Shell MF exposes/shared 配置就绪ARB-002ai13 P2 交付)
- **交付物**
- `apps/admin-portal/next.config.js`NextFederationPluginRemote 角色,`name: 'admin_app'``filename: 'static/chunks/remoteEntry.js'``exposes: { './AdminApp': './src/app/admin-app.tsx' }`shared 全部 singleton
- `apps/admin-portal/src/app/admin-app.tsx`(独立壳入口,供 Shell 动态加载 + 独立 dev 渲染)
- `apps/admin-portal/src/app/standalone.tsx`(独立 dev 壳:自实现 AppShell 占位 + mock providers`pnpm --filter admin-portal dev` 在 :4003 独立预览)
- `tsconfig.json`(沿用 tsconfig.base.json`tailwind.config.js`(引入 `@edu/ui-tokens`)、`package.json`
- **验收标准**
- `pnpm --filter admin-portal dev` 在 :4003 启动,独立壳渲染首页 + 导航占位
- MF `remoteEntry.js` 可被 Shell `dynamic import` 加载feature flag `NEXT_PUBLIC_MF_ENABLED` 控制)
- shared 单例配置通过react/react-dom/urql/graphql/@edu/* 全 singleton
#### 16.2 MSW handlers + fixtures + mock JWT
- **负责人**ai16
- **依赖**mock 先行)
- **交付物**
- `apps/admin-portal/src/mocks/handlers.ts`(拦截 `POST /api/admin/graphql` + `POST /api/auth/login` + `GET /ws`
- `apps/admin-portal/src/mocks/fixtures/*.json`50 用户 / 5 角色 / 20 班级 / 50 教师 / 1200 学生 / 100 审计日志 / 仪表盘统计)
- mock JWTadmin 角色permissions=["*"]httpOnly cookie
- **验收标准**
- `NEXT_PUBLIC_API_MOCKING=enabled` 时所有请求被 MSW 拦截返回 mock
- GraphQL mock 按 operationName 返回对应 fixture与 teacher-bff admin namespace mock 数据一致)
#### 16.3 接入 Shell 共享GraphQLProvider/AppShell/useAuth
- **负责人**ai16
- **依赖**ARB-002 Shell 暴露清单就绪ai13
- **交付物**
- `AdminApp` 通过 MF `import from 'teacher/GraphQLProvider'``'teacher/AppShell'``'teacher/useAuth'``'teacher/usePermission'``'teacher/useGraphQLClient'`
- `<AppShell scope="admin">` 渲染管理端视口导航
- GraphQL client 经 `useGraphQLClient()` 获取(**不使用 useApi/ApiClient**,对齐 ARB-002
- **验收标准**
- urql client 单例跨 Remote 共享(与 Shell 同一实例)
- `currentUser` Query 返回 mock 管理员信息
- admin 角色校验:非 admin 角色重定向到登录页
### P3 阶段
#### 16.4 用户管理页users CRUD + UserManagementTable
- **负责人**ai16
- **依赖**teacher-bff admin 命名空间 `adminUsers`/`createUser`/`updateUser`/`deleteUser` schemaISSUE-005 待 coord 仲裁 ai03 补齐)
- **交付物**
- `/admin/users` 列表页UserManagementTable邮箱/姓名/角色/状态/数据范围/最后登录 + 筛选/分页/批量操作)
- `/admin/users/new` + `/admin/users/:id` 表单页react-hook-form + zodResolver
- admin 业务 Hooks`useUsers` / `useUser` / `useCreateUser` / `useUpdateUser` / `useToggleUserStatus`urql query/mutation
- **验收标准**
- 列表筛选/分页正常mutation 后 invalidate 刷新
- DataScope L3-L5 越权由后端强制,前端 `AdminDataScopeFilter` 仅作 UI 提示
- 权限:`IAM_USER_READ` / `IAM_USER_CREATE` / `IAM_USER_UPDATE`
#### 16.5 角色权限矩阵RolePermissionMatrix + updateRolePermissions
- **负责人**ai16
- **依赖**teacher-bff `adminRoles` / `updateRolePermissions` schema
- **交付物**
- `/admin/roles`RolePermissionMatrix行=角色,列=权限按 resource 分组checkbox 网格)
- 系统预置角色只读isSystem=true 不可编辑/删除)
- Hooks`useRoles` / `useRole` / `useCreateRole` / `useUpdateRolePermissions`
- **验收标准**
- 勾选触发 `updateRolePermissions` Mutation乐观更新 + 失败回滚
- 删除前校验 userCount > 0 时禁用删除并提示
#### 16.6 权限点管理 + 视口配置ADMIN_ 前缀)
- **负责人**ai16
- **依赖**`packages/contracts/src/permissions.ts`coord 维护admin 权限点 `ADMIN_*` 前缀)
- **交付物**
- `/admin/permissions` 只读列表DataTable按 resource 分组)
- `/admin/viewports` 视口配置编辑器ViewportConfigEditor@dnd-kit 拖拽排序 + 权限绑定 + scope/isVisible
- Hooks`usePermissions`30min 缓存)/ `useViewportsConfig` / `useUpdateViewport`
- **验收标准**
- 权限点常量来自 `@edu/contracts`(不硬编码)
- 视口配置保存后 AppShell 导航即时刷新invalidate viewports queryKey
### P4 阶段
#### 16.7 组织树管理organization
- **交付物**`/admin/organization` 页(树形 + DataTableschool/grade/class 层级 CRUD
- **验收标准**:树形展开/折叠 + 拖拽调整层级(后端校验)+ DataScope 过滤
#### 16.8 学校设置system
- **交付物**`/admin/system` 页(学校基础信息 / 学年学期 / 系统参数表单)
- **验收标准**:表单 zod 校验 + 保存后 toast 反馈;仅 L5 系统管理员可编辑
#### 16.9 班级/教师/学生全局管理
- **交付物**`/admin/classes` / `/admin/teachers` / `/admin/students` 三个全局管理页adminClasses/adminTeachers/adminStudents Query
- **验收标准**:跨班级/跨年级全局视角(区别于 teacher-portal 的教师自身视角DataScope 控制可见范围
### P5 阶段
#### 16.10 审计日志页auditLogs Query + 筛选/导出)
- **负责人**ai16
- **依赖**teacher-bff 消费 `edu.iam.audit.created` Kafka 并暴露 `auditLogs` QueryISSUE-007 待 coord 修正 matrix.md §4 消费方为 teacher-bff
- **交付物**
- `/admin/audit-logs`DataTable时间/操作人/action/resource/ip + 筛选action/user/dateRange + 导出 CSV
- Hooks`useAuditLogs`(含游标分页)
- **验收标准**
- 审计日志经 GraphQL 查询(**非直接订阅 Kafka**,对齐 contract §2.2
- 100 条 mock 审计日志覆盖 create/update/delete/login/logout/permission_change
#### 16.11 WebSocket 实时通知(审计告警/异常登录)
- **交付物**
- 接入 push-gateway `GET /ws`(与 parent-portal 同类契约一致,对齐 ISSUE-006
- 审计告警 / 异常登录 / 系统异常 toast + 通知中心入口
- **验收标准**
- mock-socket 每 30s 推送 1 条 mock 系统通知
- WebSocket 断线自动重连
#### 16.12 管理仪表盘adminDashboard 聚合)
- **交付物**`/admin/dashboard`rechartstotal_teachers / total_students / school_avg_score / 趋势图)
- **验收标准**adminDashboard Query 返回聚合数据60s 轮询监控指标 + 5min 轮询统计
### P6 阶段(硬化)
#### 16.13 A11y WCAG 2.2 AA 审计 + 修复
- **交付物**eslint-plugin-jsx-a11yerror 级)+ 手动审计修复
- **验收标准**0 个 error 级违规
#### 16.14 Web Vitals + OTel browser SDK 接入
- **交付物**`next/web-vitals``POST /api/admin/web-vitals`OTel browser SDK复用 Shell TracerProviderscope='admin'
- **验收标准**LCP/CLS/FID/TTFB 上报 + trace 上报 collector
#### 16.15 Vitest 单测 + Playwright E2E覆盖率 ≥ 80%
- **交付物**`apps/admin-portal/src/**/*.test.tsx` + `e2e/*.spec.ts`
- **验收标准**:覆盖率 ≥ 80%E2E 覆盖登录 → dashboard → 用户 CRUD → 审计日志主链路
#### 16.16 Dockerfile 多阶段 + /api/health + /api/ready
- **交付物**`apps/admin-portal/Dockerfile`builder + runtime非 root+ `src/app/api/health/route.ts` + `src/app/api/ready/route.ts`
- **验收标准**`/api/health` 返回 200`/api/ready` 检查 Shell URL 可达Dockerfile HEALTHCHECK 配置
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai16 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai16 自行补充
### 4.1 我依赖(上游就绪标志
- [ ] teacher-portal Shell MF exposes/shared 就绪ai13ARB-002—— Remote 挂载前提
- [ ] teacher-bff GraphQL :3003 启用ai03—— admin 命名空间可用(**ISSUE-005 待 ai03 补齐 schema**
- [ ] api-gateway HTTP :8080 启用 + JWT 验签 + admin 角色校验ai01
- [ ] iam gRPC 50052 启用ai06—— 用户/角色/审计日志数据来源
- [ ] `edu.iam.audit.created` topic 有事件发布ai06—— 审计日志来源(经 teacher-bff 消费)
- [ ] push-gateway WebSocket :8081/ws 启用ai02—— 实时通知(**ISSUE-006 待 coord 仲裁**
- [ ] `packages/contracts` admin 权限点 `ADMIN_*` 常量就绪coord
### 4.2 我的就绪信号(供下游消费)
- [ ] admin-portal dev server :4003 启用
- [ ] MF Remote 可被 AppShell 加载(暴露 `./AdminApp` 模块)
- [ ] 独立壳渲染(首页 + 导航 + 路由守卫 + admin 角色校验)
- [ ] 登录流程可用(复用 Shell `/login`admin 角色校验后重定向 `/admin/dashboard`)—— **不自行实现登录页**(对齐 ARB-002 §2.3
- [ ] GraphQL 查询可执行currentUser / adminDashboard / auditLogs 返回数据)
- [ ] 用户/角色 CRUD 可执行createUser / updateRolePermissions
- [ ] WebSocket 通知可接收
---
## §5 风险跟踪
| 风险 | 影响 | 缓解 | 状态 |
| ---- | ---- | ---- | ---- |
| teacher-bff admin namespace schema 缺失ISSUE-005 | P3+ 全部业务页阻塞 | 提请 coord 仲裁 ai03 在 P3 启动前补齐P2 用 MSW mock 推进 | ⏳ 待仲裁 |
| 01/02 文档 REST/3003/ai07 与仲裁不一致 | 误导实现 | 已提 7 项异议ISSUE-001~007以 contract.md 为修订基准 | ⏳ 待仲裁 |
| Shell 延迟暴露 GraphQLProvider | P2 骨架阻塞 | P2 用独立壳 + mock providersShell 就绪后切换 | ⏳ |
| MF SSR 对齐复杂 | Remote SSR 上下文依赖 Shell | 优先 CSRSSR 仅首屏 dashboard | ⏳ |

View File

@@ -1,45 +1,264 @@
# ai 工作排期
> 负责人ai12
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/ai_contract.md](../contracts/ai_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/ai_contract.md](../contracts/ai_contract.md)、[objections/ai_issue.md](../objections/ai_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 阶段归属:批次 4P5见 [workline.md §1](../workline.md) 甘特图 `b4c: ai12 ai服务 gRPC 50058, after b3a, 13d`
---
## §1 总览
ai 是智能服务,提供 AiService6 个 RPC结合 Elasticsearch 检索与 LLM 网关实现智能问答与推荐。全阶段目标P2 ES 接入+LLM 网关 → P3 AiService 6 RPC → P4-P6 持续优化
ai 是 D6 智能洞察领域的"生成子域"服务Python/FastAPI无状态统一封装 LLM 调用(多 Provider 适配 + 故障切换 + 限流 + 成本控制),提供聊天 / 出题 / 表达优化 / 备课工作流四类 AI 能力。通过 gRPC 查询 content 知识点与 data-ana 学情,通过 Kafka 外发用量计费事件供 data-ana 落 ClickHouse
**端口**HTTP 3008 + gRPC 50058[port-allocation.md](../../../../infra/port-allocation.md) §3/§5 权威源)
**P5 全阶段目标**(退出标准,对应 [pending-features P5](../../../architecture/roadmap/pending-features.md) + ai-allocation §5
1. LLM Provider 适配器模式OpenAI/百川/Anthropic/本地 Ollama统一接口 + 故障切换)
2. SSE / gRPC 流式响应(题目逐字生成 + 前端打字机效果)
3. 出题 Prompt 模板管理YAML + Jinja2模板 CRUD + 参数注入:年级/学科/难度/知识点)
4. 备课工作流 4 步编排(分析学情 → 推荐知识点 → 生成题目 → 教师审核 → 入库)
5. 用量计费 / 频率限制(按用户 / 按 IP / 按 token / 按学校配额)
6. 生成质量门禁RuleValidator + LLMJudge评估通过率 > 80%
7. 安全层PII 脱敏 + Prompt 注入防御 + 输出内容审核)
---
## §2 全阶段甘特图P2-P6各 AI 自行细化
## §2 全阶段甘特图P513 天
> 对齐 [workline.md §1](../workline.md) 批次 4`ai12 ai服务 gRPC 50058 :b4c, after b3a, 13d`b3a = content P4 就绪后启动)
```mermaid
gantt
title ai12 ai 全阶段排期
title ai12 ai 服务 P5 排期13 天)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a12a, 2026-07-10, Xd
section M14 基础架构第1-4天
12.1 LLMProvider抽象+4适配器 :crit, a12a, 2026-07-10, 2d
12.2 ProviderFailoverChain+CircuitBreaker :a12b, after a12a, 1d
12.3 gRPC server(Chat+StreamChat)+ActionState整改 :crit, a12c, after a12a, 2d
12.4 Redis多维度限流+Dockerfile多阶段 :a12d, after a12b, 1d
section M15 出题核心第5-9天
12.5 PromptTemplateService+Jinja2渲染 :crit, a12e, after a12c, 2d
12.6 GenerateQuestion+StreamGenerateQuestion逐字流式 :crit, a12f, after a12e, 2d
12.7 RuleValidator+LLMJudge+QualityGate评估三道防线 :a12g, after a12f, 1d
12.8 UsageRecorder+KafkaProducer+QuotaEnforcer :a12h, after a12g, 1d
12.9 PIIRedactor+InputSanitizer+OutputModerator安全层 :a12i, after a12g, 1d
section M16 备课工作流第10-13天
12.10 gRPC client(content/data-ana/iam)+interceptor :crit, a12j, after a12f, 1d
12.11 LessonPreparationWorkflow 4步编排+状态机 :crit, a12k, after a12j, 2d
12.12 WorkflowStateStore(Redis)+教师审核+content入库 :a12l, after a12k, 1d
12.13 集成测试+契约测试+文档同步+arch:scan :a12m, after a12l, 1d
```
> **注意**:以上为 coord 初始规划ai12 接管后必须自行细化为完整 P2-P6 排期。
**关键路径**(红色 critLLMProvider 抽象 → gRPC server → PromptTemplateService → GenerateQuestion → gRPC client → 备课工作流编排
---
## §3 详细任务
### 全阶段任务
### M14 基础架构第1-4天
#### P5-12.1LLMProvider 抽象 + 4 适配器
- **负责人**ai12
- **交付物**:⚠️ 由 ai12 自行补充
- **依赖**:见 [contracts/ai_contract.md](../contracts/ai_contract.md)
- **验收标准**:⚠️ 由 ai12 自行补充
- **依赖**P5 起点)
- **交付物**
- `services/ai/src/ai/providers/base.py``LLMProvider` 抽象接口chat / stream_chat / embed
- `services/ai/src/ai/providers/openai_provider.py`
- `services/ai/src/ai/providers/anthropic_provider.py`
- `services/ai/src/ai/providers/baichuan_provider.py`
- `services/ai/src/ai/providers/local_ollama_provider.py`
- 重构 `llm_client.py` 为基于抽象接口的调用
- **验收标准**
- 4 Provider 切换可用(通过 `llm_model_routing` 配置路由)
- httpx 异步调用,不依赖 openai SDK
- 单元测试覆盖 ≥ 80%(用 MockLLMProvider
- `ruff check src/` 零错误
#### P5-12.2ProviderFailoverChain + CircuitBreaker
- **负责人**ai12
- **依赖**P5-12.1
- **交付物**
- `services/ai/src/ai/providers/failover.py`(按优先级尝试 Provider失败自动切换
- `services/ai/src/ai/providers/circuit_breaker.py`(连续 3 次失败触发熔断 60s
- **验收标准**:单 Provider 故障自动切下一个熔断器状态正确closed/open/half_open
#### P5-12.3gRPC server + ActionState 整改P0 阻塞)
- **负责人**ai12
- **依赖**P5-12.1**前置**ISSUE-03coord 升级 ai.proto 到 v1 完整版)
- **交付物**
- `services/ai/src/ai/grpc_server.py``grpc.aio` server端口 50058
- 实现 `Chat` + `StreamChat` 两个 RPC含流式
- `grpc.aio.ServerInterceptor` 透传 W3C traceparent
- **ActionState 整改**ISSUE-09所有 HTTP 端点 + gRPC RPC 返回值改为 `{success, data, error:{code,message,details,traceId}}`,删除顶层 `degraded` 字段
- **验收标准**
- teacher-bff 可调通 ai gRPC 50058 `Chat` / `StreamChat`(含流式)
- HealthService.Check 返回 SERVING
- 响应信封 004 §11.5 合规
- `pnpm run arch:scan` 更新 arch.db
#### P5-12.4Redis 多维度限流 + Dockerfile 多阶段
- **负责人**ai12
- **依赖**P5-12.2
- **交付物**
- `services/ai/src/ai/middleware/rate_limit.py`Redis 令牌桶user/IP/school 三维度)
- `services/ai/Dockerfile`(多阶段构建,目标镜像 < 200MB
- **验收标准**限流命中准确user 10/min、IP 30/min、school 100/min镜像 < 200MB
---
### M15 出题核心第5-9天
#### P5-12.5PromptTemplateService + Jinja2 渲染
- **负责人**ai12
- **依赖**P5-12.3
- **交付物**
- `services/ai/src/ai/prompts/*.yaml`5+ 模板generate_question / optimize_expression / chat / lesson_plan 等)
- `services/ai/src/ai/services/prompt_template_service.py`(模板注册 + Jinja2 渲染 + CRUD
- HTTP 端点:`GET/POST/PUT /ai/v1/prompts`
- **验收标准**5+ 模板可渲染;变量缺失返回 `AI_PROMPT_RENDER_FAILED`;模板缓存 1h TTL
#### P5-12.6GenerateQuestion + StreamGenerateQuestion 逐字流式
- **负责人**ai12
- **依赖**P5-12.5**前置**ISSUE-03ai.proto 补 `StreamGenerateQuestion` + `GenerateQuestionRequest` 字段扩展)
- **交付物**
- `services/ai/src/ai/services/question_generation_service.py`
- gRPC RPC`GenerateQuestion` + `StreamGenerateQuestion`(题目逐字流式生成)
- HTTP 端点:`POST /ai/v1/generate/question` + `POST /ai/v1/generate/question/stream`
- **验收标准**题目逐字流式返回Pydantic 请求模型完整grade/knowledge_point_ids/question_type/count
#### P5-12.7评估三道防线RuleValidator + LLMJudge + QualityGate
- **负责人**ai12
- **依赖**P5-12.6
- **交付物**
- `services/ai/src/ai/evaluation/rule_validator.py`(题型匹配/答案非空/解析合理/知识点覆盖)
- `services/ai/src/ai/evaluation/llm_judge.py`LLM-as-judge5 维度加权评分)
- `services/ai/src/ai/evaluation/quality_gate.py`(阈值 0.7,不达标重试 < 3 次)
- **验收标准**:评估通过率 > 80%;不达标自动重试;重试耗尽返回 `AI_EVALUATION_FAILED`
#### P5-12.8UsageRecorder + KafkaProducer + QuotaEnforcer
- **负责人**ai12
- **依赖**P5-12.6**前置**ISSUE-02topic 裁决)+ ISSUE-04events.proto 补 AIUsageEvent
- **交付物**
- `services/ai/src/ai/usage/usage_recorder.py`token 消耗统计)
- `services/ai/src/ai/usage/kafka_producer.py``aiokafka` + acks=all + idempotent + transactional_id
- `services/ai/src/ai/usage/quota_enforcer.py`(学校/教师月度配额Redis 计数)
- HTTP 端点:`GET /ai/v1/usage/me` + `GET /ai/v1/usage/school/{id}`
- **验收标准**:用量事件落 data-ana ClickHouse配额超限返回 `AI_QUOTA_EXCEEDED`event_id SETNX 去重
#### P5-12.9安全层PIIRedactor + InputSanitizer + OutputModerator
- **负责人**ai12
- **依赖**P5-12.6
- **交付物**
- `services/ai/src/ai/security/pii_redactor.py`(学生姓名/手机号/身份证/邮箱脱敏)
- `services/ai/src/ai/security/input_sanitizer.py`Prompt 注入防御)
- `services/ai/src/ai/security/output_moderator.py`(敏感词过滤 + 安全校验)
- **验收标准**安全测试通过PII 检出返回 `AI_PII_DETECTED`;注入检出返回 `AI_PROMPT_INJECTION_DETECTED`
---
### M16 备课工作流第10-13天
#### P5-12.10gRPC clientcontent / data-ana / iam+ interceptor
- **负责人**ai12
- **依赖**P5-12.3**前置**content gRPC 50054 就绪ai09 P4、ISSUE-07iam GetEffectiveDataScope P4 补全)
- **交付物**
- `services/ai/src/ai/clients/content_client.py`KnowledgeGraphService.GetPrerequisites / GetLearningPath
- `services/ai/src/ai/clients/data_ana_client.py`AnalyticsService.GetStudentWeakness / GetLearningTrend / GetClassPerformance
- `services/ai/src/ai/clients/iam_client.py`GetEffectiveDataScopeRedis 缓存 5min
- `grpc.aio.ClientInterceptor`trace 注入 + 重试 + 熔断)
- **验收标准**:下游 gRPC 不可达时降级(跳过学情查询 + `degraded:true`DataScope 缓存命中
#### P5-12.11LessonPreparationWorkflow 4 步编排 + 状态机
- **负责人**ai12
- **依赖**P5-12.10**前置**ISSUE-03ai.proto 补 `GenerateLessonPlan` RPC
- **交付物**
- `services/ai/src/ai/services/lesson_preparation_workflow.py`4 步:分析学情 → 推荐知识点 → 生成题目 → 教师审核)
- 状态机实现02-architecture-design.md §2.4Pending→Analyzing→Recommended→Generating→PendingReview→Persisted
- gRPC RPC`GenerateLessonPlan`
- HTTP 端点:`POST /ai/v1/lesson/preparation` + `GET /ai/v1/lesson/preparation/{id}`
- **验收标准**:端到端跑通 4 步;评估未通过自动重试 < 3 次;工作流状态可查询
#### P5-12.12WorkflowStateStore + 教师审核 + content 入库
- **负责人**ai12
- **依赖**P5-12.11**前置**content `QuestionService.CreateQuestions`(待 coord 补 proto见 02 doc §7
- **交付物**
- `services/ai/src/ai/workflow/workflow_state_store.py`Redis 持久化key `ai:workflow:{id}`TTL 24h
- HTTP 端点:`POST /ai/v1/lesson/preparation/{id}/confirm`(教师确认/修改/拒绝)
- 调 content.CreateQuestions 入库
- **验收标准**24h 内工作流可恢复;教师可审核/修改/拒绝入库成功24h 未审核过期
#### P5-12.13:集成测试 + 契约测试 + 文档同步
- **负责人**ai12
- **依赖**P5-12.12
- **交付物**
- `services/ai/tests/`pytest + pytest-asyncio + testcontainers覆盖率 ≥ 80%
- 契约测试pact-pythonai.proto 与 teacher-bff 一致性)
- 更新 `services/ai/README.md` + `docs/troubleshooting/known-issues.md` ai 分区
- `pnpm run arch:scan` 确认 arch.db 已更新
- **验收标准**`ruff check src/` + `pytest` 零错误;覆盖率 ≥ 80%README 含完整架构图
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai12 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai12 自行补充
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供方 | 就绪标志 | 状态 | 阻塞任务 |
| --------------------------------------------------- | ------------- | ----------------------------------------- | ---- | ------------- |
| ai.proto 升级 v1 完整版6 RPC + 字段扩展) | coord (shared-proto) | proto 文件含 GenerateLessonPlan / StreamGenerateQuestion | ⏳ ISSUE-03 | P5-12.3/12.6/12.11 |
| events.proto 补 AIUsageEvent message | coord (shared-proto) | proto 含 AIUsageEvent | ⏳ ISSUE-04 | P5-12.8 |
| ai 用量事件 topic 命名裁决 | coord | 004 §7.2 补登 | ⏳ ISSUE-02 | P5-12.8 |
| content gRPC 50054 启用 | ai09 (content) | HealthService.Check = SERVING | ⏳ P4 | P5-12.10 |
| data-ana gRPC 50055 启用(可选) | ai11 (data-ana) | HealthService.Check = SERVING | ⏳ P4 | P5-12.10(可降级) |
| iam `GetEffectiveDataScope` RPC P4 补全 | ai06 (iam) + coord | iam.proto 含此 RPC | ⏳ ISSUE-07 | P5-12.10(可降级) |
| LLM Provider API keyOpenAI / 百川 / Ollama | 人类决策者 | 环境变量配置 | — | P5-12.1 |
### 4.2 我的就绪信号(供下游消费)
| 就绪标志 | 消费方 | 状态 |
| ------------------------------------------------- | ---------------------- | ---- |
| ai gRPC 50058 启用HealthService.Check = SERVING | teacher-bff (ai03) | ⏳ |
| AiService.Chat / StreamChat 可调用(含流式) | teacher-bff | ⏳ |
| AiService.GenerateQuestion / StreamGenerateQuestion 可调用 | teacher-bff | ⏳ |
| AiService.GenerateLessonPlan 可调用P5 补全) | teacher-bff | ⏳ |
| AiService.OptimizeExpression 可调用 | teacher-bff | ⏳ |
| ai 用量事件 topic 可发布(供 data-ana 统计) | data-ana (ai11) | ⏳ |
### 4.3 完成信号(批次 4 P5 完成)
ai P5 完成的 5 个标志:
1. ai12ai gRPC 50058 启用 + 6 RPC 全部实现 + HealthService SERVING
2. LLM Provider 4 适配器 + FailoverChain + CircuitBreaker 可用
3. 备课工作流 4 步端到端跑通(含教师审核 + content 入库)
4. 用量事件可发布到 Kafka + data-ana ClickHouse 可落库
5. 端到端teacher-portal 教师 AI 出题 → 流式返回 → 审核入库
---
## §5 风险与缓解
| 风险 | 缓解措施 |
| ----------------------------- | -------------------------------------------------------------------------- |
| coord 未在 P5 启动前补全 protoISSUE-02/03/04 | ai12 先按 02-architecture-design.md §3.3/§4.2 建议 schema 实现proto 落地后对齐 |
| content / iam gRPC 未就绪 | 降级:跳过学情查询 + `degraded:true`;配额降级为仅 user_id 维度 |
| LLM API key 未配置 | 降级骨架响应已具备main.py 当前行为) |
| 工作流状态丢失Redis 故障) | Redis 哨兵P6 硬化)+ 事件日志P6 评估迁移 TemporalISSUE-06 |

View File

@@ -1,57 +1,393 @@
# api-gateway 工作排期
> 负责人ai01
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/api-gateway_contract.md](../contracts/api-gateway_contract.md)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/api-gateway_contract.md](../contracts/api-gateway_contract.md)、[objections/api-gateway_issue.md](../objections/api-gateway_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 依据:[coord-final-decisions.md](../../coord-final-decisions.md) §3.8 W1-W8、[president-final-rulings.md](../../president-final-rulings.md) §2.15/§2.16/§2.19、[02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §9
---
## §1 总览
api-gateway 是 Edu 系统统一入口负责路由、JWT 验签、限流、熔断、CORS。全阶段目标P2 路由+JWT → P3 限流加固 → P4-P6 持续优化
api-gateway 是 Edu 系统统一入口L3 网关层),负责路由转发、JWT RS256 验签、限流、熔断、CORS、可观测性。无业务状态,纯 HTTP 反向代理
**全阶段目标**
- **P2**:路由表 + JWT RS256HTTP JWKS + shared-go 接入 + 错误码 GW_ 前缀 + ActionState 信封 + slog + /readyz 真实检查 + 业务 metrics + tracer 资源属性 + DevMode 防护(遵循 W1-W8 / G1-G17 裁决)
- **P3**路由扩展student-bff :3009+ core-edu 路由
- **P4**路由扩展parent-bff :3010+ content / data-ana 路由
- **P5**路由扩展msg / ai 路由)+ 接入 push-gateway 协作WebSocket 升级透传评估)
- **P6**:限流迁 Redis + per-服务实例熔断评估 + 测试覆盖率 ≥ 80% + 安全加固
**当前状态2026-07-10**P1 已交付classes 域 CRUD 端到端跑通P2 升级未启动。已有仲裁核查发现 6 项未遵循裁决(见 [objections/api-gateway_issue.md](../objections/api-gateway_issue.md) §0.4)。
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai01 api-gateway 全阶段排期
title ai01 api-gateway 全阶段排期P2-P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2 基础
路由表+双入口+shared-go :a1a, 2026-07-10, 3d
JWT校验+JWKS fetcher :a1b, after a1a, 3d
限流+熔断+CORS :a1c, after a1b, 2d
section P2 基础升级批次1
P2.0 修复P0遗留 :crit, p2a, 2026-07-10, 1d
P2.1 shared-go接入 :crit, p2b, after p2a, 2d
P2.2 JWT RS256+JWKS :crit, p2c, after p2b, 3d
P2.3 错误码GW_+ActionState :crit, p2d, after p2c, 1d
P2.4 slog+metrics+tracer :crit, p2e, after p2d, 2d
P2.5 /readyz真实检查 :crit, p2f, after p2e, 1d
P2.6 DevMode防护 :p2g, after p2f, 1d
P2.7 路由表扩展iam/teacher :p2h, after p2g, 1d
section P3-P6 持续优化
路由扩展(student/parent/admin) :a1d, after a1c, 2d
指标+链路加固 :a1e, after a1d, 2d
section P3 路由扩展批次2
P3.1 student-bff路由 :p3a, after p2h, 1d
P3.2 core-edu路由 :p3b, after p3a, 1d
P3.3 API版本化/v1迁移 :p3c, after p3b, 2d
section P4 路由扩展批次3
P4.1 parent-bff路由 :p4a, after p3c, 1d
P4.2 content路由 :p4b, after p4a, 1d
P4.3 data-ana路由 :p4c, after p4b, 1d
section P5 路由扩展批次4
P5.1 msg路由 :p5a, after p4c, 1d
P5.2 ai路由 :p5b, after p5a, 1d
P5.3 push-gateway协作评估 :p5c, after p5b, 2d
section P6 硬化批次5
P6.1 限流迁Redis :p6a, after p5c, 3d
P6.2 per-服务熔断评估 :p6b, after p6a, 2d
P6.3 测试覆盖率80% :p6c, after p6b, 3d
P6.4 安全加固 :p6d, after p6c, 2d
```
> **注意**:以上为 coord 初始规划ai01 接管后必须自行细化为完整 P2-P6 排期。
**关键路径**(红色 critP2.0 → P2.1 → P2.2 → P2.3 → P2.4 → P2.5 → P2.6 → P2.7
**总时间线**P2 约 11 天 + P3-P5 约 9 天 + P6 约 10 天 = 约 30 天
---
## §3 详细任务
### P2:路由表 + JWT + 限流
### P2.0:修复 P0 遗留问题
- **负责人**ai01
- **依赖**:无
- **交付物**
- `services/api-gateway/internal/routing/router.go` — 路由表
- `/api/v1/teacher/*` → teacher-bff:3003 代理
- `/api/v1/iam/*` → iam:3002 代理
- JWT RS256 验签shared-go/jwks
- 限流 + 熔断 + CORS
- **依赖**shared-go 骨架(批次 0 已完成)+ iam GetPublicKeyai06
- **验收标准**路由双入口 + JWT 验签 + 限流 + CORS 白名单
- **完整 P3-P6 任务**:⚠️ 由 ai01 自行补充
- `services/api-gateway/go.mod` L3 改为 `go 1.22`(修复 ISSUE-005
- `go.work` L1 改为 `go 1.22`
- 删除 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §7.1 issue #3 死代码引用ISSUE-008
- 修正 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §6 审计表 metrics 行ISSUE-007
- 修正 [README.md](../../../services/api-gateway/README.md) L38 删除不存在的 `/health` 兼容端点描述
- 修正 [proxy.go](../../../services/api-gateway/internal/proxy/proxy.go) L24 删除冗余 `TrimPrefix("/api")`
- **验收标准**`go build ./...` + `go vet ./...` 通过;文档与代码一致
- **对应 ISSUE**ISSUE-005 / ISSUE-007 / ISSUE-008
### P2.1shared-go 包接入
- **负责人**ai01
- **依赖**[packages/shared-go](../../../packages/shared-go/) 已建立(批次 0 已完成coord 仲裁 ISSUE-002 / ISSUE-004
- **交付物**
- `go.work` 增加 `./packages/shared-go`
- `services/api-gateway/go.mod` 增加 `github.com/edu-cloud/shared-go` 依赖
- 按 ISSUE-004 仲裁结果接入 shared-go/loggerzap 或 slog取决于 coord 裁决)
- [tracer.go](../../../services/api-gateway/internal/observability/tracer.go) 评估接入 shared-go/tracer若接口兼容
- [config.go](../../../services/api-gateway/internal/config/config.go) 评估接入 shared-go/env
- **验收标准**`go build ./...` 通过import shared-go 成功logger 输出结构化 JSON
- **对应 ISSUE**ISSUE-002 / ISSUE-004
### P2.2JWT RS256 升级 + JWKS 缓存
- **负责人**ai01
- **依赖**iam (ai06) 暴露 `GET /.well-known/jwks.json` HTTP 端点coord 仲裁 ISSUE-001确认 HTTP JWKS
- **交付物**
- [auth.go](../../../services/api-gateway/internal/middleware/auth.go) 改用 shared-go/jwks.Fetcher`jwks.NewFetcher(cfg.JWKSURL)`
- HS256 逻辑废弃,`cfg.JWTSecret` 仅 DevMode 下用作 mock 密钥
- JWKS 缓存策略TTL 5minshared-go/jwks 默认kid 未命中时强制刷新,刷新失败保留旧公钥
- 启动时同步拉取一次 JWKS失败则 panic 拒绝启动
- claims 增加 `data_scope` 字段提取,注入 `x-data-scope`
- `internal/config/config.go` 增加 `JWKSURL` 字段(环境变量 `IAM_JWKS_URL`
- **验收标准**JWT RS256 验签通过JWKS 缓存命中率达 99%+kid 未命中自动刷新
- **对应裁决**W1错误码加 GW_ 前缀、§2.16HTTP JWKS非 gRPC
### P2.3:错误码 GW_ 前缀 + ActionState 信封
- **负责人**ai01
- **依赖**:无
- **交付物**
- [auth.go](../../../services/api-gateway/internal/middleware/auth.go) 错误码改为 `GW_UNAUTHORIZED` / `GW_INVALID_TOKEN` / `GW_INVALID_CLAIMS`
- [ratelimit.go](../../../services/api-gateway/internal/middleware/ratelimit.go) 响应体改为 `{success:false,error:{code:"GW_RATE_LIMITED",message:"...",retry_after:60}}`
- [circuit-breaker.go](../../../services/api-gateway/internal/middleware/circuit-breaker.go) 响应体改为 `{success:false,error:{code:"GW_CIRCUIT_OPEN",message:"...",retry_after:30}}`
- [recovery.go](../../../services/api-gateway/internal/middleware/recovery.go) 响应体改为 `{success:false,error:{code:"GW_INTERNAL_ERROR",message:"...",request_id:"..."}}`
- `RequestBodyLimit` 超限响应改为 `{success:false,error:{code:"GW_REQUEST_TOO_LARGE",message:"..."}}`
- **验收标准**:所有错误响应符合 ActionState 信封;错误码统一 `GW_` 前缀
- **对应裁决**W1 / W2 / G14
- **对应 ISSUE**ISSUE-009
### P2.4slog + 业务 metrics + tracer 资源属性
- **负责人**ai01
- **依赖**P2.1 shared-go 接入完成
- **交付物**
- 按 ISSUE-004 仲裁结果统一 loggerzap 或 slog
- 所有 `log.Printf` / `log.Println` / `log.Fatal` 改为结构化日志(带 `request_id` / `trace_id` / `user_id` / `method` / `path` / `status` / `latency_ms` 字段)
- 新增 `internal/observability/metrics.go`,注册 7 个业务指标:
- `api_gateway_http_requests_total`Countermethod/endpoint/status
- `api_gateway_http_request_duration_seconds`Histogrammethod/endpoint
- `api_gateway_circuit_breaker_state`Gaugeservice/state
- `api_gateway_rate_limited_total`Counterip
- `api_gateway_proxy_upstream_duration_seconds`Histogramupstream
- `api_gateway_jwks_refresh_total`Counterresult
- `api_gateway_auth_failures_total`Counterreason
- [tracer.go](../../../services/api-gateway/internal/observability/tracer.go) 资源属性补全:`service.name` + `service.version`(编译时注入)+ `deployment.environment`ENV 变量)+ `host.name`
- Metrics 中间件:在 Auth 之后、CircuitBreaker 之前注册(统计通过鉴权的请求)
- **验收标准**`/metrics` 端点返回 7 个业务指标;日志为 JSON 结构化tracer 资源属性完整
- **对应裁决**W3 / W5 / W6 / G4 / G5 / G6
### P2.5/readyz 真实健康检查
- **负责人**ai01
- **依赖**:所有下游服务实现 `/healthz`P2 阶段 iam / teacher-bff 已就绪)
- **交付物**
- [health.go](../../../services/api-gateway/internal/health/health.go) `Readyz` 重构为并行 ping 下游 `/healthz`
- 下游清单从 `cfg.ServicesURL` 动态读取iam / classes / teacher-bff / core-edu / content / msg / ai / data-ana
- 超时 2s任一不可达返回 503 + `{"status":"error","unhealthy":["iam","core-edu"]}`
- 全部可达返回 200 + `{"status":"ok"}`
- 可选依赖软失败规则:未启用 gRPC 的下游P3-P5 阶段未就绪的服务)失败仅告警,返回 200 + `degraded: true`(依据 president-final-rulings.md §3.3
- **验收标准**/readyz 真实检查下游;某服务下线时返回 503
- **对应裁决**W4 / G2
### P2.6DevMode 生产防护
- **负责人**ai01
- **依赖**:无
- **交付物**
- [config.go](../../../services/api-gateway/internal/config/config.go) `Load()` 增加 `ENV` 环境变量读取
-`DevMode=true && ENV=production``panic` 拒绝启动
- 启动日志打印 `ENV` / `DevMode` 状态
- **验收标准**`DEV_MODE=true ENV=production` 启动失败;`DEV_MODE=true ENV=development` 启动成功
- **对应裁决**W7
### P2.7路由表扩展iam / teacher
- **负责人**ai01
- **依赖**coord 仲裁 ISSUE-003API 版本化路由规则iam (ai06) / teacher-bff (ai03) P2 就绪
- **交付物**
- 按 ISSUE-003 仲裁结果更新路由(方案 A/B/C 之一)
- 若方案 Aiam 路由改为 `/api/v1/iam/v1/*path`proxy 透传 `/iam/v1/*path`
- teacher-bff 路由保持 `/api/v1/teacher/*path`
- [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §4.1 路由表同步更新
- **验收标准**:路由表与代码一致;前端调用 `/api/v1/iam/v1/auth/login` 透传到 iam 服务
- **对应裁决**§2.15
---
### P3.1student-bff 路由
- **负责人**ai01
- **依赖**student-bff (ai04) P3 就绪
- **交付物**
- `main.go` 增加 student-bff 路由:`/api/v1/student` + `/api/v1/student/*path``cfg.StudentBffURL`:3009
- `config.go` 增加 `StudentBffURL` 字段(环境变量 `STUDENT_BFF_URL`
- **验收标准**`/api/v1/student/*` 代理到 student-bff:3009
### P3.2core-edu 路由
- **负责人**ai01
- **依赖**core-edu (ai08) P3 就绪classes 服务已合并入 core-eduC1 裁决)
- **交付物**
- `main.go` classes 路由目标改为 core-edu`cfg.ClassesServiceURL``cfg.CoreEduServiceURL`
- 或保留 classes 路由别名proxy 到 core-edu
- exams / homework / grades 路由已在 P1 实现,无需修改
- **验收标准**`/api/v1/classes/*` 代理到 core-edu:3004
### P3.3API 版本化 /v1 迁移
- **负责人**ai01
- **依赖**P2.7 路由规则仲裁结果core-edu (ai08) P3 就绪
- **交付物**
- 按 ISSUE-003 仲裁结果,全服务路由统一加 `/v1` 前缀
- core-edu 路由:`/api/v1/exams/v1/*path` 等(若方案 A
- 更新 [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §4.1 路由表
- **验收标准**:所有业务路由含 `/v1` 版本前缀
---
### P4.1parent-bff 路由
- **负责人**ai01
- **依赖**parent-bff (ai05) P4 就绪
- **交付物**
- `main.go` 增加 parent-bff 路由:`/api/v1/parent` + `/api/v1/parent/*path``cfg.ParentBffURL`:3010
- `config.go` 增加 `ParentBffURL` 字段
- **验收标准**`/api/v1/parent/*` 代理到 parent-bff:3010
### P4.2content 路由
- **负责人**ai01
- **依赖**content (ai09) P4 就绪
- **交付物**
- `main.go` 已有 content 路由P1 实现textbooks / chapters / knowledge-points / questions
- 验证路由目标 `cfg.ContentServiceURL`:3005正确
- 按 P3.3 版本化规则加 `/v1` 前缀
- **验收标准**content 路由可用 + 版本化
### P4.3data-ana 路由
- **负责人**ai01
- **依赖**data-ana (ai11) P4 就绪
- **交付物**
- `main.go` 已有 data-ana 路由P1 实现analytics
- 增加 `dashboard` 路由别名:`/api/v1/dashboard` + `/*path` → data-ana
- 按版本化规则加 `/v1` 前缀
- **验收标准**data-ana 路由可用 + dashboard 别名可用
---
### P5.1msg 路由
- **负责人**ai01
- **依赖**msg (ai10) P5 就绪
- **交付物**
- `main.go` 已有 msg 路由P1 实现notifications
- 增加 `messages` 路由别名:`/api/v1/messages` + `/*path` → msg
- 按版本化规则加 `/v1` 前缀
- **验收标准**msg 路由可用 + messages 别名可用
### P5.2ai 路由
- **负责人**ai01
- **依赖**ai (ai12) P5 就绪
- **交付物**
- `main.go` 已有 ai 路由P1 实现)
- 按版本化规则加 `/v1` 前缀
- **验收标准**ai 路由可用
### P5.3push-gateway 协作评估
- **负责人**ai01
- **依赖**push-gateway (ai02) P5 就绪
- **交付物**
- 评估 WebSocket 升级请求是否需要 Gateway 透传到 push-gateway
- 若需要:增加 `/ws` + `/sse` 路由透传到 push-gateway:8081
- 若不需要:文档说明 WebSocket 直连 push-gateway不经过 Gateway
- 更新 [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §7 交互点清单
- **验收标准**WebSocket 推送链路可用(透传或直连)
---
### P6.1:限流迁 Redis
- **负责人**ai01
- **依赖**Redis 基础设施就绪
- **交付物**
- [ratelimit.go](../../../services/api-gateway/internal/middleware/ratelimit.go) 改用 Redis 令牌桶(`github.com/go-redis/redis_rate/v10`
- 支持多副本一致限流
- 增加用户级限流(基于 `x-user-id` 头)
- 登录接口额外加用户级限流(防爆破)
- 保留 DevMode 下内存令牌桶回退
- **验收标准**:多副本部署时限流一致;用户级限流生效
### P6.2per-服务实例熔断评估
- **负责人**ai01
- **依赖**P6.1 完成
- **交付物**
- 评估是否将共享 `downstream` 熔断器拆分为 per-服务实例iam / core-edu / teacher-bff 等)
- 若拆分:每个下游服务独立熔断状态,互不影响
- 若不拆分:保持 W8 裁决现状,文档说明理由
- 按 W8 裁决,此项 P6 单独评估,不强制拆分
- **验收标准**:评估报告 + 决策记录
### P6.3:测试覆盖率 80%
- **负责人**ai01
- **依赖**P2-P5 全部完成
- **交付物**
- 补全 [auth_test.go](../../../services/api-gateway/internal/middleware/auth_test.go)JWKS 验签 / claims 解析 / DevMode 旁路 / 公开路径白名单
- 补全 [cors_test.go](../../../services/api-gateway/internal/middleware/cors_test.go):白名单匹配 / 预检请求 / Vary 头
- 补全 [security_test.go](../../../services/api-gateway/internal/middleware/security_test.go):安全头设置 / Server 头移除
- 补全 [recovery_test.go](../../../services/api-gateway/internal/middleware/recovery_test.go)panic 捕获 / request_id 生成 / ActionState 信封
- 补全 [requestid_test.go](../../../services/api-gateway/internal/middleware/requestid_test.go):透传 / 生成 / 响应头
- 补全 [proxy_test.go](../../../services/api-gateway/internal/proxy/proxy_test.go):路径前缀去除 / Host 改写
- 补全 [health_test.go](../../../services/api-gateway/internal/health/health_test.go)/readyz 下游检查 / 软失败规则
- **验收标准**`go test ./... -cover` 覆盖率 ≥ 80%
### P6.4:安全加固
- **负责人**ai01
- **依赖**P6.1-P6.3 完成
- **交付物**
- 评估 IP 黑名单 / WAF 规则是否在 Gateway 层实现(建议在 Istio 层做,本服务不介入)
- 限流策略表 per-路由细化02 §4.2
- 熔断阈值 per-服务配置02 §4.3
- CORS 白名单生产环境强制配置(禁止 `*`
- 审计日志:记录所有 401 / 403 / 429 / 503 响应
- **验收标准**:安全扫描通过;审计日志可追溯
---
## §4 依赖与就绪信号
- **我依赖**iam GetPublicKey RPCai06
- **我的就绪信号**api-gateway :8080 可访问 + JWT 验签可用
### 4.1 我依赖的上游就绪标志
| 上游 | 就绪标志 | 阶段 | 状态 |
| ---- | -------- | ---- | ---- |
| coord | shared-go 包骨架tracer/logger/jwks/env| 批次 0 | ✅ 已完成 |
| coord | ISSUE-001 仲裁JWKS vs gRPC| P2 启动前 | ⏳ 待仲裁 |
| coord | ISSUE-002 仲裁shared-go 接入)| P2 启动前 | ⏳ 待仲裁 |
| coord | ISSUE-003 仲裁API 版本化路由)| P2.7 前 | ⏳ 待仲裁 |
| coord | ISSUE-004 仲裁zap vs slog| P2.1 前 | ⏳ 待仲裁 |
| iam (ai06) | `GET /.well-known/jwks.json` HTTP 端点 + RS256 签发 | P2 | ⏳ |
| iam (ai06) | gRPC 50052 启用(不影响 GatewayGateway 走 HTTP| P2 | ⏳ |
| teacher-bff (ai03) | `POST /graphql` :3003 启用 | P2 | ⏳ |
| student-bff (ai04) | `POST /graphql` :3009 启用 | P3 | ⏳ |
| core-edu (ai08) | gRPC 50053 启用 + classes 合并 | P3 | ⏳ |
| parent-bff (ai05) | `POST /graphql` :3010 启用 | P4 | ⏳ |
| content (ai09) | gRPC 50054 启用 | P4 | ⏳ |
| data-ana (ai11) | gRPC 50055 启用 | P4 | ⏳ |
| msg (ai10) | gRPC 50056 启用 | P5 | ⏳ |
| ai (ai12) | gRPC 50058 启用 | P5 | ⏳ |
| push-gateway (ai02) | :8081 启用 + /internal/push | P5 | ⏳ |
### 4.2 我的就绪标志(供下游消费)
| 阶段 | 就绪标志 | 状态 |
| ---- | -------- | ---- |
| P2 | api-gateway :8080 可访问 + JWT RS256 验签可用 + 7 个业务指标 + /readyz 真实检查 | ⏳ |
| P2 | /api/v1/iam/* + /api/v1/teacher/* 路由可用 | ⏳ |
| P3 | /api/v1/student/* + /api/v1/exams/* 等路由可用 | ⏳ |
| P4 | /api/v1/parent/* + /api/v1/textbooks/* + /api/v1/analytics/* 路由可用 | ⏳ |
| P5 | /api/v1/notifications/* + /api/v1/ai/* 路由可用 | ⏳ |
| P6 | 限流迁 Redis + 测试覆盖率 ≥ 80% | ⏳ |
---
## §5 风险与应对
| 风险 | 概率 | 影响 | 应对 |
| ---- | ---- | ---- | ---- |
| coord 仲裁延期ISSUE-001/002/003/004| 中 | P2 阻塞 | ai01 先按 HTTP JWKS + shared-go + 方案 A 推进,仲裁后调整 |
| iam JWKS 端点延期 | 中 | P2.2 阻塞 | DevMode 下用本地 mock RS256 公钥(与 mock 私钥配对) |
| shared-go 接口不兼容 | 低 | P2.1 阻塞 | ai01 自行适配,或反馈 coord 修改 shared-go |
| 下游服务未实现 /healthz | 中 | /readyz 误报 | 软失败规则:未就绪服务失败仅告警,返回 200 + degraded |
| JWKS 缓存过期时 iam 不可达 | 低 | 全量 401 | fail-open 1 次后 fail-close监控 `jwks_refresh_total` 指标 |
| DevMode 旁路误开到生产 | 低 | 鉴权绕过 | P2.6 生产防护W7 裁决) |
---
## §6 与其他模块的协作
| 模块 | 协作内容 | 时机 |
| ---- | -------- | ---- |
| iam (ai06) | JWKS HTTP 端点 + JWT RS256 签发 | P2 |
| teacher-bff (ai03) | 反向代理 :3003 GraphQL | P2 |
| student-bff (ai04) | 反向代理 :3009 GraphQL | P3 |
| parent-bff (ai05) | 反向代理 :3010 GraphQL | P4 |
| core-edu (ai08) | 反向代理 :3004 + classes 合并 | P3 |
| content (ai09) | 反向代理 :3005 | P4 |
| data-ana (ai11) | 反向代理 :3006 | P4 |
| msg (ai10) | 反向代理 :3007 | P5 |
| ai (ai12) | 反向代理 :3008 | P5 |
| push-gateway (ai02) | WebSocket 升级透传评估 | P5 |
| coord | shared-go 包维护 + ISSUE 仲裁 | 持续 |

View File

@@ -1,45 +1,359 @@
# content 工作排期
> 负责人ai09
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/content_contract.md](../contracts/content_contract.md)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/content_contract.md](../contracts/content_contract.md)、[objections/content_issue.md](../objections/content_issue.md)、[../../services/content/docs/02-architecture-design.md](../../../services/content/docs/02-architecture-design.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 当前分支:`feat-review-content-module-docs-WAIyMA`
---
## §1 总览
content 是内容服务,提供 TextbookService、ChapterService、KnowledgeGraphService、QuestionService结合 Neo4j 知识图谱与 Elasticsearch 全文检索。全阶段目标P2 服务骨架+Neo4j+ES → P3 四大 Service 实现 → P4-P6 持续优化
content 是内容资源中台服务P4 阶段),承载 D4 内容资源限界上下文,提供 Textbook / Chapter / KnowledgePoint / Question 四个聚合的 CRUD 与知识图谱查询
**关键交付**
- gRPC 50054 + 4 ServiceTextbook/Chapter/KnowledgeGraph/Question按 [coord-final-decisions.md §3.3](../../coord-final-decisions.md) N1/N3/N5 仲裁P4 首次实现即启用 gRPC + 补全 QuestionService/ChapterService proto
- MySQL 写模型 + Neo4j 知识图谱 + Outbox 事件驱动异步同步(禁止业务事务内同步双写 Neo4j
- Kafka 发布 `edu.content.knowledge_point.events` / `edu.content.question.events`(聚合 topic 策略,待 ISSUE-002 仲裁)
- P5 引入 Elasticsearch 全文检索 + AI 出题入库QuestionService.BatchCreateQuestions
- P6+ 长远演进:教材版本管理 / 跨租户内容共享 / 个性化学习路径推荐
**关键路径位置**:批次 3P4依赖批次 2 core-edu 完成实际可并行content 不强依赖 core-edu
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P4-P6
```mermaid
gantt
title ai09 content 全阶段排期
title ai09 content 全阶段排期P4-P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a9a, 2026-07-10, Xd
section P4 基础设施
P4.1 schema 迁移补字段 :crit, c4a, 2026-07-19, 2d
P4.2 Outbox 表+Publisher worker :crit, c4b, after c4a, 3d
P4.3 Kafka producer(idempotent+txn) :crit, c4c, after c4b, 2d
P4.4 Neo4j Sync Worker(异步) :crit, c4d, after c4c, 2d
P4.5 重构 kp.service 移除同步双写 :crit, c4e, after c4d, 1d
section P4 gRPC 契约
P4.6 content.proto 补 ChapterService/QuestionService :crit, c4f, 2026-07-19, 1d
P4.7 gRPC controller 实现(4 Service) :crit, c4g, after c4f, 4d
P4.8 buf generate + 类型校验 :c4h, after c4g, 1d
section P4 横切与质量
P4.9 /readyz 多依赖(DB/Neo4j/Kafka) :c4i, after c4e, 1d
P4.10 ZodError GlobalErrorFilter 分支 :c4j, after c4i, 1d
P4.11 DB 改 getDb()+ID 改 cuid2 :c4k, after c4j, 1d
P4.12 Repository 抽象补齐 :c4l, after c4k, 2d
P4.13 单元测试(Service/Repository)≥60% :c4m, after c4l, 3d
P4.14 修正 README 与实现对齐 :c4n, after c4m, 1d
section P5 ES+AI 集成
P5.1 引入 @elastic/elasticsearch :crit, c5a, after c4m, 1d
P5.2 ES mapping+ensureIndex :crit, c5b, after c5a, 1d
P5.3 ES Sync Worker(消费事件同步索引) :crit, c5c, after c5b, 2d
P5.4 GET /questions/search 检索 API :crit, c5d, after c5c, 2d
P5.5 QuestionService gRPC 完善Publish/Search :c5e, after c5d, 1d
P5.6 AI 出题 BatchCreateQuestions 联调 :c5f, after c5e, 2d
P5.7 检索性能优化(<200ms) :c5g, after c5f, 2d
P5.8 测试覆盖率≥80% :c5h, after c5g, 2d
section P6+ 演进
P6.1 Question 审核工作流状态机 :c6a, after c5h, 3d
P6.2 知识图谱可视化 API :c6b, after c6a, 3d
P6.3 教材版本管理 :c6c, after c6b, 2d
P6.4 /readyz 硬化+监控告警完善 :c6d, after c6c, 2d
```
> **注意**:以上为 coord 初始规划ai09 接管后必须自行细化为完整 P2-P6 排期。
**预估总工期**P4 约 21 天 + P5 约 13 天 + P6+ 约 10 天 = **44 天**(与 workline.md §1 批次 3+4 时间窗口一致)
---
## §3 详细任务
### 阶段任务
### 3.1 P4 阶段任务
#### P4.1 schema 迁移补字段
- **负责人**ai09
- **交付物**:⚠️ 由 ai09 自行补充
- **依赖**:见 [contracts/content_contract.md](../contracts/content_contract.md)
- **验收标准**:⚠️ 由 ai09 自行补充
- **依赖**:无(自身 schema 现状)
- **交付物**
- [textbooks.schema.ts](../../../services/content/src/textbooks/textbooks.schema.ts) textbooks 表补 `status` / `tenant_id` / `metadata` 字段
- chapters 表补 `created_at` / `updated_at` / `status`(解决 [01-understanding.md](../../../services/content/docs/01-understanding.md) C7
- knowledge_points 表补 `difficulty` / `metadata` / `created_at` / `updated_at`(解决 ISSUE-009
- questions 表补 `status` / `source` / `created_by`NULL 起步,解决 ISSUE-007/ `metadata`
- **验收标准**`pnpm typecheck` 通过Drizzle 类型重新生成;迁移脚本可幂等执行
#### P4.2 Outbox 表 + Publisher worker
- **负责人**ai09
- **依赖**P4.1
- **交付物**
- 新建 `src/shared/outbox/outbox.schema.ts`content_outbox_events 表,见 design doc §3.1.5
- 新建 `src/shared/outbox/outbox.publisher.ts`(轮询 PENDING 事件投递 Kafka指数退避重试
- 新建 `src/shared/outbox/outbox.module.ts`
- **验收标准**:业务事务内写 questions + outbox 同事务提交Publisher worker 独立轮询retry_count 累加正确
#### P4.3 Kafka produceridempotent + transactionalId
- **负责人**ai09
- **依赖**P4.2
- **交付物**
- 新建 `src/shared/kafka/producer.ts`kafkajs 客户端idempotent=truetransactionalId=content-producer
- 新建 `src/shared/kafka/kafka.module.ts`
- package.json 添加 kafkajs 依赖
- **验收标准**producer 启动成功transactionalId 唯一;幂等投递无重复
#### P4.4 Neo4j Sync Worker异步同步
- **负责人**ai09
- **依赖**P4.3
- **交付物**
- 新建 `src/shared/sync/neo4j-sync.worker.ts`(消费 content 自身 Outbox 事件,异步创建/更新 Neo4j 节点与关系)
- 消费 `KnowledgePointCreated` / `KnowledgePointPrerequisiteAdded` 等事件
- **验收标准**MySQL 写知识点后Neo4j 节点最终一致出现(延迟 < 2sNeo4j 故障时事件不丢失,恢复后补齐
#### P4.5 重构 knowledge-points.service.ts 移除同步双写
- **负责人**ai09
- **依赖**P4.4
- **交付物**
- [knowledge-points.service.ts](../../../services/content/src/knowledge-points/knowledge-points.service.ts) 删除 `safeCreateNode` 同步写 Neo4j 逻辑
- 改为发 Outbox 事件 `KnowledgePointCreated`
- `addPrerequisite` 改为发 Outbox 事件 `KnowledgePointPrerequisiteAdded`
- **验收标准**:业务事务内不再直接写 Neo4jNeo4j 写入全部走异步 Sync Worker解决 ISSUE-010 + 01-understanding C9
#### P4.6 content.proto 补 ChapterService / QuestionService
- **负责人**ai09
- **依赖**:无(自身 proto 现状)
- **交付物**
- [content.proto](../../../packages/shared-proto/proto/content.proto) 补 ChapterServiceCreateChapter/ListChapters/GetChapter/UpdateChapter/DeleteChapter
- 补 QuestionServiceCreateQuestion/BatchCreateQuestions/GetQuestion/ListQuestions/UpdateQuestion/DeleteQuestion/PublishQuestion/SearchQuestions
- 补 TextbookService.UpdateTextbook / DeleteTextbook
- 补全 message 定义Chapter / Question / QuestionRequest 等)
- 同步补 events.proto 的 KnowledgePointEvent / QuestionEvent / TextbookEvent / ChapterEvent待 ISSUE-002 仲裁后定)
- **验收标准**`buf lint` 通过;`buf breaking` 无破坏性变更(新增字段 OKcontract.md 与 design doc §4.2 RPC 数对齐
#### P4.7 gRPC controller 实现4 Service
- **负责人**ai09
- **依赖**P4.6
- **交付物**
- 新建 `src/textbooks/textbooks.grpc.controller.ts`
- 新建 `src/chapters/chapters.grpc.controller.ts`
- 新建 `src/knowledge-points/knowledge-points.grpc.controller.ts`
- 新建 `src/questions/questions.grpc.controller.ts`
- main.ts 启用 gRPC server 50054
- **验收标准**`grpcurl` 调用 4 Service 全部 RPC 返回正确HealthService.Check 返回 SERVING解决 N1
#### P4.8 buf generate + 类型校验
- **负责人**ai09
- **依赖**P4.7
- **交付物**`pnpm buf:generate` 生成 TS 类型content 服务引用生成类型
- **验收标准**`pnpm typecheck` 通过
#### P4.9 /readyz 多依赖检查
- **负责人**ai09
- **依赖**P4.5
- **交付物**[health.controller.ts](../../../services/content/src/shared/health/health.controller.ts) 改造 /readyz检查 DB / Neo4j / Kafka producer / Kafka consumer lag
- **验收标准**:返回 design doc §6.6 格式Neo4j 不可用 → status=degradedDB 不可用 → status=down解决 N2 + 01-understanding C-section readyz 问题)
#### P4.10 ZodError GlobalErrorFilter 分支
- **负责人**ai09
- **依赖**:无
- **交付物**[global-error.filter.ts](../../../services/content/src/shared/errors/global-error.filter.ts) 增加 ZodError 识别分支,返回 400 + 字段级错误详情
- **验收标准**Zod 校验失败返回 design doc §4.3 错误结构
#### P4.11 DB 改 getDb() + ID 改 cuid2
- **负责人**ai09
- **依赖**:无
- **交付物**
- [database.ts](../../../services/content/src/config/database.ts) 改为 `getDb()` 函数式懒加载(对齐 classes 黄金模板)
- service 层 `randomUUID()` 改为 `cuid2()`package.json 添加 @paralleldrive/cuid2
- **验收标准**:所有 service 使用 getDb();所有 ID 生成用 cuid2
#### P4.12 Repository 抽象补齐
- **负责人**ai09
- **依赖**P4.11
- **交付物**:补齐 textbooks/questions 的 Repository 抽象(与 chapters/knowledge-points 一致)
- **验收标准**Service 层不直接调用 Drizzle API全部走 Repository
#### P4.13 单元测试 ≥ 60%
- **负责人**ai09
- **依赖**P4.12
- **交付物**
- 新建 `*.spec.ts` 覆盖 Service 层 + Repository 层
- 重点覆盖 QuestionsService 题型校验 / KnowledgePointsService 前置依赖 / Outbox Publisher 重试逻辑
- **验收标准**`pnpm test` 通过;覆盖率 ≥ 60%
#### P4.14 修正 README 与实现对齐
- **负责人**ai09
- **依赖**P4.13
- **交付物**[README.md](../../../services/content/README.md) 修正 `TextbooksService.createKnowledgeGraph` 错误描述(实际在 KnowledgePointsService补齐 4 个领域模块说明
- **验收标准**README 与源码完全一致(解决 01-understanding C3
### 3.2 P5 阶段任务
#### P5.1 引入 @elastic/elasticsearch
- **负责人**ai09
- **依赖**P4 全部完成
- **交付物**package.json 添加 @elastic/elasticsearch;新建 `src/config/elasticsearch.ts`
- **验收标准**esClient 单例ES_URL 未配置时 esClient=null 降级
#### P5.2 ES mapping + ensureIndex
- **负责人**ai09
- **依赖**P5.1
- **交付物**:按 design doc §3.3.1 实现 questions 索引 mapping启动时 `ensureIndex` 幂等
- **验收标准**索引创建成功ik_max_word / ik_smart 分词器配置正确
#### P5.3 ES Sync Worker
- **负责人**ai09
- **依赖**P5.2
- **交付物**:新建 `src/shared/sync/es-sync.worker.ts`,消费 `QuestionCreated` / `QuestionUpdated` / `QuestionPublished` / `QuestionDeleted` 事件增量更新索引
- **验收标准**MySQL 写题目后ES 索引最终一致(延迟 < 2s
#### P5.4 GET /questions/search 检索 API
- **负责人**ai09
- **依赖**P5.3
- **交付物**[questions.controller.ts](../../../services/content/src/questions/questions.controller.ts) 增加 `@Get("search")` 端点ES 查询支持 q / type / difficulty / knowledgePointId 过滤
- **验收标准**:检索延迟 < 200msP5 退出标准);返回分页结构
#### P5.5 QuestionService gRPC 完善 Publish/Search
- **负责人**ai09
- **依赖**P5.4
- **交付物**gRPC controller 补 PublishQuestion / SearchQuestions RPC 实现
- **验收标准**:与 contract.md §1.1 完全对齐(解决 ISSUE-004 部分)
#### P5.6 AI 出题 BatchCreateQuestions 联调
- **负责人**ai09
- **依赖**P5.5 + ai12 ai 服务就绪
- **交付物**:与 ai12 联调 BatchCreateQuestions RPC服务账号权限校验
- **验收标准**AI 服务调用成功入库batch_size ≤ 100 限制生效
#### P5.7 检索性能优化
- **负责人**ai09
- **依赖**P5.6
- **交付物**ES 查询 DSL 优化缓存热点查询结果Redis
- **验收标准**P95 延迟 < 200ms
#### P5.8 测试覆盖率 ≥ 80%
- **负责人**ai09
- **依赖**P5.7
- **交付物**补集成测试gRPC + ES + Neo4j 端到端)
- **验收标准**:覆盖率 ≥ 80%
### 3.3 P6+ 演进任务
#### P6.1 Question 审核工作流状态机
- **负责人**ai09
- **依赖**P5 完成
- **交付物**Question.status 状态机完整实现draft → pending_review → published/rejected → archived审核日志表
- **验收标准**:状态转换校验正确;非法转换返回 409
#### P6.2 知识图谱可视化 API
- **负责人**ai09
- **依赖**P6.1
- **交付物**`GET /knowledge-graph/visualization` 返回 nodes/edges 结构(解决 01-understanding L3
- **验收标准**:返回 D3.js / vis.js 可消费的图结构
#### P6.3 教材版本管理
- **负责人**ai09
- **依赖**P6.2
- **交付物**Textbook.version 字段启用;版本切换不破坏题库引用
- **验收标准**:新旧版本教材并存;题库引用按版本隔离
#### P6.4 /readyz 硬化 + 监控告警完善
- **负责人**ai09
- **依赖**P6.3
- **交付物**/readyz 探针列表完善Prometheus 告警规则补齐consumer lag / outbox pending / ES latency
- **验收标准**:告警阈值合理;故障演练通过
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai09 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai09 自行补充
### 4.1 我依赖的上游就绪标志
| 上游 | 就绪标志 | 必需性 | mock 策略 |
| -------------- | ----------------------------------------------------- | ---------------------- | ------------------------------------------ |
| infra | MySQL 8 / Neo4j 5 / Kafka 集群可用 | 🔴 必需 | 本地 docker-compose |
| infra | Redis 可用P5+ 缓存用env.ts 已预留 REDIS_URL | 🟢 P5+ 可选 | 未配置时跳过缓存 |
| infra | Elasticsearch 8 可用P5+ | 🟢 P5+ 必需 | 未配置时检索降级到 MySQL LIKE |
| shared-proto | content.proto / events.proto 补全 | 🔴 必需P4.6 自身完成) | 自行修改 |
| core-edu (ai08) | gRPC 50053 启用 | 🟢 可选content 独立) | 不依赖 core-edu 实时数据 |
| ai (ai12) | gRPC 50058 启用 | 🟢 P5 联调时必需 | grpc-mock 拦截 |
### 4.2 我的就绪标志(供下游消费)
- [ ] **P4 就绪**
- [ ] content gRPC 50054 启用HealthService.Check 返回 SERVING
- [ ] TextbookService 5 RPC 可调用Create/Get/List/Update/Delete
- [ ] ChapterService 5 RPC 可调用Create/Get/List/Update/Delete
- [ ] KnowledgeGraphService 4 RPC 可调用GetPrerequisites/GetLearningPath/AddPrerequisite/RemovePrerequisite
- [ ] QuestionService 7 RPC 可调用Create/BatchCreate/Get/List/Update/Delete/Publish/Search
- [ ] edu.content.knowledge_point.events / edu.content.question.events topic 可发布
- [ ] /readyz 返回 DB/Neo4j/Kafka 三依赖状态
- [ ] **P5 就绪**
- [ ] GET /questions/search 检索 API 可用(延迟 < 200ms
- [ ] QuestionService.SearchQuestions gRPC 可调用
- [ ] BatchCreateQuestions 与 ai12 联调通过
- [ ] **P6+ 就绪**
- [ ] 审核工作流状态机完整
- [ ] 知识图谱可视化 API 可用
### 4.3 我提供的 mock供下游消费
在 content 真实服务就绪前为下游teacher-bff / student-bff / ai / data-ana提供以下 mock
- **gRPC mock**grpc-mock 拦截 50054 端口):
- TextbookService.ListTextbooks 返回固定 5 个 Textbook语数英理化
- ChapterService.ListChapters 返回固定章节树(每教材 10 章)
- KnowledgeGraphService.GetLearningPath 返回固定 8 个 KnowledgePoint 推荐顺序
- KnowledgeGraphService.GetPrerequisites 返回固定 3 个前置知识点
- QuestionService.SearchQuestions 返回固定 20 个 Question含 options
- QuestionService.BatchCreateQuestions 返回成功 + 生成 20 个 ID
- **Kafka mock**content 就绪前不发布真实事件,下游使用本地 stub
---
## §5 风险与缓解
| 风险 | 阶段 | 缓解措施 |
| ------------------------------------------ | ---- | ------------------------------------------------------------ |
| ISSUE-002 topic 策略未仲裁导致 Outbox 阻塞 | P4 | 优先推动 coord 仲裁;开发期间用 stub topic仲裁后切换 |
| ISSUE-004 RPC 数量未仲裁导致 proto 阻塞 | P4 | 优先推动 coord 仲裁;按建议方案 21 RPC 实现,仲裁后调整 |
| Neo4j 与 MySQL 双向一致性 | P4 | Outbox 事件驱动 + 幂等去重 + consumer lag 监控 |
| ES 索引重建期间检索不可用 | P5 | alias 切换模式(双索引蓝绿) |
| AI 批量出题 CreateQuestions 高并发 | P5 | batch_size ≤ 100 + 异步队列 + 限流 |
| schema 迁移期间历史数据 created_by 缺失 | P4 | 按 ISSUE-007 方案NULL 起步或 'system' 默认值回填 |
---
## §6 与 workline.md §1 对齐
- 批次 3P4ai09 content 11 天workline.md §1 排期 `after b2b, 11d`
- 本排期 P4 实际 21 天(含测试 + README 修正),与 workline.md 11 天存在差距
- **差距原因**workline.md §1 为 coord 初始规划(标注"ai09 接管后必须自行细化"),本文件为 ai09 细化后的实际排期
- **同步动作**:提请 coord 在 workline.md §1 更新 content 工期为 21 天(或协调压缩测试任务)

View File

@@ -1,45 +1,370 @@
# core-edu 工作排期
> 负责人ai08
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/core-edu_contract.md](../contracts/core-edu_contract.md)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/core-edu_contract.md](../contracts/core-edu_contract.md)、[services/core-edu/docs/02-architecture-design.md](../../../services/core-edu/docs/02-architecture-design.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 批次归属:批次 2P3 核心教学),关键路径
---
## §1 总览
core-edu 是教学核心服务,提供 ClassService、ExamService、HomeworkService、GradeService、AttendanceService并基于 Outbox 模式发布领域事件。全阶段目标P2 服务骨架+Outbox → P3 五大 Service 实现 → P4-P6 持续优化
core-edu 是教学核心服务,承载 D2 教学组织classes+ D3 教学核心exams / homework / grades / attendance / schedule两个限界上下文
**全阶段目标**
| 阶段 | 目标 | 就绪信号 |
| ---- | ---- | -------- |
| P2已部分完成 | 服务骨架 + Outbox 模式 + REST CRUD + 三支柱可观测 | HTTP 3004 可访问 + /healthz + /readyzDB 探针) |
| P3核心 | gRPC 50053 启用 + 5 Service 全量 RPC + 状态机 + Outbox 事件全量 + 排课考勤 + 成绩计算配置化 + Temporal 试点 | gRPC 50053 + 27 RPC + HealthService SERVING |
| P4 | 持续优化 + 消费 data-ana mastery 事件 + content gRPC 调用(知识点关联) | — |
| P5 | 配合 msg 服务事件消费联调 + AI 辅助批改预留 | — |
| P6 | /readyz 硬化 + Outbox relay 迁独立 Go 服务评估 + 多租户行级隔离 | — |
**当前状态2026-07-10**
- ✅ P2 骨架已就绪exams/homework/grades 三域 REST CRUD + Outbox + 三支柱)
- ⏳ P3 待启动(依赖 ISSUE-001 ~ ISSUE-006 仲裁结果)
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai08 core-edu 全阶段排期
title ai08 core-edu 全阶段排期P2-P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a8a, 2026-07-10, Xd
section P2 骨架(已就绪)
P2 服务骨架+Outbox+REST CRUD :done, p2, 2026-07-01, 7d
section P3 核心教学批次2 关键路径)
P3.0 等待 coord 仲裁 ISSUE-001~006 :crit, p3a, 2026-07-10, 2d
P3.1 黄金模板对齐Drizzle getDb+Zod+kafka logger+/readyz探针 :crit, p3b, after p3a, 2d
P3.2 TOPIC_MAP 重命名 edu.teaching.* + payload schema_version/event_id :crit, p3c, after p3b, 1d
P3.3 考试状态机+作业状态机+成绩幂等 :crit, p3d, after p3c, 3d
P3.4 gRPC server 50053 启用+5 Service 27 RPC :crit, p3e, after p3d, 3d
P3.5 排课考勤数据模型+AttendanceService :p3f, after p3e, 2d
P3.6 成绩计算配置化grade_formulas+GradeCalculator :p3g, after p3e, 2d
P3.7 作业提交 Redis 分布式锁 :p3h, after p3e, 1d
P3.8 DataScope 下推Repository WHERE 注入) :p3i, after p3e, 1d
P3.9 消费 IAM 事件user.created/updated/deleted :p3j, after p3e, 1d
P3.10 Temporal 工作流试点(考试发布编排) :p3k, after p3e, 2d
P3.11 classes 服务合并到 core-edu :p3l, after p3e, 2d
P3.12 测试覆盖率≥80%+Dockerfile多阶段核对 :p3m, after p3l, 2d
section P4 持续优化
P4.1 消费 data-ana mastery.updated 事件 :p4a, after p3m, 2d
P4.2 content gRPC 调用(知识点关联) :p4b, after p4a, 2d
P4.3 读模型双轨读策略验证MySQL+ClickHouse :p4c, after p4b, 1d
section P5 联调
P5.1 msg 事件消费联调(通知触发) :p5a, after p4c, 2d
P5.2 AI 辅助批改接口预留GetExam/ListGradesByExam :p5b, after p5a, 1d
section P6 硬化
P6.1 /readyz 硬化Kafka+Redis+Temporal 探针) :p6a, after p5b, 1d
P6.2 Outbox relay 迁独立 Go 服务评估 :p6b, after p6a, 2d
P6.3 多租户行级隔离验证 :p6c, after p6b, 1d
```
> **注意**:以上为 coord 初始规划ai08 接管后必须自行细化为完整 P2-P6 排期。
**关键路径**P3.0(仲裁)→ P3.1(模板对齐)→ P3.2TOPIC_MAP→ P3.3(状态机)→ P3.4gRPC→ P3.5/P3.6/P3.7/P3.8/P3.9/P3.10/P3.11(并行)→ P3.12(测试)
**预计工期**
- P313 天(含 2 天等待仲裁)
- P45 天
- P53 天
- P64 天
- 合计25 天P3 后)
---
## §3 详细任务
### 全阶段任务
### P3.0 等待 coord 仲裁 ISSUE-001 ~ ISSUE-006
- **负责人**ai08等待 coord
- **依赖**:无
- **交付物**coord 出具仲裁结论(见 [coord.md](../coord.md) 后续章节)
- **验收标准**6 项 ISSUE 全部裁决,状态更新为"已裁决"
- **阻塞说明**
- ISSUE-001proto 实际状态):阻塞 P3.4 gRPC 实现
- ISSUE-002events.proto 未同步):阻塞 P3.2 TOPIC_MAP 重命名
- ISSUE-003状态命名不一致阻塞 P3.3 状态机实现
- ISSUE-004class.transferred topic阻塞 P3.11 classes 合并
- ISSUE-005RPC 数量口径):阻塞就绪信号声明
- ISSUE-0067 项设计决策):阻塞 P3 全部实施
### P3.1 黄金模板对齐P0
- **负责人**ai08
- **交付物**:⚠️ 由 ai08 自行补充
- **依赖**:见 [contracts/core-edu_contract.md](../contracts/core-edu_contract.md)
- **验收标准**:⚠️ 由 ai08 自行补充
- **依赖**:无(可与其他 P3 任务并行)
- **交付物**
1. `src/config/database.ts` 改为 `getDb()` 函数式(对齐 classes 黄金模板)
2. `src/config/kafka.ts` L20/L22 `console.log`/`console.warn``logger.info`/`logger.warn`
3. 全部 Controller 接入 Zod ValidationPipeexams/homework/grades schema 已存在,需接入到 Controller
4. `src/shared/health/health.controller.ts` /readyz 补 Redis ping + Kafka producer 连接探针
- **验收标准**
- `pnpm run lint` + `pnpm run typecheck` 零错误
- /readyz 返回 3 项依赖状态MySQL + Redis + Kafka
- kafka.ts 无 console.* 调用
### P3.2 TOPIC_MAP 重命名 + payload 补字段P0
- **负责人**ai08
- **依赖**ISSUE-002 仲裁events.proto 同步)
- **交付物**
1. `src/shared/outbox/outbox.publisher.ts` TOPIC_MAP 改为 `edu.teaching.<aggregate>.<action>` 风格:
- `edu.exam.events``edu.teaching.exam.created` / `.updated` / `.deleted` / `.published` / `.submitted`
- `edu.homework.events``edu.teaching.homework.assigned` / `.submitted` / `.graded`
- `edu.grade.events``edu.teaching.grade.recorded` / `.updated`
- `edu.class.events` → 按 ISSUE-004 仲裁结果
- 新增 `edu.teaching.attendance.recorded`
2. outbox payload 补 `schema_version`(默认 `"v1"`+ `event_id`UUID+ `occurred_at`(业务时间戳)+ `metadata: { traceId, userId }`
3. `src/shared/outbox/outbox.schema.ts``event_id``occurred_at``next_retry_at` 字段 + `uniq_event_id` 唯一索引
- **验收标准**
- TOPIC_MAP 命名符合 coord §3.1 仲裁
- outbox 表新增字段已迁移
- payload JSON 含 schema_version + event_id + occurred_at + metadata
### P3.3 考试/作业状态机 + 成绩幂等P1
- **负责人**ai08
- **依赖**ISSUE-003 仲裁(状态命名统一)
- **交付物**
1. `src/exams/domain/exam-state-machine.ts`纯函数canTransition / transition
2. `src/homework/domain/homework-state-machine.ts`(纯函数)
3. `src/exams/exams.service.ts` 接入状态机PublishExam / StartExam / SubmitExam / GradeExam / ArchiveExam
4. `src/homework/homework.service.ts` 接入状态机SubmitHomework / GradeHomework
5. `src/grades/grades.schema.ts``idempotency_key` 字段 + `uniq_student_exam` / `uniq_student_hw` / `uniq_idempotency` 唯一索引
6. `src/grades/grades.service.ts` 实现幂等录入(先 SELECT 检查,再 INSERT
- **验收标准**
- 状态机非法转换抛 `CORE_EDU_EXAM_INVALID_STATUS_TRANSITION`409
- 同一学生同一考试/作业重复录入返回已存在成绩(幂等)
- 状态命名按 ISSUE-003 仲裁结果统一
### P3.4 gRPC server 启用 + 5 Service 27 RPCP1
- **负责人**ai08
- **依赖**ISSUE-001 仲裁proto 补全、ISSUE-005 仲裁RPC 统计口径)
- **交付物**
1. `src/main.ts` 启用 gRPC server端口 50053`@grpc/grpc-js` + `@bufbuild/protobuf`
2. `src/exams/exams.grpc-controller.ts`ExamService 8 RPCCreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExam / PublishExam / SubmitExam / GradeExam
3. `src/homework/homework.grpc-controller.ts`HomeworkService 5 RPCAssignHomework / GetHomework / ListHomeworkByClass / SubmitHomework / GradeHomework
4. `src/grades/grades.grpc-controller.ts`GradeService 6 RPCRecordGrade / GetGrade / ListGradesByStudent / ListGradesByExam / ListGradesByHomework / UpdateGrade
5. `src/attendance/attendance.grpc-controller.ts`AttendanceService 4 RPCRecordAttendance / GetAttendance / ListAttendanceByStudent / ListAttendanceByClass
6. `src/classes/classes.grpc-controller.ts`ClassService 4 RPCGetClass / GetClassesByTeacher / BatchGetClasses / ListStudentsByClass
7. `src/shared/health/health.grpc-controller.ts`HealthService.Check 返回 SERVING
- **验收标准**
- gRPC 50053 可连接HealthService.Check 返回 SERVING
- 27 RPC 全部可调用(含 P3 新增 5 RPC
- gRPC 调用走 AuthMiddleware + PermissionGuardgRPC 上下文适配)
### P3.5 排课考勤数据模型 + AttendanceServiceP2
- **负责人**ai08
- **依赖**P3.4 gRPC 启用
- **交付物**
1. `src/schedule/schedule.schema.ts`core_edu_courses / core_edu_lessons / core_edu_schedules 三表)
2. `src/attendance/attendance.schema.ts`core_edu_attendance 表)
3. `src/schedule/schedule.service.ts`(含 ScheduleConflictChecker教师/班级时间冲突检测)
4. `src/attendance/attendance.service.ts`(含唯一索引幂等)
5. `src/schedule/schedule.controller.ts` + `src/attendance/attendance.controller.ts`REST + gRPC 双入口)
- **验收标准**
- 排课冲突检测正确(教师时间重叠抛 CORE_EDU_SCHEDULE_CONFLICT 409
- 考勤录入幂等(同一学生同一课时仅 1 条)
### P3.6 成绩计算配置化P2
- **负责人**ai08
- **依赖**P3.4 gRPC 启用、ISSUE-006 决策 #3scope 优先级)
- **交付物**
1. `src/grades/grade-formulas.schema.ts`core_edu_grade_formulas 表scope / scope_id / formula_type / weights / custom_expression / effective_from / effective_to
2. `src/grades/domain/grade-calculator.ts`(纯函数:按 scope 优先级查询公式 → 加权/平均/自定义求值)
3. `src/grades/grades.service.ts` 接入 GradeCalculator
4. custom 公式安全求值(白名单正则 + Function 沙箱)
- **验收标准**
- 三种公式类型weighted / average / custom均正确计算
- scope 优先级按 ISSUE-006 决策 #3 仲裁结果
- custom 公式禁止函数调用 / 对象访问 / eval白名单校验
### P3.7 作业提交 Redis 分布式锁P2
- **负责人**ai08
- **依赖**P3.4 gRPC 启用、Redis 部署
- **交付物**
1. `src/shared/redis/redis.client.ts`Redis 单例)
2. `src/homework/homework.service.ts` submitHomework 接入 Redis 分布式锁
- 锁 key`lock:hw:submit:{homeworkId}:{studentId}`
- 锁过期30s
- 重试5 次 × 100ms 间隔
- 超时429 CORE_EDU_HOMEWORK_SUBMIT_LOCK_TIMEOUT
3. 同步对 exam submit 也接入锁(锁 key`lock:exam:submit:{examId}:{studentId}`
- **验收标准**
- 50 并发提交同一作业,仅 1 条提交入库
- 锁超时返回 429
- DB 唯一索引兜底(锁失效时仍幂等)
### P3.8 DataScope 下推P1
- **负责人**ai08
- **依赖**:无
- **交付物**
1. `src/shared/datascope/datascope-injector.ts`(根据 x-user-data-scope 头注入 WHERE 条件)
2. `src/exams/exams.repository.ts` 接入 DataScopeInjector按 class_id / school_id 过滤)
3. `src/grades/grades.repository.ts` 接入 DataScopeInjector按 student_id / class_id / school_id 过滤)
4. `src/middleware/permission.guard.ts` 增强:解析 dataScope 注入到 request
- **验收标准**
- 教师仅能查自己班级的考试/成绩
- 学生仅能查自己的成绩
- 校管理员可查全校
### P3.9 消费 IAM 事件P1
- **负责人**ai08
- **依赖**IAM gRPC 50052 就绪ai06
- **交付物**
1. `src/shared/kafka/kafka.consumer.ts`Kafka consumer 单例)
2. `src/iam-events/iam-user.consumer.ts`(订阅 edu.identity.user.created / .updated / .deleted
3. `src/iam-events/teacher-associations.schema.ts`core_edu_teacher_associations 表 + uniq_teacher_class_subject 唯一索引)
4. 消费幂等(基于 user_id + class_id + subject_id 唯一索引)
- **验收标准**
- IAM 发布 user.created 事件后core-edu 写入 teacher_associations
- 重复消费同一事件不重复写入(幂等)
- user.deleted 事件软删除教师关联(保留历史成绩归属)
### P3.10 Temporal 工作流试点P3
- **负责人**ai08
- **依赖**Temporal server 部署infra、P3.4 gRPC 启用
- **交付物**
1. `src/workflows/exam-publish.workflow.ts`(考试发布编排工作流)
2. `src/workflows/exam-publish.activities.ts`5 个 Activity创建提交骨架 / 通知 msg / 等待作答窗口 / 自动提交未答 / 通知教师)
3. `src/exams/exams.service.ts` publishExam 调用 `workflowClient.start(examPublishWorkflow, ...)`
4. `src/shared/temporal/temporal.client.ts`Temporal client 单例)
- **验收标准**
- 考试发布后 Temporal UI 可见工作流实例
- 工作流完成 5 个 Activity
- 失败可重试
### P3.11 classes 服务合并到 core-eduP3
- **负责人**ai08
- **依赖**ISSUE-006 决策 #1合并时机仲裁、P3.4 gRPC 启用
- **交付物**
1. `services/classes/src/` 代码迁入 `services/core-edu/src/classes/`
2. 删除独立 `services/classes/` 目录
3. `src/classes/classes.module.ts` 接入 core-edu AppModule
4. `src/classes/classes.grpc-controller.ts`ClassService 4 RPC
5. classes 错误码 `CLASSES_*` 保留coord §5.5 仲裁,黄金模板历史遗留)
- **验收标准**
- classes 服务代码完全迁入 core-edu
- ClassService 4 RPC 可调用
- 原 classes 服务端口 3001 不再存在
- `pnpm run arch:scan` 确认 arch.db 已更新
### P3.12 测试覆盖率 + Dockerfile 核对P3
- **负责人**ai08
- **依赖**P3.1 ~ P3.11 全部完成
- **交付物**
1. 单元测试:状态机 / GradeCalculator / ScheduleConflictChecker / DataScopeInjector纯函数优先
2. 集成测试ExamsService / HomeworkService / GradesService / AttendanceService / ScheduleService
3. Outbox relay 测试mock Kafka
4. `Dockerfile` 多阶段构建核对builder → runner最终镜像无 devDependencies
- **验收标准**
- `pnpm run test` 通过
- 覆盖率 ≥ 80%
- Dockerfile 多阶段构建,最终镜像 ≤ 200MB
### P4.1 消费 data-ana mastery.updated 事件
- **负责人**ai08
- **依赖**data-ana gRPC 50055 就绪ai11
- **交付物**`src/data-ana-events/mastery.consumer.ts`(订阅 edu.insight.mastery.updated基于 mastery_score_id 幂等)
- **验收标准**:重复消费不重复写入
### P4.2 content gRPC 调用(知识点关联)
- **负责人**ai08
- **依赖**content gRPC 50054 就绪ai09
- **交付物**`src/content/content.client.ts`(调用 ContentService.GetKnowledgePoints排课关联知识点
- **验收标准**lessons.knowledge_point_ids 引用的知识点在 content 服务可查
### P4.3 读模型双轨读策略验证
- **负责人**ai08
- **依赖**data-ana CDC 链路就绪
- **交付物**:验证刚提交成绩查 MySQL强一致聚合统计查 ClickHouse最终一致 < 5s
- **验收标准**:双轨读策略符合 02 文档 §3.3
### P5.1 msg 事件消费联调
- **负责人**ai08
- **依赖**msg gRPC 50056 就绪ai10
- **交付物**:验证 msg 消费 core-edu 事件触发通知edu.teaching.exam.created / homework.assigned / grade.recorded
- **验收标准**:端到端事件链路通
### P5.2 AI 辅助批改接口预留
- **负责人**ai08
- **依赖**:无
- **交付物**:确认 ExamService.GetExam / GradeService.ListGradesByExam 接口对 ai 服务可用
- **验收标准**ai 服务可调用上述 RPC
### P6.1 /readyz 硬化
- **负责人**ai08
- **依赖**:无
- **交付物**/readyz 补 Temporal client 连接探针
- **验收标准**/readyz 返回 4 项依赖状态MySQL + Redis + Kafka + Temporal
### P6.2 Outbox relay 迁独立 Go 服务评估
- **负责人**ai08评估不实施
- **依赖**:无
- **交付物**:评估报告(是否迁出进程内 relay
- **验收标准**:产出评估结论
### P6.3 多租户行级隔离验证
- **负责人**ai08
- **依赖**:无
- **交付物**:验证 school_id 行级隔离(不同学校数据互不可见)
- **验收标准**:跨学校查询返回空
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai08 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai08 自行补充
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供方 | 就绪信号 | 状态 | 影响 |
| ------ | ------ | -------- | ---- | ---- |
| coord 仲裁 ISSUE-001 ~ ISSUE-006 | coord | coord.md 仲裁章节 | ⏳ 待仲裁 | 阻塞 P3 全部 |
| iam gRPC 50052 | ai06 | HealthService.Check = SERVING | ⏳ | 阻塞 P3.9 消费 IAM 事件 |
| events.proto 同步 | coord | events.proto 含 AttendanceEvent + schema_version | ⏳ | 阻塞 P3.2 TOPIC_MAP |
| core_edu.proto 补全 | coord 或 ai08 | 5 service 27 RPC 定义 | ⏳ | 阻塞 P3.4 gRPC 实现 |
| Redis 部署 | infra | redis:6379 可连接 | ⏳ | 阻塞 P3.7 分布式锁 + P3.1 /readyz |
| Temporal server 部署 | infra | temporal:7233 可连接 | ⏳ | 阻塞 P3.10 工作流试点 |
| buf.gen.yaml gRPC 插件 | coord | buf generate 产出 TS gRPC 代码 | ⏳ | 阻塞 P3.4 gRPC 实现 |
| data-ana gRPC 50055P4 | ai11 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.1 mastery 消费 |
| content gRPC 50054P4 | ai09 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.2 知识点关联 |
| msg gRPC 50056P5 | ai10 | HealthService.Check = SERVING | ⏳ | 阻塞 P5.1 事件联调 |
### 4.2 我的就绪标志(供下游消费)
| 阶段 | 就绪信号 | 消费方 | 状态 |
| ---- | -------- | ------ | ---- |
| P2已就绪 | HTTP 3004 可访问 + /healthz + /readyzDB 探针) | teacher-bffREST 调用) | ✅ |
| P3核心 | gRPC 50053 + 27 RPC + HealthService SERVING | teacher-bff / student-bff / parent-bff / ai | ⏳ |
| P3 子信号 1 | ClassService 4 RPC 可调用 | teacher-bff班级列表 | ⏳ |
| P3 子信号 2 | ExamService 8 RPC 可调用 | teacher-bff / student-bff / ai | ⏳ |
| P3 子信号 3 | HomeworkService 5 RPC 可调用 | teacher-bff / student-bff | ⏳ |
| P3 子信号 4 | GradeService 6 RPC 可调用 | teacher-bff / student-bff / parent-bff | ⏳ |
| P3 子信号 5 | AttendanceService 4 RPC 可调用 | parent-bff | ⏳ |
| P3 子信号 6 | edu.teaching.* topic 可发布(含 attendance.recorded | msg / data-ana / push-gateway | ⏳ |
### 4.3 Mock 策略(全并行开发期间)
详见 [contracts/core-edu_contract.md §4](../contracts/core-edu_contract.md)。

View File

@@ -1,18 +1,34 @@
# data-ana 工作排期
> 负责人ai11
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/data-ana_contract.md](../contracts/data-ana_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/data-ana_contract.md](../contracts/data-ana_contract.md)、[objections/data-ana_issue.md](../objections/data-ana_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 基线日期:批次 0 已完成2026-07-09批次 1P22026-07-10 启动
---
## §1 总览
data-ana 是数据分析服务,提供 AnalyticsService12 个 RPC基于 ClickHouse 列存查询与 CDC 消费实现实时分析。全阶段目标P2 ClickHouse 接入+CDC 消费 → P3 AnalyticsService 12 RPC → P4-P6 持续优化
data-ana 是 D6 智能洞察领域的纯读模型服务Python/FastAPI基于 ClickHouse ReplacingMergeTree 宽表 + Debezium CDC 消费实现实时学情分析
**核心交付物**
- gRPC server :50055 + AnalyticsService 12 RPC含 1 个 Server Streaming
- HTTP :3006 14 端点3 基础 + 11 业务,保留作 Gateway 直连降级)
- ClickHouse 5 宽表student_dashboard_view / student_errors / mastery_snapshot / attendance_logs / ai_usage_log
- CDC 消费者core-edu MySQL binlog → Kafka → ClickHouse 宽表投影)
- 掌握度计算(加权滑动平均 + 遗忘曲线)+ 预警评估
- 派生数据事件发布edu.insight.mastery.updated / edu.insight.warning.triggered豁免 Outbox
**阶段里程碑**
- P2 预备ClickHouse 接入 + CDC 骨架 + ActionState 信封重构 + mock 数据集
- P3 预备CDC 通道接入 + 掌握度算法 v1 + Repository 封装
- P4 主战场gRPC 50055 启用 + 12 RPC + 4 端 Dashboard + Warning + DataScope + 事件发布
- P5 扩展SubscribeMasteryUpdate stream + AI 用量消费 + 手动 commit
- P6 硬化CDC 水平扩展 + 容量规划 + 数据治理 + 监控告警
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
@@ -20,26 +36,317 @@ gantt
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a11a, 2026-07-10, Xd
section P2 预备与批次1并行
2.1 ClickHouse DDL 5宽表建表 :a11a, 2026-07-10, 2d
2.2 CDC消费骨架(aiokafka) :a11b, after a11a, 2d
2.3 mock数据集(30学生×5考试×10作业) :a11c, after a11a, 1d
2.4 ActionState信封重构(P0整改) :crit, a11d, 2026-07-10, 2d
2.5 config.py修正(env/Redis/gRPC) :a11e, after a11d, 1d
section P3 预备与批次2并行
3.1 gRPC server骨架(3 RPC,不启用) :a11f, after a11b, 2d
3.2 core-edu CDC通道接入(grades/exams/homework) :a11g, after a11b, 3d
3.3 ExamCache内存LRU :a11h, after a11g, 1d
3.4 掌握度算法v1(weighted_moving_avg) :a11i, after a11g, 2d
3.5 ClickHouseRepository(FINAL/argMax) :a11j, after a11i, 2d
section P4 主战场批次3, 11d
4.1 gRPC 50055正式启用 :crit, a11k, after a11j, 1d
4.2 analytics.proto扩展12 RPC :crit, a11l, after a11k, 2d
4.3 4端Dashboard RPC实现 :a11m, after a11l, 3d
4.4 WarningService+TriggerWarning :a11n, after a11l, 2d
4.5 GetMasteryDistribution+GetStudentMastery :a11o, after a11m, 1d
4.6 iam.GetEffectiveDataScope集成(降级兜底) :crit, a11p, after a11k, 2d
4.7 DataScope 6级WHERE注入 :a11q, after a11p, 1d
4.8 attendance+content CDC消费 :a11r, after a11g, 2d
4.9 MasteryEvent+WarningTriggered发布 :a11s, after a11n, 1d
4.10 HTTP 14端点+readyz硬化 :a11t, after a11m, 2d
section P5 扩展批次4并行
5.1 SubscribeMasteryUpdate stream RPC :a11u, after a11t, 3d
5.2 AIUsageEvent消费→ai_usage_log :a11v, after a11u, 2d
5.3 手动commit替换auto_commit :a11w, after a11v, 1d
5.4 Admin Dashboard AI用量区块 :a11x, after a11v, 2d
section P6 硬化批次5并行
6.1 CDC多实例水平扩展 :a11y, after a11x, 3d
6.2 ExamCache Redis化 :a11z, after a11y, 2d
6.3 容量规划+TTL归档策略 :a11aa, after a11z, 2d
6.4 监控告警(consumer lag HPA) :a11ab, after a11aa, 2d
6.5 readyz深度硬化 :a11ac, after a11ab, 1d
```
> **注意**:以上为 coord 初始规划ai11 接管后必须自行细化为完整 P2-P6 排期。
> **关键路径**critActionState 信封重构 → gRPC 50055 启用 → analytics.proto 扩展 → iam GetEffectiveDataScope 集成
> **总工期**:约 51 天2026-07-10 ~ 2026-08-30其中 P4 主战场 11 天为关键交付期
---
## §3 详细任务
### 全阶段任务
### 3.1 P2 预备期2026-07-10 ~ 2026-07-188d
#### 任务 2.1ClickHouse DDL 5 宽表建表
- **负责人**ai11
- **交付物**:⚠️ 由 ai11 自行补充
- **依赖**:见 [contracts/data-ana_contract.md](../contracts/data-ana_contract.md)
- **验收标准**⚠️ 由 ai11 自行补充
- **依赖**ClickHouse 实例就绪,由 infra 提供)
- **交付物**`infra/clickhouse/ddl/data_ana.sql`5 宽表 DDLstudent_dashboard_view / student_errors / mastery_snapshot / attendance_logs / ai_usage_log
- **验收标准**5 表在 ClickHouse 中创建成功ReplacingMergeTree 引擎 + ORDER BY + PARTITION BY 符合 02 §3 DDL 设计
#### 任务 2.2CDC 消费骨架
- **负责人**ai11
- **依赖**Kafka 就绪
- **交付物**`src/data_ana/cdc_consumer.py` 重构aiokafka AIOKafkaConsumer + EventHandler 路由框架)
- **验收标准**:能消费 mock CDC 事件并打印路由日志consumer group = `data-ana-cdc`
#### 任务 2.3mock 数据集
- **负责人**ai11
- **依赖**:任务 2.1
- **交付物**`scripts/seed_clickhouse.py`(批量导入 30 学生 × 5 考试 × 10 作业 × 30 天出勤模拟数据)
- **验收标准**ClickHouse 5 表有数据,可查询返回非空结果
#### 任务 2.4ActionState 信封重构P0 整改coord-cross-review §5.3
- **负责人**ai11
- **依赖**:无
- **交付物**`src/data_ana/shared/action_state.py`ActionState[T] 泛型 + ActionStateError + ok()/fail() 类方法)
- **验收标准**main.py 所有端点返回 `ActionState[T]`degraded 标记在顶层 `details.degraded`(非 error.detailsruff 零错误
#### 任务 2.5config.py 修正
- **负责人**ai11
- **依赖**:无
- **交付物**`src/data_ana/config.py` 修正env_prefix 补 Redis / gRPC / ClickHouse 配置项pydantic-settings 校验)
- **验收标准**:配置项覆盖 02 §13 配置清单,环境变量缺失时 pydantic-settings 报错
---
### 3.2 P3 预备期2026-07-18 ~ 2026-07-268d
#### 任务 3.1gRPC server 骨架3 RPC不正式启用
- **负责人**ai11
- **依赖**analytics.proto 当前 3 RPC无需 coord 补全)
- **交付物**`src/data_ana/grpc_server.py`grpc.aio Server 骨架 + 3 RPC 实现,绑定 :50055 但不启动对外)
- **验收标准**:本地可启动 gRPC server3 RPC 可调用返回 mock 数据
#### 任务 3.2core-edu CDC 通道接入
- **负责人**ai11
- **依赖**core-edu MySQL 就绪 + Debezium CDC 配置core-edu 就绪前用 mock binlog 事件)
- **交付物**`src/data_ana/cdc_consumer.py` 完善EventHandler 处理 grades/exams/homework/classes 表 CDC 事件)
- **验收标准**:消费 CDC 事件 → 解析 Debezium JSON → 查 ExamCache 填 class_id → upsert ClickHouse 宽表
#### 任务 3.3ExamCache 内存 LRU
- **负责人**ai11
- **依赖**:任务 3.2
- **交付物**`src/data_ana/exam_cache.py`(内存 LRU dictmax 10000 条exam_id → {class_id, subject_id}
- **验收标准**CDC exams 事件触发 ExamCache 更新grades 事件查 ExamCache 获取 class_id
#### 任务 3.4:掌握度算法 v1
- **负责人**ai11
- **依赖**:任务 3.2
- **交付物**`src/data_ana/mastery_service.py`(加权滑动平均算法,权重 w_i = 0.6^i归一化
- **验收标准**:输入学生近期 N 次成绩 → 输出 mastery_level (0.0-1.0) → 写 mastery_snapshot 表
#### 任务 3.5ClickHouseRepository 封装
- **负责人**ai11
- **依赖**:任务 2.1
- **交付物**`src/data_ana/clickhouse_client.py` 重构(查询封装 + FINAL/argMax 去重 + DataScope WHERE 注入接口)
- **验收标准**:查询 student_dashboard_view 返回去重后最新版本数据
---
### 3.3 P4 主战场期2026-07-26 ~ 2026-08-0611d批次 3
#### 任务 4.1gRPC 50055 正式启用
- **负责人**ai11
- **依赖**:任务 3.1
- **交付物**main.py lifespan 启动 gRPC server :50055HealthService.Check 返回 SERVING
- **验收标准**gRPC server 对外可访问HealthService.Check = SERVING
#### 任务 4.2analytics.proto 扩展 12 RPC
- **负责人**ai11本分支内补全 proto提请 coord 合并)
- **依赖**ISSUE-003 解决coord 确认或 ai11 自行补全)
- **交付物**`packages/shared-proto/proto/analytics.proto` 扩展至 12 RPC3 现有 + 9 新增 message 定义)
- **验收标准**`buf lint` 零错误,`buf generate` 生成 Python stub 成功12 RPC 全部可调用
#### 任务 4.34 端 Dashboard RPC 实现
- **负责人**ai11
- **依赖**:任务 4.2
- **交付物**GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard 4 RPC 实现
- **验收标准**4 RPC 返回 ActionState[DashboardData]DataScope 过滤生效,降级时返回骨架数据 + degraded: true
#### 任务 4.4WarningService + TriggerWarning
- **负责人**ai11
- **依赖**:任务 3.4(掌握度算法)
- **交付物**`src/data_ana/warning_service.py`(预警阈值评估 + TriggerWarning RPC + GetWarnings RPC
- **验收标准**:掌握度 < 0.4 触发 LOW_MASTERY 预警,成绩环比下降 20% 触发 SCORE_DROP缺勤 ≥ 3 次/周触发 ABSENT_FREQUENT
#### 任务 4.5GetMasteryDistribution + GetStudentMastery
- **负责人**ai11
- **依赖**:任务 3.4 + 任务 4.2
- **交付物**2 RPC 实现(班级掌握度分布 + 学生知识点掌握度明细)
- **验收标准**:返回 mastered/progressing/weak 三档分布数据
#### 任务 4.6iam.GetEffectiveDataScope 集成(降级兜底)
- **负责人**ai11
- **依赖**ISSUE-001 解决iam.proto 补全 GetEffectiveDataScope。若 P4 时 iam 未就绪,使用降级兜底
- **交付物**`src/data_ana/iam_client.py`gRPC 调 iam.GetEffectiveDataScope + Redis 缓存 5min + 降级兜底)
- **验收标准**iam 可用时调 gRPC 获取 DataScopeiam 不可用时按 role 映射默认 DataScope + degraded: true
#### 任务 4.7DataScope 6 级 WHERE 注入
- **负责人**ai11
- **依赖**:任务 4.6 + 任务 3.5
- **交付物**ClickHouseRepository 查询方法注入 DataScope WHERE 子句SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL
- **验收标准**:教师只能查自己班级数据,学生只能查自己数据,管理员可查全校数据
#### 任务 4.8attendance + content CDC 消费
- **负责人**ai11
- **依赖**:任务 3.2CDC 框架)
- **交付物**EventHandler 扩展 attendance_logs 表 CDC + content_knowledge_points 表 CDC
- **验收标准**:考勤事件落 attendance_logs 表,知识点事件更新 mastery_snapshot 元数据
#### 任务 4.9MasteryEvent + WarningTriggered 事件发布
- **负责人**ai11
- **依赖**:任务 3.4 + 任务 4.4
- **交付物**`src/data_ana/kafka_producer.py`aiokafka AIOKafkaProducer + idempotent + transactional_id
- **验收标准**:掌握度计算完成发布 `edu.insight.mastery.updated`,预警触发发布 `edu.insight.warning.triggered`,豁免 Outbox
#### 任务 4.10HTTP 14 端点 + readyz 硬化
- **负责人**ai11
- **依赖**:任务 4.3 + 任务 4.4 + 任务 4.5
- **交付物**main.py 14 个 HTTP 端点全部实现3 基础 + 11 业务)+ /readyz 检查 4 依赖clickhouse/cdc_consumer/redis/iam_grpc
- **验收标准**14 端点返回 ActionState[T]/readyz 依赖检查正确反映服务状态
---
### 3.4 P5 扩展期2026-08-06 ~ 2026-08-1913d批次 4 并行)
#### 任务 5.1SubscribeMasteryUpdate Server Streaming RPC
- **负责人**ai11
- **依赖**:任务 4.2 + 任务 4.9
- **交付物**SubscribeMasteryUpdate RPC 实现server-streaming客户端订阅 student_id/class_id掌握度更新时推送
- **验收标准**:客户端订阅后,掌握度计算完成时收到 MasteryUpdateEvent 流
#### 任务 5.2AIUsageEvent 消费 → ai_usage_log
- **负责人**ai11
- **依赖**ISSUE-002 解决events.proto 补 AIUsageEvent+ ai 服务发布 `edu.insight.ai.usage` topic
- **交付物**EventHandler 扩展 AIUsageEvent 消费 → 落 ai_usage_log 表
- **验收标准**ai 服务发布用量事件后ai_usage_log 表有数据Admin Dashboard AI 用量区块可展示
#### 任务 5.3:手动 commit 替换 auto_commit
- **负责人**ai11
- **依赖**:任务 3.2
- **交付物**cdc_consumer.py 改为 `enable_auto_commit=False` + 手动 commitat-least-once
- **验收标准**ClickHouse 写入成功后才 commit offset重启后无重复消费依赖 ReplacingMergeTree 去重)
#### 任务 5.4Admin Dashboard AI 用量区块
- **负责人**ai11
- **依赖**:任务 5.2
- **交付物**GetAdminDashboard RPC 补全 AI 用量统计区块(按 provider/model/时间窗聚合)
- **验收标准**Admin Dashboard 返回 AI 用量数据,无数据时显示"暂无数据"
---
### 3.5 P6 硬化期2026-08-19 ~ 2026-08-3011d批次 5 并行)
#### 任务 6.1CDC 多实例水平扩展
- **负责人**ai11
- **依赖**:任务 5.3(手动 commit
- **交付物**CdcConsumer 支持多实例分摊 partitionconsumer group 不变)
- **验收标准**2+ 实例消费同一 topic 无重复无遗漏
#### 任务 6.2ExamCache Redis 化
- **负责人**ai11
- **依赖**:任务 6.1
- **交付物**exam_cache.py 改为 Redis 实现key: `data_ana:exam:{exam_id}` TTL 30 天)
- **验收标准**:多实例共享 ExamCache重启后缓存不丢失
#### 任务 6.3:容量规划 + TTL 归档策略
- **负责人**ai11
- **依赖**:无
- **交付物**ClickHouse TTL 策略student_dashboard_view 保留 2 年ai_usage_log 保留 1 年)+ 冷热数据分离方案
- **验收标准**TTL 配置生效,过期数据自动清理
#### 任务 6.4:监控告警完善
- **负责人**ai11
- **依赖**:任务 6.1
- **交付物**Prometheus 指标补全consumer lag histogram + 慢查询 counter + ClickHouse 连接池 gauge+ Grafana dashboard
- **验收标准**consumer lag 超阈值触发 HPA慢查询超阈值告警
#### 任务 6.5readyz 深度硬化
- **负责人**ai11
- **依赖**:任务 4.10
- **交付物**/readyz 检查项完善ClickHouse 查询超时 1s + Redis ping + iam gRPC 超时 2s + CDC consumer lag < 1000
- **验收标准**:任一依赖不健康时 /readyz 返回 503K8s 摘流量
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai11 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai11 自行补充
### 4.1 我依赖的上游就绪标志
- [ ] **core-edu gRPC 50053 启用**ai08批次 2—— CDC 数据源grades/exams/homework/attendance 表 binlog
- [ ] **core-edu MySQL Debezium CDC 配置**ai08 + SRE—— CDC 通道前提
- [ ] **content gRPC 50054 启用**ai09批次 3—— 知识点维度 CDC
- [ ] **iam.proto 补全 GetEffectiveDataScope RPC**ai06/coordISSUE-001—— DataScope 解析
- [ ] **analytics.proto 扩展至 12 RPC**coord/ai11ISSUE-003—— gRPC stub 生成前提
- [ ] **events.proto 补全 AIUsageEvent message**coordISSUE-002—— AI 用量消费P5
- [ ] **ai 服务发布 `edu.insight.ai.usage` topic**ai12批次 4—— AI 用量统计P5
> **降级兜底**core-edu / content / iam 未就绪时,使用 ClickHouse 内置 mock 数据集 + 硬编码 DataScope 降级,标注 `details.degraded: true`
### 4.2 我的就绪信号(供下游消费)
- [ ] **P4 就绪**data-ana gRPC 50055 启用HealthService.Check = SERVING+ AnalyticsService 12 RPC 可调用 + 4 端 Dashboard 返回结构化数据
- [ ] **P4 就绪**`edu.insight.mastery.updated` topic 可发布mastery.updated / warning.triggered
- [ ] **P5 就绪**SubscribeMasteryUpdate server-streaming RPC 可订阅
- [ ] **P6 就绪**CDC 多实例水平扩展 + ExamCache Redis 化完成
### 4.3 下游消费方
| 下游 | 消费接口 | 就绪依赖阶段 |
| -------------------------- | ---------------------------------------------------------------- | ------------ |
| teacher-bffai03 | gRPC 50055 GetTeacherDashboard / GetClassPerformance 等 | P4 |
| student-bffai04 | gRPC 50055 GetStudentDashboard / GetStudentWeakness 等 | P4 |
| parent-bffai05 | gRPC 50055 GetParentDashboard | P4 |
| ai 服务ai12 | gRPC 50055 反向调用查学情GetStudentMastery / GetLearningTrend | P5 |
| core-eduai08 | Kafka `edu.insight.mastery.updated`(推荐个性化练习) | P4 |
| msgai10 | Kafka `edu.insight.warning.triggered`(推送通知) | P4 |
---
## §5 风险与缓解
| 风险 | 影响 | 缓解措施 |
| -------------------------------------------- | ---- | ------------------------------------------------------------------------------------------ |
| iam.proto 未补全 GetEffectiveDataScope | P4 | 降级兜底:按 role 映射默认 DataScope + degraded: trueISSUE-001 |
| analytics.proto 未扩展 12 RPC | P4 | ai11 本分支自行补全 proto提请 coord 合并ISSUE-003 |
| core-edu CDC 通道未就绪 | P3-P4 | mock 数据集降级 + 本地 stub CDC 事件 |
| ClickHouse ReplacingMergeTree 去重延迟 | P4 | 查询加 FINAL / argMax 强制去重02 §3.6 已设计) |
| 单实例 CDC 消费者单点故障 | P4 | P6 演进为多实例 + Redis ExamCacheP4 阶段监控 consumer lag 告警 |
| 掌握度算法精度不足 | P4 | v1 用加权滑动平均P5+ 评估引入遗忘曲线 max 叠加02 §9 已设计 MasteryMethod 枚举预留) |

View File

@@ -3,54 +3,154 @@
> 负责人ai06
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/iam_contract.md](../contracts/iam_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 裁决依据:[coord-final-decisions](../../coord-final-decisions.md) I1-I8、[president-final-rulings](../../president-final-rulings.md) §3.2/§2.15/§2.16/§5.5
---
## §1 总览
iam 是身份认证服务全阶段目标gRPC 50052 + 12 RPC + /readyz 深度 + iam_student_guardians + DataScope + 审计日志 + Outbox。
iam 是身份认证服务全阶段目标gRPC 50052 + 12 RPC + REST 双入口 + /readyz 深度 + iam_student_guardians + DataScope 6 级 + 审计日志 + Outbox + RBAC CRUD 完整化
**核心裁决约束**coord-final-decisions I1-I8
- I1P2 即启用 gRPC server 50052REST + gRPC 双入口并存,非"P2 仅 REST → P3 gRPC"
- I2直接用 shared-ts Outbox 工具包(非 iam 自建)
- I3首次实现即 DB 驱动 + Redis 缓存 PermissionGuard废弃硬编码 ROLE_PERMISSIONS map
- I4首次实现即注册 AuthMiddlewareController 通过 @Req() 注入用户上下文)
- I5P2 本地文件 RS256 密钥IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATHP6 迁 Vault
- I6P2 即补全 iam_student_guardians 表 + GetChildrenByParent RPC + GET /iam/children
- I7/iam/v1/* 前缀Controller 加 v1 前缀Gateway 透传)
- I8统一 GET /iam/permissions/effective
**工作量分级**president §3.2iam 实际工作量 26-27 天,拆分 P2.1(核心,阻塞批次 2+ P2.2扩展P3 期间持续补,不阻塞)。
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai06 iam 全阶段排期
title ai06 iam 全阶段排期P2.1/P2.2 拆分版)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2.1 核心
gRPC server 50052启用 :crit, a6a, 2026-07-10, 2d
12 RPC实现 :crit, a6b, after a6a, 4d
/readyz深度(5依赖) :a6c, after a6b, 1d
iam_student_guardians+DataScope :a6d, after a6b, 1d
section P2.1 核心阻塞批次2
1.1 proto 契约确认(依赖coord补全iam.proto) :crit, a1, 2026-07-10, 1d
1.2 gRPC server 50052 + AuthMiddleware注册 :crit, a2, after a1, 2d
1.3 8 RPC实现(GetViewports/GetEffectivePermissions/GetEffectiveAccess/Logout/GetPublicKey/BatchGetUsers/GetEffectiveDataScope/GetChildrenByParent) :crit, a3, after a2, 4d
1.4 JWT RS256本地文件+refresh轮换 :crit, a4, after a2, 2d
1.5 /iam/v1/*前缀迁移+端点统一(I7/I8) :crit, a5, after a3, 1d
1.6 iam_student_guardians表+GetChildrenByParent(I6) :crit, a6, after a3, 1d
1.7 shared-ts Outbox接入+事件发布(I2) :crit, a7, after a3, 2d
1.8 DB驱动PermissionGuard基础(I3) :crit, a8, after a3, 2d
1.9 /readyz深度(5依赖:DB/Redis/Kafka/gRPC/JWKS) :a9, after a8, 1d
1.10 02文档回写(I1-I8对齐) :crit, a10, 2026-07-10, 1d
section P2.2-P6 扩展
审计日志+Outbox :a6e, after a6d, 3d
持续补全 :a6f, after a6e, 5d
section P2.2 扩展P3期间持续补
2.1 三层角色模型(system/organization/temporary) :b1, after a8, 3d
2.2 DataScope 6级实现(待ISSUE-004裁决) :b2, after a8, 2d
2.3 视口4层+getEffectivePermissions完整 :b3, after b1, 3d
2.4 审计日志(user_audit_log表+AuditEvent发布) :b4, after b1, 3d
2.5 Redis缓存完整实现(I3完整) :b5, after b1, 2d
2.6 密码策略(强度/过期/重用限制) :b6, after b4, 2d
2.7 单元测试+集成测试(覆盖率≥80%) :b7, after b6, 3d
section P3-P6 持续优化
3.1 RBAC CRUD完整化(角色/权限/视口增删改) :c1, after b3, 3d
3.2 2FA实现(TOTP) :c2, after b6, 3d
3.3 JWT密钥迁移Vault(P6) :c3, after c2, 2d
3.4 /readyz硬化+性能优化 :c4, after c1, 2d
```
> **注意**:以上为 coord 初始规划ai06 接管后必须自行细化为完整 P2-P6 排期。
---
## §3 详细任务
### P2.1gRPC + 12 RPC + /readyz + DataScope
### P2.1gRPC + 8 RPC + RS256 + AuthMiddleware + Outbox阻塞批次 2
- **负责人**ai06
- **批次**:批次 1P2
- **预估**5-7 天
- **前置依赖**
- coord 补全 iam.proto 至 12 RPCISSUE-005阻塞项
- coord 补全 events.proto UserEvent/RoleEventISSUE-002阻塞 Outbox 事件发布)
- shared-ts Outbox 工具包已就绪(✅ 已存在)
- **交付物**
- gRPC server 50052 + 12 RPC见 iam.proto
- /readyz 5 项依赖检查
- iam_student_guardians 表 + DataScope=CHILDREN
- **依赖**iam.proto批次 0 已完成
- **验收标准**12 RPC 全部可用 + /readyz 返回 5 项状态
- **完整 P2.2-P6 任务**:⚠️ 由 ai06 自行补充
1. gRPC server 50052 启用NestJS gRPC transport
2. 8 RPC 实现GetViewports / GetEffectivePermissions / GetEffectiveAccess / Logout / GetPublicKey / BatchGetUsers / GetEffectiveDataScope / GetChildrenByParent
3. AuthMiddleware 注册app.module.ts configure 消费Controller 改用 @Req() 注入用户上下文I4
4. JWT RS256 本地文件加载IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATH+ refresh token 轮换 + 旧 token 黑名单I5
5. /iam/v1/* 前缀迁移Controller `@Controller('v1/iam')`+ 端点路径统一 I8I7
6. iam_student_guardians 表 + GET /iam/v1/children REST + GetChildrenByParent gRPCI6
7. shared-ts Outbox 接入,发布 UserEvent/RoleEventI2
8. DB 驱动 PermissionGuard 基础(废弃 permission.guard.ts 硬编码 ROLE_PERMISSIONS map改调 IamService.getEffectivePermissionsI3
9. /readyz 深度检查 5 项依赖DB SELECT 1 / Redis PING / Kafka 连接 / gRPC 自身可达 / JWKS 可读)
10. 01/02 文档回写(删除全部中间过渡方案,对齐 I1-I8 + §2.15/§2.16/§5.5
- **验收标准**
- gRPC 50052 HealthService.Check 返回 SERVING
- 8 RPC 全部可调用并返回正确响应
- GetPublicKey 返回 RS256 PEM 公钥(供 api-gateway 验签)
- GetChildrenByParent 返回学生列表(供 parent-bff P4 消费)
- /iam/v1/* 前缀生效,旧路径不保留
- AuthMiddleware 注入 req.useruserId/roles/dataScope
- /readyz 返回 5 项依赖状态
- **就绪信号**gRPC 50052 启用 + GetPublicKey RPC 可用 + HealthService.Check 返回 SERVING
### P2.2:三层角色 + DataScope + 视口 + 审计 + 缓存P3 期间持续补,不阻塞)
- **负责人**ai06
- **批次**:批次 2-3 期间P3 进行中持续补)
- **预估**12-15 天
- **前置依赖**P2.1 完成 + ISSUE-004DataScope 枚举裁决)
- **交付物**
1. 三层角色模型system / organization / temporaryiam_roles 表扩展 role_type + level 字段)
2. DataScope 6 级实现ALL / SCHOOL / GRADE / CLASS / SUBJECT|DISTRICT / SELF待 ISSUE-004 裁决)
3. 视口 4 层admin / teacher / student / parent+ getEffectivePermissions 完整聚合
4. 审计日志user_audit_log 表 + AuditEvent 发布到 edu.iam.audit.created topicpresident §5.5
5. Redis 缓存完整实现getEffectivePermissions TTL 5min + 角色变更主动 DEL + getUserViewports 缓存)
6. 密码策略(强度校验 / 过期提醒 / 重用限制)
7. 单元测试 + 集成测试(覆盖率 ≥ 80%,对齐 classes 黄金模板)
- **验收标准**
- 三层角色可创建/分配/查询
- DataScope 在 Repository 层动态注入 WHERE 条件
- 审计事件可发布到 Kafka
- Redis 缓存命中率可观测iam_permission_cache_hits_total 指标)
- 测试覆盖率 ≥ 80%
### P3-P6RBAC CRUD + 2FA + Vault 迁移 + 硬化
- **负责人**ai06
- **批次**:批次 4-5P5-P6 期间)
- **预估**8-10 天
- **交付物**
1. RBAC CRUD 完整化(角色/权限/视口增删改,系统角色禁止删除)
2. 2FA 实现TOTPpending-features §P2 提及)
3. JWT 密钥迁移 VaultP6president X8
4. /readyz 硬化 + 性能优化(连接池调优、缓存策略优化)
- **验收标准**
- RBAC CRUD 全部端点可用 + 权限装饰器覆盖
- 2FA 可启用/验证/禁用
- Vault 密钥轮换不中断服务
---
## §4 依赖与就绪信号
- **我依赖**iam 是基础服务)
- **我的就绪信号**gRPC 50052 启用 + GetPublicKey RPC 可用 + HealthService.Check 返回 SERVING
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供方 | 就绪标志 | 状态 |
| ------ | ------ | -------- | ---- |
| iam.proto 补全至 12 RPC | coord | proto 文件含 12 RPC + 全部 message | ❌ 仅 4 RPCISSUE-005 |
| events.proto 补全 UserEvent/RoleEvent/AuditEvent | coord | proto 文件含 3 个 message | ❌ 缺失ISSUE-002 |
| shared-ts Outbox 工具包 | coord | outbox.service.ts + outbox.module.ts 可导入 | ✅ 已就绪 |
| shared-ts Redis 工具包 | coord | redis client 单例可导入 | ⏳ 待确认 |
### 4.2 我的就绪信号(供下游消费)
- [ ] iam gRPC 50052 启用HealthService.Check 返回 SERVING
- [ ] IamService 12 RPC 全部可调用Register/Login/RefreshToken/Logout/GetUserInfo/BatchGetUsers/GetEffectivePermissions/GetEffectiveAccess/GetEffectiveDataScope/GetViewports/GetPublicKey/GetChildrenByParent
- [ ] IamService.GetPublicKey 可用(返回 RS256 PEM 公钥,供 api-gateway 验签)
- [ ] IamService.GetChildrenByParent 可用(供 parent-bff 查孩子列表)
- [ ] edu.iam.user.events / edu.iam.role.events / edu.iam.audit.created topic 可发布
- [ ] JWT RS256 签发链路打通access_token 15min + refresh_token 7day 轮换)
- [ ] /iam/v1/* REST 端点可用(供 gateway 透传 + admin-portal 直连)

View File

@@ -1,45 +1,316 @@
# msg 工作排期
> 负责人ai10
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/msg_contract.md](../contracts/msg_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/msg_contract.md](../contracts/msg_contract.md)、[02-architecture-design.md §10](../../../services/msg/docs/02-architecture-design.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
---
## §1 总览
msg 是消息服务,提供 NotificationServicePreferenceServiceTemplateService基于 Outbox 模式发布消息事件。全阶段目标P2 服务骨架+Outbox → P3 三大 Service 实现 → P4-P6 持续优化
msg 是消息通知中台P5,提供 NotificationService + NotificationPreferenceService + NotificationTemplateService 三服务,基于 Outbox 模式发布通知事件,消费 iam/core-edu/data-ana 共 12 类事件触发多渠道通知
**全阶段目标**
- P2-P3服务骨架补全schema 迁移 + Outbox + Kafka 基础设施 + mock 消费)
- P4三大 Service 主体实现Notification + Preference + Template+ ChannelDispatcher 多渠道
- P5gRPC 50056 启用 + PushGatewayClient gRPC + 12 类事件 consumer + ES mapping
- P6测试覆盖 ≥ 80% + /readyz 硬化 + 黄金模板对齐 + README 修正
**批次归属**:批次 4P5依赖批次 3 contentai09就绪后启动预估 13 天。
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai10 msg 全阶段排期
title ai10 msg 全阶段排期13 天)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a10a, 2026-07-10, Xd
```
section P2-P3 骨架补全
T1-T2 schema迁移(notifications+preferences字段) :crit, a1, 2026-07-10, 1d
T3 新建msg_notification_templates表 :a2, after a1, 1d
T4 新建msg_outbox_events+Publisher worker :crit, a3, after a1, 2d
T5 新建shared/kafka(producer+consumer骨架) :crit, a4, after a3, 1d
T6 引入ioredis+IdempotencyGuard(SETNX) :a5, after a3, 1d
> **注意**:以上为 coord 初始规划ai10 接管后必须自行细化为完整 P2-P6 排期。
section P4 主体实现
T7 ChannelDispatcher多渠道抽象 :crit, a6, after a4, 2d
T10 重构notifications.service(移除同步fetch) :a7, after a6, 1d
T11 batchMarkAsRead/markAllAsRead/recall/getUnreadCount :a8, after a7, 1d
T12 NotificationPreference CRUD :a9, after a7, 1d
T13 NotificationTemplate CRUD+render :a10, after a7, 1d
T15 createBatch改批量INSERT :a11, after a7, 1d
section P5 gRPC+事件+ES
T8 PushGatewayClient gRPC(替代fetch降级) :crit, a12, after a8, 1d
T9 12类Kafka事件consumer(iam/core-edu/data-ana) :crit, a13, after a12, 2d
T14 ES索引mapping+ensureIndex+同步 :a14, after a12, 1d
gRPC 50056启用+3 Service 13 RPC :crit, a15, after a13, 1d
section P6 硬化与对齐
T16 NotificationsModule补exports :a16, after a15, 1d
T17 /readyz多依赖(DB/ES/Redis/Kafka/PushGW) :a17, after a15, 1d
T19 统一关闭到LifecycleService :a18, after a15, 1d
T20-T21 DB改getDb()+ID改cuid2 :a19, after a15, 1d
T22 单元测试覆盖≥80% :crit, a20, after a19, 2d
T23 修正README与实现对齐 :a21, after a20, 1d
```
---
## §3 详细任务
### 全阶段任务
### P2-P3 骨架补
#### T1-T2schema 迁移notifications + preferences 字段扩展)
- **负责人**ai10
- **交付物**:⚠️ 由 ai10 自行补充
- **依赖**:见 [contracts/msg_contract.md](../contracts/msg_contract.md)
- **验收标准**:⚠️ 由 ai10 自行补充
- **依赖**msg 独占 DB
- **交付物**
- `msg_notifications` 表新增 status / metadata / related_entity_type / related_entity_id / group_id / sender_id / template_id / event_id / updated_at 字段 + 6 个索引
- `msg_notification_preferences` 表补齐 created_at / updated_at + 新增 frequency_limit / quiet_hours_start / quiet_hours_end / quiet_hours_timezone 字段
- **验收标准**Drizzle schema 定义更新,迁移脚本可执行,索引符合 02-architecture-design.md §3.1.1 / §3.1.2
#### T3新建 msg_notification_templates 表
- **负责人**ai10
- **依赖**T1-T2
- **交付物**`msg_notification_templates` 表 schemacode + type + title_template + content_template + default_channels + variables + locale + statusUNIQUE INDEX `(code, locale)`
- **验收标准**schema 定义 + 迁移脚本,符合 02-architecture-design.md §3.1.3
#### T4新建 msg_outbox_events 表 + Outbox Publisher worker
- **负责人**ai10
- **依赖**T1-T2
- **交付物**
- `msg_outbox_events` 表 schemaevent_id PK + aggregate_type + aggregate_id + event_type + topic + payload + status + retry_count + created_at + published_at + next_retry_at
- `shared/outbox/outbox.publisher.ts`(独立 worker每 1s 轮询 PENDING 事件投递 Kafka
- `shared/outbox/outbox.schema.ts`Drizzle schema
- **验收标准**Outbox Publisher 可轮询 + 投递 + 更新 status=SENT符合 02-architecture-design.md §3.1.4 / §5.4
- **关联 ISSUE**ISSUE-003Outbox 强制)
#### T5新建 shared/kafka/producer + consumer 骨架)
- **负责人**ai10
- **依赖**T4
- **交付物**
- `shared/kafka/kafka.producer.ts`idempotent=true + transactionalId=msg-producer
- `shared/kafka/kafka.consumer.ts`consumer group = msg-service
- `shared/kafka/topic-map.ts`PRODUCER_TOPIC_MAP + CONSUMER_TOPICS
- **验收标准**producer 可投递消息consumer 可订阅 topic符合 02-architecture-design.md §5.3
#### T6引入 ioredis + IdempotencyGuardSETNX
- **负责人**ai10
- **依赖**:无
- **交付物**
- `shared/redis/redis.client.ts`ioredis 客户端,连接 REDIS_URL
- `shared/redis/idempotency.guard.ts`SETNX `msg:processed:{event_id}` TTL 7 天)
- `shared/redis/read-bitmap.ts`(已读位图 BITCOUNT / GETBIT
- env.ts 补 REDIS_URL 必填校验
- **验收标准**IdempotencyGuard SETNX 原子去重Redis 不可用时降级到 DB 唯一索引
- **关联 ISSUE**ISSUE-012三层幂等防线补 msg_idempotency 表中间层)
### P4 主体实现
#### T7ChannelDispatcher 多渠道抽象
- **负责人**ai10
- **依赖**T5、T6
- **交付物**
- `channels/notification-channel.interface.ts`NotificationChannel 接口)
- `channels/in-app.channel.ts`(站内信,写 MySQL
- `channels/email.channel.ts`邮件SMTP异步队列
- `channels/sms.channel.ts`短信HTTP API
- `channels/wechat.channel.ts`微信HTTP API
- `channels/push.channel.ts`(推送,调 PushGatewayClient
- `channels/channel-dispatcher.ts`Promise.allSettled 并行投递 + in_app 总是发送)
- **验收标准**:新增渠道只需实现接口 + 注册,符合 02-architecture-design.md §12
#### T10重构 notifications.service.ts
- **负责人**ai10
- **依赖**T4、T5、T7
- **交付物**:重构 notifications.service.ts移除同步 fetch push-gateway改为 ChannelDispatcher + Outbox 事务
- **验收标准**send 方法走 BEGIN TX → INSERT notifications + INSERT outbox → COMMIT → ChannelDispatcher.dispatch
#### T11新增端点batchMarkAsRead / markAllAsRead / recall / getUnreadCount
- **负责人**ai10
- **依赖**T10
- **交付物**controller + service 新增 4 个端点
- **验收标准**:符合 02-architecture-design.md §4.1 REST API 表
- **关联 ISSUE**ISSUE-010markAsRead 权限改 READ
#### T12NotificationPreference CRUD
- **负责人**ai10
- **依赖**T1-T2
- **交付物**`preferences/` 目录controller + service + repository + schema + dto
- **验收标准**GET / PUT preferences 端点可用
#### T13NotificationTemplate CRUD + render
- **负责人**ai10
- **依赖**T3
- **交付物**`templates/` 目录controller + service + repository + schema + dto`{{variable}}` 占位符替换渲染
- **验收标准**CreateTemplate / GetTemplate / ListTemplates / RenderTemplate 可用
#### T15createBatch 改批量 INSERT
- **负责人**ai10
- **依赖**T10
- **交付物**createBatch 改为 `db.insert(notifications).values([...])` 批量 INSERT
- **验收标准**1 万条广播通知 < 5s性能验收
### P5 gRPC + 事件 + ES
#### T8PushGatewayClient gRPC
- **负责人**ai10
- **依赖**D5push-gateway 提供 gRPC PushService.Push
- **交付物**`shared/push/push-gateway.client.ts`gRPC 调用,替代 fetch POST /internal/push 降级)
- **验收标准**gRPC 调用 push-gateway PushService.Push降级模式保留push-gateway 不可用时走 in_app
- **关联 ISSUE**ISSUE-005调用方向澄清
#### T912 类 Kafka 事件 consumer
- **负责人**ai10
- **依赖**T5、T6、D1-D2events.proto 补齐 message + 字段)
- **交付物**
- `shared/kafka/consumers/iam.consumer.ts`6 类 user/role 事件)
- `shared/kafka/consumers/core-edu.consumer.ts`5 类 exam/homework/grade/attendance 事件)
- `shared/kafka/consumers/data-ana.consumer.ts`1 类 mastery 事件)
- 每个 consumer 走 IdempotencyGuard → NotificationService.createNotificationFromEvent
- **验收标准**12 类事件均可消费 + 幂等去重 + fan-out 通知
- **关联 ISSUE**ISSUE-013events.proto 缺 4 类 message阻塞
#### T14ES 索引 mapping + ensureIndex + 同步
- **负责人**ai10
- **依赖**:无
- **交付物**
- `config/elasticsearch.ts``notifications` 索引 mappingik_max_word 分词)
- ensureIndex 幂等创建
- 数据同步Outbox 事件触发 ES 索引更新(替代当前同步 safeIndex
- **验收标准**ES 检索可用mapping 符合 02-architecture-design.md §3.2.1
- **关联 ISSUE**ISSUE-011降级方向待仲裁
#### gRPC 50056 启用 + 3 Service 13 RPC
- **负责人**ai10
- **依赖**D3-D4msg.proto 补 RPC + Service
- **交付物**
- `notifications.grpc.controller.ts`NotificationService gRPC
- `preferences.grpc.controller.ts`NotificationPreferenceService gRPC
- `templates.grpc.controller.ts`NotificationTemplateService gRPC
- app.module.ts 注册 gRPC server :50056
- **验收标准**HealthService.Check 返回 SERVING13 RPC 可调用
- **关联 ISSUE**ISSUE-009RPC 数量待仲裁 13 vs 17
### P6 硬化与对齐
#### T16NotificationsModule 补 exports
- **负责人**ai10
- **依赖**:无
- **交付物**notifications.module.ts 补 `exports: [NotificationsService]`
- **验收标准**BFF 可注入 NotificationsService
#### T17/readyz 多依赖检查
- **负责人**ai10
- **依赖**T4、T5、T6、T8
- **交付物**health.controller.ts /readyz 检查 DB / ES / Redis / Kafka producer / Kafka consumer / PushGateway 6 项依赖
- **验收标准**:符合 02-architecture-design.md §6.6 判定规则DB down → downRedis/ES/Kafka down → degraded
#### T19统一关闭到 LifecycleService
- **负责人**ai10
- **依赖**:无
- **交付物**:移除 main.ts 重复 closeDb/closeEs统一到 LifecycleService8 步关闭序列
- **验收标准**:符合 02-architecture-design.md §6.7 优雅关闭顺序
#### T20-T21DB 改 getDb() + ID 改 cuid2
- **负责人**ai10
- **依赖**:无
- **交付物**database.ts 改 getDb() 函数式service 层 randomUUID → cuid2
- **验收标准**:与 classes 黄金模板对齐
#### T22单元测试覆盖 ≥ 80%
- **负责人**ai10
- **依赖**:全部 P4-P5 任务
- **交付物**`*.spec.ts`Service / Repository / ChannelDispatcher / IdempotencyGuard / OutboxPublisher
- **验收标准**:覆盖率 ≥ 80%
#### T23修正 README
- **负责人**ai10
- **依赖**:全部任务
- **交付物**README.md API 表与实现对齐PUT /:id/read、补 batch/user/:userId/page 端点、补 env 变量表)
- **验收标准**:无文档脱节
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai10 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai10 自行补充
### 4.1 我依赖的上游就绪标志
- [ ] **D1**events.proto 补 UserEvent / RoleEvent / NotificationEvent / MasteryEvent messagecoord 维护)— 🔴 阻塞 T9
- [ ] **D2**events.proto GradeEvent 补 class_id全部事件补 student_ids[]coord 维护)— 🔴 阻塞 T9 fan-out
- [ ] **D3**msg.proto 补 BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / RecallNotification RPCcoord 维护)— 🔴 阻塞 gRPC依赖 ISSUE-009 仲裁)
- [ ] **D4**msg.proto 补 NotificationPreferenceService + NotificationTemplateServicecoord 维护)— 🔴 阻塞 gRPC
- [ ] **D5**push-gateway 提供 gRPC PushService.Push 方法ai02— 🔴 阻塞 T8
- [ ] **D6**iam 发布 6 类 user/role 事件ai06— 🟡 不阻塞开发(用 mock阻塞集成验证
- [ ] **D7**core-edu 发布 5 类教学事件ai08— 🟡 同上
- [ ] **D8**data-ana 发布 mastery 事件ai11— 🟡 同上
- [ ] **D9**Redis 集群可用infra 部署)— 🟡 降级到 DB 唯一索引
### 4.2 我的就绪标志(供下游消费)
- [ ] msg gRPC 50056 启用HealthService.Check 返回 SERVING
- [ ] NotificationService RPC 可调用(数量待 ISSUE-009 仲裁)
- [ ] NotificationPreferenceService RPC 可调用
- [ ] NotificationTemplateService RPC 可调用(含 RenderTemplate
- [ ] `edu.notification.*` topic 可发布(供 push-gateway / data-ana 消费,命名待 ISSUE-008 仲裁)
- [ ] /readyz 返回 6 项依赖状态
- [ ] 测试覆盖率 ≥ 80%
---
## §5 Mock 策略
### 5.1 我提供的 mock供下游
在 msg 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / push-gateway提供 mock
- **gRPC mock**grpc-mock 拦截 50056 端口
- ListNotifications 返回固定 10 条未读通知
- MarkAsRead 返回 success=true
- GetPreference 返回默认偏好in_app + email 开启sms + push 关闭)
- RenderTemplate 返回固定 title + content
- **Kafka mock**msg 就绪前不发布真实通知事件push-gateway 使用本地 stub 推送
### 5.2 我消费的 mock开发期间
在真实上游就绪前msg 使用以下 mock
- **业务事件**core-edu / data-ana 就绪前msg 内置定时器发布本地 stub 事件ExamEvent / HomeworkEvent触发 mock 通知流程验证 consumer 链路
- **用户偏好**iam 就绪前使用默认偏好(所有用户 in_app 开启)
- **模板渲染**:内置 5 个常用模板exam.published / homework.graded / grade.recorded / mastery.warning / system.notice
- **Push Gateway**ai02 就绪前用 fetch POST /internal/push 降级(当前实现保留)
---
## §6 风险与缓解
| 风险 | 影响 | 缓解 |
| ---- | ---- | ---- |
| events.proto 补齐延迟D1-D2 | T9 consumer 无法验证 | 开发期用 stub 事件proto 补齐后切换 |
| RPC 数量仲裁延迟ISSUE-009 | gRPC controller 实现范围不确定 | 先实现 13 RPC 基线,仲裁后增减 |
| topic 命名仲裁延迟ISSUE-008 | Kafka producer/consumer topic 不确定 | 开发期用 02-architecture-design.md §5.3 的 TOPIC_MAP仲裁后统一 |
| push-gateway gRPC 延迟D5 | T8 无法验证 | 保留 fetch POST 降级gRPC 就绪后切换 |

View File

@@ -1,45 +1,333 @@
# parent-bff 工作排期
> 负责人ai05
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-bff_contract.md](../contracts/parent-bff_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-bff_contract.md](../contracts/parent-bff_contract.md)、[02-architecture-design.md](../../../services/parent-bff/docs/02-architecture-design.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
---
## §1 总览
parent-bff 为家长端提供 GraphQL 聚合 API覆盖 Dashboard、多子女切换、成绩趋势等场景。全阶段目标P2 GraphQL schema 骨架 → P3 Dashboard+多子女+成绩趋势 → P4-P6 持续优化
parent-bff 为家长端提供 GraphQL 聚合 API(端口 3010,覆盖 Dashboard、多子女切换、成绩趋势、学情诊断、通知偏好等场景。
**全阶段目标**
| 阶段 | 交付核心 | 依赖 |
| --- | --- | --- |
| P4 MVP | GraphQL Yoga + DataLoader + 多子女切换 + ChildGuard + 并行 gRPC 聚合 + Redis 缓存 + /readyz 下游探针 | iam.GetChildrenByParentI6 裁决)+ core-edu gRPC + data-ana gRPC |
| P5 通知接入 | msg gRPC + push-gateway HTTP + Kafka consumer 缓存失效 + 通知偏好过滤 | msg gRPC 50056 + push-gateway /internal/push |
| P6 硬化 | 熔断器 opossum + HPA + SLO 监控 + 灰度发布 | — |
**关键路径**:批次 0coord 补 proto→ 批次 1iam gRPC + GetChildrenByParent→ 批次 2core-edu gRPC**批次 3parent-bff P4 MVP** → 批次 4msg P5→ parent-bff P5 接入
**P0 阻塞项**(详见 [objections/parent-bff_issue.md](../objections/parent-bff_issue.md) ISSUE-008/007
- iam.GetChildrenByParent RPC + iam_student_guardians 表I6 裁决ai06 负责)
- core-edu ClassService.GetClass proto 缺失ISSUE-008待 coord 仲裁归属)
- msg.proto Notification 缺 child_id 字段ISSUE-007P5 阶段阻塞)
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai05 parent-bff 全阶段排期
title ai05 parent-bff 全阶段排期(全并行 + mock
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a5a, 2026-07-10, Xd
section P4 MVP 核心
P4.1 骨架搭建(env+main+health) :crit, a5a, 2026-07-31, 2d
P4.2 GraphQL Yoga+schema第一版 :crit, a5b, after a5a, 3d
P4.3 DownstreamClient抽象+gRPC mock :crit, a5c, after a5a, 2d
P4.4 ChildGuard+DataLoader实现 :crit, a5d, after a5b, 3d
P4.5 Dashboard/children/grades Resolver :a5e, after a5d, 2d
P4.6 Redis缓存层+ Orchestrator降级 :a5f, after a5e, 2d
P4.7 /readyz下游探针+可观测三支柱 :a5g, after a5f, 1d
P4.8 单元+集成测试(≥80%覆盖) :a5h, after a5g, 2d
section P5 通知接入
P5.1 msg gRPC client接入 :b5a, after a5h, 2d
P5.2 Notification Resolver+偏好配置 :b5b, after b5a, 2d
P5.3 push-gateway HTTP /internal/push :b5c, after b5a, 1d
P5.4 Kafka consumer订阅+缓存失效 :b5d, after b5c, 3d
P5.5 通知偏好过滤逻辑 :b5e, after b5d, 1d
section P6 硬化
P6.1 opossum熔断器per-service :c5a, after b5e, 2d
P6.2 HPA+podAntiAffinity :c5b, after c5a, 1d
P6.3 SLO告警规则+灰度发布 :c5c, after c5b, 2d
```
> **注意**:以上为 coord 初始规划ai05 接管后必须自行细化为完整 P2-P6 排期
> **说明**:以上日期为 coord 总排期推算(批次 3 P4 在批次 2 P3 完成后启动。全并行模式下P4/P5/P6 代码一口气完成,上游未就绪时用 mock最后统一集成测试
---
## §3 详细任务
### 全阶段任务
### P4.1骨架搭建env + main + health
- **负责人**ai05
- **交付物**:⚠️ 由 ai05 自行补充
- **依赖**:见 [contracts/parent-bff_contract.md](../contracts/parent-bff_contract.md)
- **验收标准**:⚠️ 由 ai05 自行补充
- **依赖**:无(克隆 teacher-bff 骨架)
- **交付物**
- `services/parent-bff/package.json`name=@edu/parent-bff
- `src/config/env.ts`Zod 校验,见 02 §12.1 完整配置项)
- `src/main.ts`(启动 + /metrics + SIGTERM 优雅关闭)
- `src/app.module.ts`
- `src/shared/health/health.controller.ts`/healthz 直接 ok
- `src/shared/observability/{logger,metrics,tracer}.ts`service=parent-bff
- `src/shared/errors/{application-error,global-error.filter}.ts`BFF_PARENT_ 前缀)
- `Dockerfile`多阶段EXPOSE 3010
- `tsconfig.json`NodeNext + ESM .js 后缀)
- **验收标准**`pnpm run typecheck` + `pnpm run lint` 零错误;`docker build` 通过;本地启动 /healthz 返回 200
### P4.2GraphQL Yoga + schema 第一版
- **负责人**ai05
- **依赖**P4.1
- **交付物**
- `src/entry/graphql.controller.ts`POST /graphql + GET /graphql playground 仅 dev
- `src/entry/context.middleware.ts`(解析 x-user-id/x-user-roles/x-request-id 注入 GraphQL context
- `src/graphql/schema.ts`typeDefs + resolvers见 02 §4.2 GraphQL schema
- `src/graphql/types/`parent/child/grade/homework/exam/analytics/notification.type.ts
- GraphQL 复杂度限制depth ≤ 7cost ≤ 100002 §9 #5
- `packages/shared-ts/contracts/graphql/parent-bff.graphql`SDL-first 集中管理,对齐 coord ARB-001 模式)
- **验收标准**POST /graphql 可内省 schema深度超 7 的查询被拒cost 超 1000 被拒
### P4.3DownstreamClient 抽象 + gRPC mock
- **负责人**ai05
- **依赖**P4.1 + ai03 DownstreamClient 抽象模式teacher-bff 参考)
- **交付物**
- `src/clients/grpc/grpc.factory.ts`gRPC client 创建 + interceptortrace/metrics/retry
- `src/clients/iam.client.ts`IamClient interface + gRPC impl + mock impl
- `src/clients/core-edu.client.ts`CoreEduClient interface + gRPC impl + mock impl
- `src/clients/data-ana.client.ts`DataAnaClient interface + gRPC impl + mock impl
- mock 数据:固定 2 个孩子student-001 李同学 + student-002 李妹妹)+ 固定成绩/作业/考试/学情
- **验收标准**DEV_MODE=true 时走 mock impl返回固定数据mock 数据 student_id 与 core-edu mock 一致(见 contract.md §4.2
### P4.4ChildGuard + DataLoader 实现
- **负责人**ai05
- **依赖**P4.2 + P4.3
- **交付物**
- `src/aggregation/child-guard.ts`DataScope=CHILDREN 越权校验,见 02 §2.3
- 30s TTL Redis 缓存绑定列表ISSUE-002 修正§3.1.1 同步为 30s
- singleflight 模式防缓存击穿02 §14 #5
- 越权时抛 BFF_PARENT_CHILD_NOT_BOUND(403)
- `src/dataloader/dataloader.module.ts`per-request 实例注册器)
- `src/dataloader/{children,grade,homework,exam}.dataloader.ts`(批量去重 N+1 防御)
- **验收标准**
- childId ∉ 绑定列表时抛 403
- 30s 内第二次查询不调 iam.GetChildrenByParent
- 并发 100 请求只调 iam 1 次singleflight
- DataLoader 同 parentId 多次调用合并为 1 次 gRPC
### P4.5Dashboard / children / grades Resolver
- **负责人**ai05
- **依赖**P4.4
- **交付物**
- `src/graphql/resolvers/dashboard.resolver.ts`(聚合 iam.GetUserInfo + iam.GetChildrenByParent + core-edu.ListGradesByStudent 并行)
- `src/graphql/resolvers/child.resolver.ts`childQuery + ChildGuard 校验 + 延迟加载 grades/homework/exams/analytics
- `src/graphql/resolvers/select-child.resolver.ts`mutation仅审计日志不持久化
- `src/graphql/resolvers/grade.resolver.ts`childGrades query含分页
- **验收标准**
- dashboard Query 返回 parent + children + unreadNotifications
- child(childId) 对未绑定 childId 返回 403
- selectChild mutation 记录审计日志traceId + parentId + childId + timestamp
### P4.6Redis 缓存层 + Orchestrator 降级
- **负责人**ai05
- **依赖**P4.5
- **交付物**
- `src/shared/cache/redis.client.ts`ioredis 连接)
- `src/shared/cache/cache-key.builder.ts`bff:parent:* 前缀,见 02 §3.1.1
- `src/aggregation/orchestrator.ts`Promise.allSettled 并行 + 降级标记 partial
- `src/aggregation/response-mapper.ts`proto → GraphQL type含 Grade.score string→Float 转换ISSUE-008
- `src/aggregation/fallback-strategy.ts`(下游失败时返回缓存陈旧数据或 null 字段)
- **验收标准**
- dashboard 聚合结果缓存 15s第二次命中不调下游
- data-ana 失败时返回 dashboard.degraded=true其他字段正常
- Redis 不可用时降级为内存 LRU
### P4.7/readyz 下游探针 + 可观测三支柱
- **负责人**ai05
- **依赖**P4.6
- **交付物**
- `src/shared/health/health.controller.ts` 补 /readyz 下游探针02 §9 #7 ai05 调整)
- 探针iam gRPC + core-edu gRPC + data-ana gRPC + Redis超时 1s/服务
- 任一失败返回 503 + degraded=true
- metrics 指标全量落地02 §6.4 表格 11 项指标)
- tracer auto-instrumentationshttp/nestjs/express/ioredis/grpc-js
- logger 字段对齐parentId/childId/operation/traceId
- **验收标准**
- /readyz 返回 4 项依赖状态
- /metrics 暴露 parent_bff_* 指标
- Jaeger 可看到 dashboard 请求完整 span 链
### P4.8:单元 + 集成测试≥80% 覆盖)
- **负责人**ai05
- **依赖**P4.7
- **交付物**
- `test/unit/child-guard.test.ts`(越权拦截 + 缓存命中 + singleflight
- `test/unit/orchestrator.test.ts`(并行编排 + 部分失败降级)
- `test/unit/dataloader.test.ts`(批量去重)
- `test/unit/graphql-complexity.test.ts`depth/cost 限制)
- `test/integration/dashboard.test.ts`3 子女 × 3 下游并行Redis Testcontainers
- `test/integration/readyz.test.ts`iam 故障时 503
- `vitest.config.ts`(覆盖率阈值 80%
- **验收标准**:覆盖率 ≥ 80%10 项关键用例02 §11.2)全部通过
### P5.1msg gRPC client 接入
- **负责人**ai05
- **依赖**msg gRPC 50056 就绪ai10或 mock
- **交付物**
- `src/clients/msg.client.ts`MsgClient interface + gRPC impl + mock impl
- env.ts 启用 MsgServiceUrl / MsgGrpcTarget
- **验收标准**mock 模式下 NotificationService.ListNotifications / MarkAsRead 可调
### P5.2Notification Resolver + 偏好配置
- **负责人**ai05
- **依赖**P5.1 + msg.proto 补 child_id 字段ISSUE-007 仲裁结果)
- **交付物**
- `src/graphql/resolvers/notification.resolver.ts`notifications query + markNotificationRead mutation
- `src/graphql/resolvers/notification-preference.resolver.ts`notificationPreferences query + updateNotificationPreferences mutation
- `src/parent/dto/parent-inputs.dto.ts`UpdateNotificationPreferencesSchema Zod 校验)
- **验收标准**notifications Query 返回家长通知列表(含 childId偏好更新后缓存失效
### P5.3push-gateway HTTP /internal/push 接入
- **负责人**ai05
- **依赖**push-gateway /internal/push 就绪ai02或 mock
- **交付物**
- `src/clients/http/push-http.client.ts`HTTP POST /internal/pushU2 仲裁)
- env.ts 启用 PushGatewayUrl
- **验收标准**mock 模式下 pushViaHttp 返回 success
### P5.4Kafka consumer 订阅 + 缓存失效
- **负责人**ai05
- **依赖**Kafka topic 已创建C5 仲裁edu.notification.sent/read/recalled/failed + edu.teaching.grade.recorded/homework.graded/exam.published
- **交付物**
- `src/shared/kafka/kafka.consumer.ts`consumer group: parent-bff-event-subscriber
- `src/shared/kafka/handlers/notification-push.handler.ts`(偏好过滤 + push-gateway 推送)
- `src/shared/kafka/handlers/cache-invalidation.handler.ts`(成绩/作业/考试事件失效对应缓存)
- 幂等性Redis SETNX event_id 去重
- DLQedu.parent-bff.dlq
- **验收标准**
- 收到 edu.teaching.grade.recorded 后 bff:parent:grades:{childId} 缓存失效
- 家长关闭"成绩推送"偏好时,该家长不收到推送
- 重复 event_id 不重复处理
### P5.5:通知偏好过滤逻辑
- **负责人**ai05
- **依赖**P5.4
- **交付物**:通知偏好过滤逻辑集成到 notification-push.handler02 §5.4
- 拉取家长 NotificationPreferencesRedis 缓存 300s
- 按 eventTypeMap 映射事件类型 → 偏好开关
- 取 prefs.channels 与 event.channels 交集
- **验收标准**:偏好开关为 false 时不推送channels 无交集时不推送
### P6.1opossum 熔断器 per-service
- **负责人**ai05
- **依赖**P5 完成
- **交付物**
- `src/clients/grpc/circuit-breaker.ts`opossumper-downstream-service 独立 circuitiam/core-edu/data-ana/msg
- 熔断开启时抛 BFF_PARENT_SERVICE_UNAVAILABLE(503)
- metrics: parent_bff_circuit_state Gauge
- **验收标准**:下游连续失败 5 次熔断开启30s 后半开探测
### P6.2HPA + podAntiAffinity
- **负责人**ai05
- **依赖**P6.1
- **交付物**`infra/k8s/helm/parent-bff/` Chart对齐 004 §1.2 端口)
- HPA 2-10 副本CPU 70% / 内存 80% 触发)
- podAntiAffinity 跨节点分布
- values-dev.yaml / values-staging.yaml / values-prod.yaml
- **验收标准**helm template 通过HPA 可根据负载扩缩
### P6.3SLO 告警规则 + 灰度发布
- **负责人**ai05
- **依赖**P6.2
- **交付物**
- `infra/prometheus/rules.yml` 追加 parent-bff 告警规则P95 > 200ms / 错误率 > 0.1% / 可用性 < 99.9%
- `infra/grafana/dashboards/parent-bff.json` 面板
- 灰度发布:按 parentId hash 路由流量百分比K8s Service + Istio/Envoy weight
- **验收标准**Prometheus 告警规则 lint 通过Grafana 面板可展示 parent-bff 指标
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai05 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai05 自行补充
### 4.1 我依赖的上游就绪标志
| 上游 | 就绪信号 | 责任方 | 阻塞阶段 | 状态 |
| --- | --- | --- | --- | --- |
| iam gRPC 50052 + GetChildrenByParent RPC | HealthService.Check = SERVING + GetChildrenByParent 可调 | ai06 | P4P0 阻塞) | ⏳ |
| iam_student_guardians 表 | 表已建 + Repository 查询方法可用 | ai06 | P4P0 阻塞) | ⏳ |
| core-edu gRPC 50053 | HealthService.Check = SERVING + Exam/Homework/Grade Service 可调 | ai08 | P4 | ⏳ |
| core-edu ClassService.GetClass | proto 补全 + RPC 实现ISSUE-008 待仲裁) | ai08 或 classes | P4 | ⏳ |
| data-ana gRPC 50055 | HealthService.Check = SERVING + AnalyticsService 可调 | ai11 | P4 | ⏳ |
| msg gRPC 50056 | HealthService.Check = SERVING + NotificationService 可调 | ai10 | P5 | ⏳ |
| msg.proto Notification.child_id | proto 字段补全ISSUE-007 待仲裁) | ai10 | P5 | ⏳ |
| push-gateway /internal/push | HTTP 端点可用 | ai02 | P5 | ⏳ |
| api-gateway /parent 路由 | `/api/v1/parent/*` → parent-bff:3010 代理生效 | ai01 | P4 | ⏳ |
| Kafka topic 已创建 | edu.notification.sent/read/recalled/failed + edu.teaching.* | coord/infra | P5 | ⏳ |
| Redis 已部署 | redis://edu-redis:6379 可达 | coord/infra | P4 | ⏳ |
| buf.gen.yaml gRPC 插件 | TS gRPC client 代码生成可用 | coord | P4 | ⏳ |
| ai03 DownstreamClient 抽象 | teacher-bff clients/ 抽象层可参考 | ai03 | P4 | ⏳ |
### 4.2 我的就绪信号(供下游消费)
| 信号 | 检查方式 | 消费方 |
| --- | --- | --- |
| parent-bff GraphQL :3010 启用 | GET /healthz 返回 200 | api-gateway / K8s |
| /readyz 返回 200含 4 下游 gRPC 连通性) | GET /readyz 返回 200 | K8s readinessProbe |
| GraphQL schema 可内省 | POST /graphql 返回 schema | parent-portalai15 |
| 核心 Query 可执行 | dashboard / myChildren / childGrades / childAnalytics | parent-portal |
| 核心 Mutation 可执行 | selectChild / markNotificationReadP5 | parent-portal |
| DataScope=CHILDREN 校验生效 | 家长查询未绑定 childId 返回 403 | 集成测试 |
| metrics 暴露 | GET /metrics 返回 parent_bff_* 指标 | Prometheus |
### 4.3 全并行 Mock 策略
> 开发期间上游未就绪时parent-bff 使用 mock 完成全部 P4-P6 代码,最后统一集成测试。
| 下游 | Mock 方式 | 切换真实时机 |
| --- | --- | --- |
| iam gRPC | grpc-mock 拦截 + 固定 UserInfoparent 角色)+ 固定 2 个 ChildInfo | iam 就绪信号 ✅ |
| core-edu gRPC | grpc-mock 拦截 + 固定成绩/作业/考试 | core-edu 就绪信号 ✅ |
| data-ana gRPC | grpc-mock 拦截 + 固定学情/趋势 | data-ana 就绪信号 ✅ |
| msg gRPC | grpc-mock 拦截 + 固定 10 条通知(含 childId | msg 就绪信号 ✅ |
| push-gateway HTTP | fetch mock + 返回 success | push-gateway 就绪信号 ✅ |
| Redis | Testcontainers 真实 Redis 实例 | — |
| Kafka | kafkajs mock + jest.mock | Kafka topic 创建 ✅ |
**关键**iam.GetChildrenByParent 的 mock 必须返回与 core-edu mock 数据一致的 student_id否则 ChildGuard 越权校验会失败。
---
## §5 跨模块协作需求(需 coord 协调)
| # | 需求 | 涉及 AI | 阻塞阶段 | 协调内容 |
| --- | --- | --- | --- | --- |
| 1 | iam 补 GetChildrenByParent RPC + iam_student_guardians 表 | ai06 | P4P0 | I6 裁决已定ai06 P2.1 即补 |
| 2 | core-edu ClassService 归属仲裁 + proto 补全 | ai08 / classes | P4 | ISSUE-008 待 coord 仲裁 |
| 3 | msg.proto Notification 补 child_id 字段 | ai10 | P5 | ISSUE-007 待 coord 仲裁 |
| 4 | api-gateway 新增 /parent 路由 | ai01 | P4 | main.go + config.go 新增 ParentBffURL |
| 5 | 004 §4 依赖图同步 C6 仲裁(补 DataAna + Msg | coord | P4 | ISSUE-004 |
| 6 | parent-portal 文档同步 GraphQL 决策 | ai15 | P4 | ISSUE-005 跨模块契约冲突 |
| 7 | buf.gen.yaml 补 gRPC TS 插件 | coord | P4 | 02 §7.3 #7 |
| 8 | docker-compose.deploy.yml 新增 parent-bff 服务 | coord | P4 | 端口 3010 + edu-net |
| 9 | full-stack-runbook 端口矩阵追加 3010 | coord | P4 | 02 §7.3 #5 |
| 10 | shared-ts/contracts/graphql/parent-bff.graphql 建库 | ai05 | P4 | SDL-first 集中管理 |

View File

@@ -2,44 +2,331 @@
> 负责人ai15
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
---
## §1 总览
parent-portal 是家长端微前端,通过 MF Remote 接入主应用,覆盖 Dashboard、多子女切换等场景。全阶段目标P2 MF Remote 骨架 → P3 Dashboard+多子女切换 → P4-P6 持续优化
parent-portal 是家长端微前端MF Remote),挂载到 teacher-portal Shell覆盖家长仪表盘、多子女切换、子女学情查看、通知偏好等场景
- **MF 角色**RemoteShell = teacher-portal :4000
- **端口**4002dev/prod 一致,[port-allocation.md](../../../../infra/port-allocation.md) §4
- **阶段归属**P4 启动(依赖 P4 的 parent-bff + data-ana 就绪)
- **DataScope**CHILDREN仅查看自己绑定子女的数据
- **全阶段目标**P4 MF Remote 骨架 + 核心页面 → P5 推送接入 + 通知中心 → P6 硬化A11y/性能/安全/PWA/多语言)
> **前置阻塞**(见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md) ISSUE-010
> - iam `GetChildrenByParent` 接口缺失P0 阻塞ai06 补全),补全前用 mock固定 2 个子女 student-001 + student-002
> - ISSUE-001 待 coord 仲裁REST vs GraphQL仲裁前按 GraphQL 预排期mock 用 MSW 拦截 GraphQL
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P4-P6
```mermaid
gantt
title ai15 parent-portal 全阶段排期
title ai15 parent-portal 全阶段排期P4-P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a15a, 2026-07-10, Xd
section P4 骨架与核心页面11d
4.1 MF Remote 骨架+next.config.js+健康检查 :crit, a15a, 2026-07-29, 2d
4.2 GraphQL client接入+MSW mock层 :crit, a15b, after a15a, 1d
4.3 ChildSwitcher+useChildSwitcher+Zustand slice :crit, a15c, after a15b, 2d
4.4 Dashboard页面+ParentDashboard组件 :a15d, after a15c, 2d
4.5 子女成绩页面+ChildGradeChart :a15e, after a15c, 1d
4.6 子女作业页面 :a15f, after a15e, 1d
4.7 通知偏好页面+PreferenceForm+Zod :a15g, after a15d, 1d
4.8 跨标签同步(BroadcastChannel) :a15h, after a15c, 1d
section P4 质量保障(并行)
4.9 Vitest单测+MSW集成测试 :a15i, after a15g, 2d
4.10 Dockerfile多阶段构建 :a15j, after a15a, 1d
section P5 推送与通知中心5d
5.1 WebSocket接入+事件处理 :crit, a15k, after a15i, 2d
5.2 通知中心页面+NotificationFeed :a15l, after a15k, 2d
5.3 推送降级(HTTP轮询) :a15m, after a15l, 1d
section P6 硬化8d
6.1 Web Vitals+OTel browser SDK :a15n, after a15m, 2d
6.2 A11y WCAG 2.2 AA审计+修复 :a15o, after a15n, 2d
6.3 性能优化+bundle分析 :a15p, after a15o, 1d
6.4 多语言扩展(en-US) :a15q, after a15p, 1d
6.5 PWA(Service Worker+manifest) :a15r, after a15q, 1d
6.6 安全硬化(CSP+敏感数据脱敏) :a15s, after a15r, 1d
```
> **注意**:以上为 coord 初始规划ai15 接管后必须自行细化为完整 P2-P6 排期。
> **总工期**P4 11d + P5 5d + P6 8d = 24d约 5 周)
> **关键路径**(红色 critMF 骨架 → GraphQL client → ChildSwitcher → 质量保障 → WebSocket → 通知中心
---
## §3 详细任务
### 阶段任务
### P4 阶段任务
#### P4-1MF Remote 骨架 + next.config.js + 健康检查
- **负责人**ai15
- **交付物**:⚠️ 由 ai15 自行补充
- **依赖**:见 [contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
- **验收标准**:⚠️ 由 ai15 自行补充
- **依赖**teacher-portal Shell MF 配置就绪ARB-002ai13 P2 交付ISSUE-002 仲裁MF shared 清单)
- **交付物**
- `apps/parent-portal/next.config.js`NextFederationPluginRemote 角色,`name: parent_app``exposes: ./pages + ./ChildSwitcher``shared` 含 ARB-002 全部 7 项)
- `apps/parent-portal/src/app/layout.tsx`RootLayout复用 Shell 暴露的字体/令牌/i18n Provider
- `apps/parent-portal/src/app/api/health/route.ts``GET /api/health``{ status: 'ok', ts }`
- `apps/parent-portal/src/app/api/ready/route.ts``GET /api/ready` → 检查 API_GATEWAY_URL 可达)
- `apps/parent-portal/tsconfig.json`(继承 tsconfig.base.jsonstrict
- `apps/parent-portal/package.json`(依赖对齐 Shellreact 18.3 + next 14 + urql + @tanstack/react-query v5 + zustand + nuqs
- **验收标准**
1. `pnpm --filter parent-portal dev` 启动 :4002
2. `GET /api/health` 返回 200
3. teacher-portal Shell 能加载 parent-portal remoteEntry.jsMF 拓扑验证)
4. feature flag `NEXT_PUBLIC_MF_ENABLED` 可控制 MF 开关
#### P4-2GraphQL client 接入 + MSW mock 层
- **负责人**ai15
- **依赖**P4-1ISSUE-001 仲裁(确认 GraphQLteacher-portal Shell 暴露 GraphQLProviderARB-002
- **交付物**
- `apps/parent-portal/src/lib/graphql-client.ts`(从 Shell 暴露的 `useGraphQLClient()` 获取 urql client 单例)
- `apps/parent-portal/src/graphql/queries/`currentUser / myChildren / childSummary / childGrades / childHomework / childTrend / childWeakness 查询文档)
- `apps/parent-portal/src/graphql/mutations/`markAsRead / updateNotificationPreferences mutation 文档)
- `apps/parent-portal/src/mocks/handlers.ts`MSW 拦截 `POST /api/v1/parent/graphql`,按 operationName 返回 mock
- `apps/parent-portal/src/mocks/fixtures/*.json`(固定 2 个子女 student-001 李同学 + student-002 李妹妹,与 parent-bff mock 一致)
- `apps/parent-portal/src/mocks/browser.ts`MSW worker 初始化,`NEXT_PUBLIC_API_MOCKING=enabled` 控制)
- **验收标准**
1. MSW enabled 时,所有 GraphQL 查询返回 mock 数据
2. myChildren mock 返回 2 个子女id 与 childGrades/childHomework mock 的 student_id 一致
3. urql client 单例跨组件共享MF shared singleton 验证)
#### P4-3ChildSwitcher + useChildSwitcher + Zustand slice
- **负责人**ai15
- **依赖**P4-2ISSUE-009 仲裁switchChild 是 GraphQL Mutation 还是纯前端状态)
- **交付物**
- `apps/parent-portal/src/stores/childSwitcherSlice.ts`Zustand slicechildren / currentChildId / isLoading / error / switchChild / refreshChildren
- `apps/parent-portal/src/hooks/useChildSwitcher.ts`(封装 myChildren GraphQL query + 切换逻辑 + invalidate 子女维度查询)
- `apps/parent-portal/src/components/ChildSwitcher.tsx`variant: tab | dropdown状态机idle/switching/switched/error
- `apps/parent-portal/src/components/MultiChildTabBar.tsx`≤3 子女用 Tab>3 用下拉,移动端友好)
- localStorage 持久化 `parent:currentChildId`(刷新恢复)
- **验收标准**
1. 切换子女后,`['parent','grades',currentChildId]` 等子女维度查询 invalidate 重拉
2. 刷新页面后 currentChildId 从 localStorage 恢复
3. 0 子女显示 EmptyChildState1 子女不显示 TabBar2-3 子女显示 Tab
4. 切换子女竞态:快速连续切换,旧请求 abort新数据正确显示
#### P4-4Dashboard 页面 + ParentDashboard 组件
- **负责人**ai15
- **依赖**P4-3
- **交付物**
- `apps/parent-portal/src/app/(app)/parent/dashboard/page.tsx`
- `apps/parent-portal/src/components/ParentDashboard.tsx`(组合 useChildren + useChildSummary插槽summary-cards / todo-reminders / recent-grades / attendance / custom
- `apps/parent-portal/src/components/ChildSummaryCard.tsx`(单子女概览:头像/姓名/年级/今日作业数/成绩趋势缩略图)
- `apps/parent-portal/src/components/AttendanceCalendar.tsx`(出勤日历热力图)
- **验收标准**
1. 多子女并列卡片展示
2. 权限校验:`PARENT_DASHBOARD_VIEW`,用 `<RequirePermission>`
3. SSR 首屏 + CSR 交互(依 02 §14.4 渲染策略)
#### P4-5子女成绩页面 + ChildGradeChart
- **负责人**ai15
- **依赖**P4-3
- **交付物**
- `apps/parent-portal/src/app/(app)/parent/grades/page.tsx`
- `apps/parent-portal/src/components/ChildGradeChart.tsx`recharts 折线 + 班级均分对比 + 多子女对比模式)
- **验收标准**
1. 权限校验:`GRADES_READ_CHILD`
2. 切换子女后图表刷新
3. 多子女对比模式可选
#### P4-6子女作业页面
- **负责人**ai15
- **依赖**P4-3
- **交付物**`apps/parent-portal/src/app/(app)/parent/homework/page.tsx`
- **验收标准**
1. 权限校验:`HOMEWORK_READ_CHILD`
2. 复用 Shell 暴露的 DataTable 展示作业列表
#### P4-7通知偏好页面 + PreferenceForm + Zod
- **负责人**ai15
- **依赖**P4-4
- **交付物**
- `apps/parent-portal/src/app/(app)/parent/preferences/page.tsx`
- `apps/parent-portal/src/components/PreferenceForm.tsx`(矩阵式 UI子女×事件×渠道react-hook-form + zodResolver
- `apps/parent-portal/src/schemas/notificationPreferences.ts`Zod schema见 02 §14.4
- **验收标准**
1. 权限校验:`PARENT_PREFERENCES_UPDATE`
2. 不可用渠道 Toggle disabled + tooltip
3. 保存成功后 invalidate `['parent','preferences']`
4. 表单 dirty 状态追踪 + 离开页提示
#### P4-8跨标签同步BroadcastChannel
- **负责人**ai15
- **依赖**P4-3
- **交付物**
- `apps/parent-portal/src/lib/crossTabSync.ts`(见 02 §16.2 完整实现)
- 集成到 RootLayout`useCrossTabSync()`
- **验收标准**
1. Tab A 切换子女 → Tab B 同步更新
2. LWW 冲突解决ts 大的胜出)
3. Safari 降级为 storage 事件
#### P4-9Vitest 单测 + MSW 集成测试
- **负责人**ai15
- **依赖**P4-4 ~ P4-7
- **交付物**
- `apps/parent-portal/vitest.config.ts`
- 单测:组件渲染/交互、Hook 逻辑、Zod schema 校验、纯函数 utils覆盖率 ≥ 85%
- 集成测试useChildSwitcher + Zustand slice + invalidate 流程MSW mock覆盖率 ≥ 75%
- **验收标准**
1. `pnpm --filter parent-portal test` 全绿
2. 覆盖率达标(单元 ≥ 85%,集成 ≥ 75%
#### P4-10Dockerfile 多阶段构建
- **负责人**ai15
- **依赖**P4-1
- **交付物**`apps/parent-portal/Dockerfile`builder + runtimenode:22-alpine
- **验收标准**
1. `docker build` 成功
2. HEALTHCHECK 指向 `/api/health`
3. 镜像体积 < 300MB
### P5 阶段任务
#### P5-1WebSocket 接入 + 事件处理
- **负责人**ai15
- **依赖**push-gateway :8081/ws 就绪ai02parent-portal P4 完成
- **交付物**
- `apps/parent-portal/src/hooks/useWebSocket.ts`(连接 push-gateway wsJWT 鉴权,自动重连)
- 事件处理:`NotificationRequested` → toast + 未读数+1`GradeRecorded` → toast + 成绩 invalidate`SchoolAnnouncement` → toast + dashboard invalidate
- **验收标准**
1. WS 连接建立后收事件正常
2. 断线自动重连(指数退避)
#### P5-2通知中心页面 + NotificationFeed
- **负责人**ai15
- **依赖**P5-1
- **交付物**
- `apps/parent-portal/src/app/(app)/parent/notifications/page.tsx`
- `apps/parent-portal/src/components/NotificationFeed.tsx`(按子女×类型×已读筛选,批量已读,跳转,置顶)
- **验收标准**
1. 权限校验:`NOTIFICATION_READ_OWN`
2. WebSocket 推送 invalidate 通知列表
#### P5-3推送降级HTTP 轮询)
- **负责人**ai15
- **依赖**P5-1
- **交付物**WS 重试 5 次失败后降级为 HTTP 轮询60s 拉取通知列表)
- **验收标准**:降级后通知延迟 ≤ 60s用户感知降级提示
### P6 阶段任务
#### P6-1Web Vitals + OTel browser SDK
- **交付物**`next/web-vitals` 上报 + OTel browser SDK复用 Shell 暴露的 TracerProvider
- **验收标准**LCP/CLS/TTFB 指标上报到 Gateway
#### P6-2A11y WCAG 2.2 AA 审计 + 修复
- **交付物**axe-core 自动扫描 + 手动键盘导航测试0 严重违规
- **验收标准**:所有页面 0 严重 A11y 违规
#### P6-3性能优化 + bundle 分析
- **交付物**bundle 分析报告 + 代码分割优化(首屏 JS ≤ 80KB gzipped
- **验收标准**Lighthouse 移动端 4G ≥ 90 分
#### P6-4多语言扩展en-US
- **交付物**`apps/parent-portal/src/i18n/messages/en-US/*.json`(镜像 zh-CN 结构)
- **验收标准**en-US 完成度 100%
#### P6-5PWAService Worker + manifest
- **交付物**`public/manifest.json` + Service Worker 缓存策略(见 02 §18.4
- **验收标准**:可安装到主屏,离线可查看缓存的子女数据
#### P6-6安全硬化CSP + 敏感数据脱敏)
- **交付物**CSP 头配置(复用 Shell+ 截图脱敏 + 页面离开遮罩
- **验收标准**CSP 无违规报告;敏感数据不泄漏
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai15 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai15 自行补充
### 4.1 我依赖的上游就绪标志
| 上游 | 就绪信号 | 提供方 | 状态 | 阻塞影响 |
| ---- | -------- | ------ | ---- | -------- |
| teacher-portal Shell | MF exposesAppShell + GraphQLProvider + hooks + UI 组件)+ shared singleton | ai13 | ⏳ P2 | P4 无法启动 |
| parent-bff GraphQL | `POST /graphql` :3010 + currentUser/myChildren/childSummary/childGrades Query | ai05 | ⏳ P4 | 核心数据源 |
| iam GetChildrenByParent | gRPC 50052 + `iam_student_guardians` 表 | ai06 | ⏳ P3 补全 | P0 多子女阻塞(见 ISSUE-010 |
| core-edu | gRPC 50053 + GradeService/HomeworkService/AttendanceService | ai08 | ⏳ P3 | 成绩/作业数据 |
| data-ana | gRPC 50055 + AnalyticsService | ai11 | ⏳ P4 | 学情分析数据 |
| msg | gRPC 50056 + NotificationService | ai10 | ⏳ P5 | 通知中心 |
| push-gateway | :8081/ws WebSocket | ai02 | ⏳ P5 | 实时推送 |
| shared-ts | ApiClient/Loggercoord 维护) | coord | ⏳ | 基础工具 |
| contracts | Permissions 常量coord 维护) | coord | ⏳ | 权限校验 |
| ui-tokens / ui-components / hooks | 三层令牌 + shadcn + usePermission/useAuthai07/ai13 维护) | ai07/ai13 | ⏳ P2 收尾 | UI 基础 |
### 4.2 我的就绪信号(供下游消费)
| 信号 | 说明 | 阶段 |
| ---- | ---- | ---- |
| parent-portal :4002 dev server 启用 | MF Remote 可被 Shell 加载 | P4-1 完成 |
| MF Remote remoteEntry.js 可加载 | Shell 端 `remotes.parent` 可解析 | P4-1 完成 |
| 核心 GraphQL 查询可执行 | currentUser / myChildren / childSummary 返回数据mock 或真实) | P4-2 完成 |
| 多子女切换可用 | ChildSwitcher + invalidate 流程通过 | P4-3 完成 |
| Dashboard 可访问 | 家长登录 → 看到 Dashboard含子女卡片 | P4-4 完成 |
| 健康检查通过 | `GET /api/health` + `GET /api/ready` 200 | P4-1 完成 |
| 测试覆盖率达标 | 单元 ≥ 85% + 集成 ≥ 75% | P4-9 完成 |
| Docker 镜像可构建 | `docker build` 成功 | P4-10 完成 |
### 4.3 全并行 Mock 策略
| 消费接口 | Mock 方式 | 切换真实时机 |
| -------- | --------- | ------------ |
| parent-bff GraphQL | MSW 拦截 `POST /api/v1/parent/graphql`,按 operationName 返回 fixtures | parent-bff GraphQL :3010 就绪 ✅ |
| iam login | MSW 返回固定 JWTparent 角色) | api-gateway + iam 就绪 ✅ |
| push-gateway WebSocket | mock-socket 模拟 WS 推送(每 30s 1 条通知) | push-gateway :8081 就绪 ✅ |
| 子女数据一致性 | myChildren mock 返回 student-001 + student-002与所有 child* 查询 student_id 一致 | iam GetChildrenByParent 就绪 |
> Mock 由 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制,上游就绪后设为 `disabled`。
---
## §5 风险与缓解
| 风险 | 影响 | 缓解 |
| ---- | ---- | ---- |
| ISSUE-001/002 未仲裁REST vs GraphQL | P4-2 GraphQL client 接入方向不确定 | 先按 GraphQL 预排期;仲裁若改 RESTP4-2 重写(预计 1d |
| iam GetChildrenByParent 缺失ISSUE-010 | 多子女场景无法落地 | mock 固定 2 子女开发coord 跟踪 ai06 P3 补全 |
| MF SSR 对齐复杂 | Remote SSR 需 Shell 上下文 | 优先 CSR仅 Dashboard 首屏 SSRP4-1 PoC 验证 |
| parent-bff 契约未最终确认 | GraphQL schema 可能变动 | P4 启动前与 ai05 对齐 schemaMSW mock 解耦 |
| TanStack Query 缓存膨胀 | 多子女历史查询堆积 | gcTime 5min + 切换子女清理非当前子女缓存 |
---
## §6 质量门禁
每个任务完成前必须通过:
- `pnpm --filter parent-portal lint` 零错误
- `pnpm --filter parent-portal typecheck` 零错误
- `pnpm --filter parent-portal test` 全绿P4-9 起强制)
- 设计令牌三层规则(无 `#hex` / 无硬编码字体 / 无任意值ESLint 强制)
- A11yjsx-a11y error 级零违规
> 提交前校验见 [project_rules §8](../../../../.trae/rules/project_rules.md)commit 遵循 Conventional Commits`feat(parent-portal): ...`

View File

@@ -1,45 +1,406 @@
# push-gateway 工作排期
> 负责人ai02
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/push-gateway_contract.md](../contracts/push-gateway_contract.md)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/push-gateway_contract.md](../contracts/push-gateway_contract.md)、[objections/push-gateway_issue.md](../objections/push-gateway_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 依据:[ai-allocation.md](../../ai-allocation.md) §7.2、[02-architecture-design.md](../../../services/push-gateway/docs/02-architecture-design.md) §12 实施优先级、[president-final-rulings.md](../../president-final-rulings.md) §3.4 回写义务
---
## §1 总览
push-gateway 负责长连接接入HTTP /internal/* + WebSocket/SSE与多设备会话管理配合审计表实现消息推送可观测。全阶段目标P2 HTTP+WS 接入 → P3 多设备会话 → P4-P6 审计与加固
push-gateway 是 Go 实现的实时推送基础设施服务L3 网关层),管理 WebSocket 长连接,接收 msg 服务的推送请求投递到在线客户端。无业务状态,仅持有连接池 + Redis 跨实例广播
**全阶段目标**
- **批次 0等待期**:复审 02 文档 + 回写 ISSUE-053/055/056/058 裁决 + 提请 ISSUE-001~007 仲裁
- **批次 4P513 天)**:完整实现 /internal/push + WebSocket 生命周期 + Redis Pub/Sub + Kafka 消费 + /readyz + /metrics + OTel + 多阶段 Dockerfile + slog + shared-go 接入 + 集成测试
- **批次 5P6 硬化5 天)**Reconnect 协议 + Redis Stream 持久化 + 测试覆盖率 ≥ 80% + ADR/非功能性需求/失败模式章节补全
**关键路径依赖**
- 批次 0.14shared-go 包骨架coord—— tracer/logger/jwks/env 4 模块
- 批次 1iam P2.1JWT RS256 + JWKS 端点)—— WebSocket 鉴权前置
- 批次 4msg gRPC 50056 + edu.notification.requested topic —— 推送事件来源
---
## §2 全阶段甘特图(P2-P6各 AI 自行细化
## §2 全阶段甘特图(批次 0 + 批次 4 + 批次 5
```mermaid
gantt
title ai02 push-gateway 全阶段排期
title ai02 push-gateway 全阶段排期(批次 0 + 批次 4 P5 + 批次 5 P6
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a2a, 2026-07-10, Xd
section 批次0 等待期 2天
0.1 复审01/02文档+核查已有仲裁 :crit, a0a, 2026-07-10, 1d
0.2 回写ISSUE-053/055/056/058到02文档 :crit, a0b, after a0a, 1d
0.3 提请ISSUE-001~007待coord仲裁 :a0c, after a0a, 1d
section 批次4 P5-P0 安全加固 3天
4.1 Origin校验+CheckOrigin白名单 :crit, a4a, after b3a, 1d
4.2 /internal/*鉴权对齐X-Internal-Token :crit, a4b, after a4a, 1d
4.3 心跳改WebSocket控制帧+SetReadDeadline :crit, a4c, after a4a, 1d
4.4 单用户连接数限制MaxConn=5 :crit, a4d, after a4c, 1d
4.5 Send通道满时指标+日志 :a4e, after a4d, 1d
section 批次4 P5-P0 基础设施 2天
4.6 Dockerfile重构多阶段+非root+healthcheck+ldflags :crit, a4f, after b3a, 1d
4.7 引入log/slog替换标准log :a4g, after a4f, 1d
4.8 接入shared-go tracer/logger/jwks/env :a4h, after a4g, 1d
section 批次4 P5-P1 横向扩展 3天
4.9 Redis Pub/Sub跨实例广播 :crit, a4i, after a4h, 2d
4.10 Redis SET在线状态+启动重建(ISSUE-058) :crit, a4j, after a4i, 1d
4.11 /metrics自定义指标+/readyz软失败(ISSUE-055) :a4k, after a4j, 1d
4.12 优雅关闭所有WebSocket连接 :a4l, after a4k, 1d
section 批次4 P5-P2 高级功能 3天
4.13 Kafka消费edu.notification.requested(ISSUE-053) :a4m, after a4j, 2d
4.14 JWT RS256升级+JWKS fetcher :a4n, after a4m, 1d
4.15 设计决策记录章节回写(ISSUE-056) :a4o, after a4n, 1d
section 批次4 P5-P2 集成 2天
4.16 与msg联调/internal/push双通道 :crit, a4p, after a4o, 1d
4.17 集成测试+端到端验证 :crit, a4q, after a4p, 1d
section 批次5 P6 硬化 5天
5.1 Reconnect协议session_id+last_seq :a5a, after a4q, 2d
5.2 Redis Stream替代Pub/Sub持久化 :a5b, after a5a, 2d
5.3 测试覆盖率≥80% :crit, a5c, after a4q, 3d
5.4 ADR+非功能性需求+失败模式章节(ISSUE-007) :a5d, after a4q, 2d
```
> **注意**:以上为 coord 初始规划ai02 接管后必须自行细化为完整 P2-P6 排期。
> **依赖锚点**`b3a` = 批次 3 完成信号content + data-ana 就绪,见 [workline.md](../workline.md) §1 批次 3
> **总工期**:批次 02 天)+ 批次 413 天)+ 批次 55 天)= **20 天**
---
## §3 详细任务
### 全阶段任务
### 3.1 批次 0等待期2 天)
#### 任务 0.1:复审 01/02 文档 + 核查已有仲裁
- **负责人**ai02
- **交付物**:⚠️ 由 ai02 自行补充
- **依赖**:见 [contracts/push-gateway_contract.md](../contracts/push-gateway_contract.md)
- **验收标准**:⚠️ 由 ai02 自行补充
- **依赖**:无
- **交付物**
- [objections/push-gateway_issue.md](../objections/push-gateway_issue.md) §0 核查矩阵
- 01/02 文档审查结论(已汇报给用户)
- **验收标准**
- 5 项已有仲裁ISSUE-053/055/056/058 + ARB /internal/push核查完成
- 01 文档 7 项偏差登记
- 02 文档 4 项未回写 + 3 项规范缺失登记
#### 任务 0.2:回写 ISSUE-053/055/056/058 到 02 文档
- **负责人**ai02
- **依赖**:任务 0.1
- **交付物**02-architecture-design.md 修订
- §5.1 topic 改为 `edu.notification.requested`ISSUE-053
- §6.7 增 Kafka 软失败逻辑 + Redis 软失败ISSUE-055/058待 ISSUE-006 仲裁最终策略)
- 新增 §5.4"设计决策记录gRPC vs HTTP 协议选型coord 已采纳 P1"ISSUE-056
- §3.1/§8.4 补 Hub 启动 Redis SET 重建 + 60s 不一致窗口文档化 + `push_gateway_redis_set_rebuild_total` 指标ISSUE-058
- **验收标准**4 项裁决全部回写,[objections/push-gateway_issue.md](../objections/push-gateway_issue.md) §0 核查矩阵状态更新为 ✅
#### 任务 0.3:提请 ISSUE-001~007 待 coord 仲裁
- **负责人**ai02
- **依赖**:任务 0.1
- **交付物**[objections/push-gateway_issue.md](../objections/push-gateway_issue.md) §1 七项 issue
- **验收标准**coord 在 [coord.md](../coord.md) 追加 ARB-003+ 仲裁章节
### 3.2 批次 4P5完整实现13 天)
#### 任务 4.1Origin 校验 + CheckOrigin 白名单P01 天)
- **负责人**ai02
- **依赖**:批次 3 完成信号 `b3a`
- **交付物**[internal/ws/handler.go](../../../services/push-gateway/internal/ws/handler.go) 修订
- `upgrader.CheckOrigin``return true` 改为读 `WS_ALLOWED_ORIGINS` 环境变量白名单
- 无 Origin 头拒绝
- **验收标准**
- 非白名单 Origin 返 403
- 白名单来源teacher/student/parent portal 域名)通过
#### 任务 4.2/internal/* 鉴权对齐 X-Internal-TokenP01 天)
- **负责人**ai02
- **依赖**:任务 4.1 + ISSUE-002 仲裁结果
- **交付物**
- [internal/ws/handler.go](../../../services/push-gateway/internal/ws/handler.go) `internalAPIKeyHeader` 改为 `X-Internal-Token`(待仲裁确认)
- [internal/config/config.go](../../../services/push-gateway/internal/config/config.go) `InternalAPIKey``InternalAPIToken`,环境变量 `INTERNAL_API_TOKEN`
- 错误码对齐 `PUSH_UNAUTHORIZED`
- **验收标准**:无 token/错 token 返 401 + `PUSH_UNAUTHORIZED`DevMode 跳过
#### 任务 4.3:心跳改用 WebSocket 控制帧 + SetReadDeadlineP01 天)
- **负责人**ai02
- **依赖**:任务 4.1
- **交付物**[internal/ws/handler.go](../../../services/push-gateway/internal/ws/handler.go) 重构
- 移除文本消息 `ping/pong` 逻辑([handler.go#L82-L84](../../../services/push-gateway/internal/ws/handler.go#L82-L84)
- 改用 `conn.SetReadDeadline(60s)` + `conn.SetPongHandler`
- 客户端 Ping 控制帧 → gorilla 自动回 Pong
- 60s 无任何消息则关闭连接
- **验收标准**
- 僵尸连接 60s 后自动清理
- 心跳走 RFC 6455 控制帧,不再走文本消息
#### 任务 4.4:单用户连接数限制 MaxConn=5P01 天)
- **负责人**ai02
- **依赖**:任务 4.3
- **交付物**[internal/hub/hub.go](../../../services/push-gateway/internal/hub/hub.go) 修订
- Hub 增加 `counters map[string]int`02 文档 §2
- `Register` 检查 `counters[userID] >= 5``ErrTooManyConnections`
- 超限返 close 帧code=1008 policy violation
- **验收标准**:第 6 个连接被拒,错误码 `PUSH_TOO_MANY_CONNECTIONS` 429
#### 任务 4.5Send 通道满时指标 + 日志P01 天)
- **负责人**ai02
- **依赖**:任务 4.4
- **交付物**[internal/hub/hub.go](../../../services/push-gateway/internal/hub/hub.go) `Send` 方法修订
- 通道满时 `slog.Warn` + `messages_dropped_total` Counter
- **验收标准**`/metrics` 暴露 `push_gateway_messages_dropped_total{reason="channel_full"}`
#### 任务 4.6Dockerfile 重构P01 天)
- **负责人**ai02
- **依赖**:批次 3 完成信号 `b3a`
- **交付物**[Dockerfile](../../../services/push-gateway/Dockerfile) 重构
- 多阶段(已有,保留)
- 非 root 用户(`adduser -D appuser` + `USER appuser`
- healthcheck`wget --spider http://localhost:8081/healthz`
- ldflags 优化(`-ldflags="-s -w -X main.Version=$(git rev-parse --short HEAD)"`
- go.mod 与 Dockerfile 版本对齐1.25.0 vs golang:1.22-alpine
- **验收标准**`docker build` 通过,容器以非 root 运行healthcheck 工作
#### 任务 4.7:引入 log/slog 替换标准 logP01 天)
- **负责人**ai02
- **依赖**:任务 4.6
- **交付物**:新增 `internal/observability/logger.go`
- `slog.NewJSONHandler` + `slog.SetDefault`
- 日志字段:`timestamp` `level` `service=push-gateway` `request_id` `trace_id` `user_id` `conn_id` `event`
- main.go / handler.go / hub.go 替换所有 `log.Printf``slog`
- **验收标准**:日志输出 JSON 格式,包含 trace_id 字段
#### 任务 4.8:接入 shared-goP01 天)
- **负责人**ai02
- **依赖**:任务 4.7 + 批次 0.14shared-go 骨架)
- **交付物**
- go.mod 引入 `github.com/edu-cloud/shared-go`
- 替换本地 [observability/tracer.go](../../../services/push-gateway/internal/observability/tracer.go) 为 `shared-go/observability/tracer`
- 引入 `shared-go/observability/logger`(替换任务 4.7 本地实现)
- 引入 `shared-go/config/env`(替换 [config.go](../../../services/push-gateway/internal/config/config.go) `getEnv`
- 引入 `shared-go/auth/jwks`(任务 4.14 使用)
- **验收标准**:本地 tracer.go/logger.go 删除,统一从 shared-go import
#### 任务 4.9Redis Pub/Sub 跨实例广播P12 天)
- **负责人**ai02
- **依赖**:任务 4.8
- **交付物**:新增 `internal/redis/pubsub.go` + Hub 改造
- 订阅 `edu.push.channel.user.*` + `edu.push.channel.broadcast`
- 本实例无目标用户时 PUBLISH 到对应 channel
- 持有该用户的实例订阅后投递到本地连接
- 引入 `github.com/redis/go-redis/v9` 依赖
- **验收标准**
- 双实例部署msg 调实例 A `/internal/push user=B`,实例 B 持有 B → 收到推送
- 广播 PUBLISH 一次,所有实例投递本地连接
#### 任务 4.10Redis SET 在线状态 + 启动重建P11 天)
- **负责人**ai02
- **依赖**:任务 4.9
- **交付物**Hub 改造(对齐 ISSUE-058
- 连接建立:`SADD edu:push:online:<userID> <instanceID>` + `EXPIRE 60s`
- 心跳续期:`EXPIRE 60s`
- 连接断开:`SREM` + 空 SET 则 `DEL`
- **Hub 启动重建**:遍历内存连接 SADD + EXPIRE先清空 Redis 中本 instanceID 旧成员(避免幽灵)
- 实例崩溃 SET 自然过期60s
- **验收标准**
- 实例重启后 60s 内 Redis SET 重建完成
- `push_gateway_redis_set_rebuild_total` 指标暴露
#### 任务 4.11/metrics 自定义指标 + /readyz 软失败P11 天)
- **负责人**ai02
- **依赖**:任务 4.10
- **交付物**:新增 `internal/observability/metrics.go` + /readyz 重构
- 指标清单02 文档 §6.4`active_connections` `messages_pushed_total` `messages_dropped_total` `heartbeat_total` `disconnect_total` `redis_pubsub_latency_seconds` `kafka_consumed_total` `redis_set_rebuild_total`
- /readyz 检查 Redis PING + Kafka consumer lag
- Redis/Kafka 软失败ISSUE-055/058待 ISSUE-006 仲裁):返 200 + `degraded: true`
- **验收标准**
- `/metrics` 暴露 8+ 自定义指标
- Redis 故障时 /readyz 返 200 + `degraded: true`(不返 503
#### 任务 4.12:优雅关闭所有 WebSocket 连接P11 天)
- **负责人**ai02
- **依赖**:任务 4.11
- **交付物**[main.go](../../../services/push-gateway/main.go) + Hub 改造
- Hub 新增 `CloseAll()` 方法,向所有连接发 close 帧code=1001 going away
- SIGTERM → 标记 Hub closing拒新连接→ CloseAll → 等 10s → srv.Shutdown → 关 Kafka consumer → 关 Redis subscriber → tracerShutdown
- **验收标准**SIGTERM 后所有连接收到 close 帧,无连接泄漏
#### 任务 4.13Kafka 消费 edu.notification.requestedP22 天)
- **负责人**ai02
- **依赖**:任务 4.10 + msg 就绪信号
- **交付物**:新增 `internal/kafka/consumer.go`
- Consumer Group `push-gateway`
- 订阅 `edu.notification.requested`ISSUE-053 裁决的 topic 名)
- 至少一次 + 重试 3 次入 DLQ
- 幂等:`event_id` Redis SETNX TTL 24h
- 消费 → 调 Hub.SendToUser / Broadcast
- **验收标准**
- msg 发布 `NotificationRequested` → push-gateway 消费 → 推送到在线客户端
- 重复 event_id 不重投
#### 任务 4.14JWT RS256 升级 + JWKS fetcherP21 天)
- **负责人**ai02
- **依赖**:任务 4.8shared-go/jwks+ iam 就绪信号
- **交付物**[internal/ws/handler.go](../../../services/push-gateway/internal/ws/handler.go) `authenticate` 重构
- 移除 HS256 共享密钥校验
- 改用 RS256通过 `shared-go/auth/jwks` 拉取 iam `/.well-known/jwks.json` 公钥
- 缓存公钥 + 定期刷新5 分钟)
- **验收标准**
- iam 签发的 RS256 JWT 通过校验
- 公钥轮换后 5 分钟内生效
#### 任务 4.15设计决策记录章节回写P21 天)
- **负责人**ai02
- **依赖**:任务 4.13
- **交付物**02-architecture-design.md 新增 §5.4ISSUE-056
- 标题:"设计决策记录gRPC vs HTTP 协议选型coord 已采纳 P1"
- 正文标注"coord 已采纳,见 coord-final-decisions P1/P5/P6"
- 记录决策背景、方案对比、采纳理由
- **验收标准**:章节存在且标注正确
#### 任务 4.16:与 msg 联调 /internal/push 双通道P21 天)
- **负责人**ai02
- **依赖**:任务 4.13 + 任务 4.14 + msg 就绪
- **交付物**:联调测试报告
- 定向推送msg → HTTP /internal/push → push-gateway → WebSocket
- 广播msg → Kafka NotificationRequested → push-gateway → 全在线客户端
- 离线场景:`delivered: false, online: false` → msg 走 SMS/邮件
- **验收标准**[matrix.md](../matrix.md) §9.5 推送链路检查清单全通过
#### 任务 4.17:集成测试 + 端到端验证P21 天)
- **负责人**ai02
- **依赖**:任务 4.16
- **交付物**:集成测试用例 + 端到端验证报告
- 单实例 1000 连接压测
- 双实例跨实例推送验证
- Redis 故障降级验证
- Kafka 消费积压验证
- **验收标准**
- [workline.md](../workline.md) §8 push-gateway 行更新为 ✅
- [matrix.md](../matrix.md) §8 push-gateway 就绪信号 ✅
### 3.3 批次 5P6硬化5 天)
#### 任务 5.1Reconnect 协议2 天)
- **负责人**ai02
- **依赖**:批次 4 完成
- **交付物**02 文档 §7.3 落地
- 首次连接返 `{type:"hello", session_id, seq:0}`
- 每条推送带递增 `seq`
- 重连 `/ws?token=&session_id=&last_seq=` → 从 msg 拉取 `last_seq+1` 到当前补推
- **验收标准**:客户端断线 30s 内重连,未送达消息补推成功
#### 任务 5.2Redis Stream 替代 Pub/Sub2 天)
- **负责人**ai02
- **依赖**:任务 5.1
- **交付物**`internal/redis/stream.go`
- Pub/Sub → Redis Stream持久化
- Consumer Group `push-gateway`
- ACK 机制:投递成功后 XACK
- 崩溃恢复:未 ACK 消息重新投递
- **验收标准**实例崩溃时未投递消息不丢失msg 落库兜底 + Stream 持久化双保险)
#### 任务 5.3:测试覆盖率 ≥ 80%3 天,可与 5.1/5.2 并行)
- **负责人**ai02
- **依赖**:批次 4 完成
- **交付物**
- `hub_test.go`:注册/注销/推送/广播/连接数限制
- `ws_test.go`:鉴权/心跳/Origin 校验
- `config_test.go`:环境变量加载
- `redis_test.go`Pub/Sub + SET 重建
- `kafka_test.go`:消费 + 幂等
- **验收标准**`go test -cover ./...` ≥ 80%
#### 任务 5.4ADR + 非功能性需求 + 失败模式章节补全2 天,可与 5.1/5.2 并行)
- **负责人**ai02
- **依赖**:批次 4 完成 + ISSUE-007 仲裁
- **交付物**02-architecture-design.md 新增章节
- §14 ADRArchitecture Decision Records
- ADR-001gorilla/websocket 选型vs nhooyr/websocket
- ADR-002Redis Pub/Sub vs StreamP5 Pub/Sub → P6 Stream 演进)
- ADR-003心跳间隔 30s/60s 选型依据
- ADR-00410w 容量依据goroutine-per-connection 内存估算)
- ADR-005HTTP + Kafka 双通道vs 单通道)
- §15 非功能性需求(可用性 SLO 99.9% / 安全合规 / 容量 SLA
- §16 失败模式(实例崩溃 / Redis 故障 / 网络分区 / Kafka 积压降级)
- §17 容量估算10w 连接内存/CPU/带宽)
- **验收标准**4 章节齐全,符合 arc42 规范
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai02 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai02 自行补充
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供方 | 就绪信号 | 当前状态 | 影响任务 |
| ------ | ------ | -------- | -------- | -------- |
| shared-go 包骨架 | coord批次 0.14 | `packages/shared-go` 含 tracer/logger/jwks/env 4 模块 | ⏳ | 任务 4.8 |
| iam JWT RS256 + JWKS 端点 | ai06批次 1 | iam gRPC 50052 + `/.well-known/jwks.json` 可访问 | ⏳ | 任务 4.14 |
| msg gRPC + Kafka topic | ai10批次 4 | msg gRPC 50056 + `edu.notification.requested` topic 有事件 | ⏳ | 任务 4.13/4.16 |
| Redis 基础设施 | coordP1 | Redis 7.x 可访问 | ✅ P1 已就绪 | 任务 4.9/4.10 |
| Kafka 基础设施 | coordP1 | Kafka 可访问 | ✅ P1 已就绪 | 任务 4.13 |
| ISSUE-001~007 仲裁 | coord | [coord.md](../coord.md) 追加 ARB-003+ | ⏳ | 任务 4.2/4.11/4.15/5.4 |
### 4.2 我的就绪信号(供下游消费)
| 就绪标志 | 验证方式 | 供消费方 |
| -------- | -------- | -------- |
| push-gateway HTTP :8081 启用 | `GET /healthz` 返 200 | k8s 探针 / 监控 |
| /readyz 返 200含 Redis/Kafka 软失败检查) | `GET /readyz` 返 200 + `degraded` 字段 | k8s 探针 |
| WebSocket /ws 端点可升级JWT RS256 鉴权) | 客户端 `ws://host:8081/ws?token=JWT` 建立连接 | teacher-portal / student-portal / parent-portal |
| /internal/push + /internal/broadcast 接收 msg 推送 | msg 调用返 `{success:true, delivered:true}` | msg (ai10) |
| /internal/online/<userID> 查在线状态 | 返 `{online:bool, instances:[]}` | msg (ai10) |
| Kafka consumer `edu.notification.requested` 订阅成功 | Consumer Group `push-gateway` lag=0 | msg (ai10) |
| /metrics 暴露 8+ 自定义指标 | `GET /metrics``push_gateway_*` 指标 | Prometheus |
### 4.3 Mock 策略(开发期间)
**我提供的 mock**push-gateway 未就绪前,供前端 portal
- WebSocket mock前端用 mock-socket 库模拟 WS 连接,每 30s 推 1 条 mock 通知
- HTTP mock/internal/* 返 200 success
**我消费的 mock**(上游未就绪前):
- NotificationEvent mockmsg 未就绪前push-gateway 内置定时器每 30s 生成 mock 事件推所有在线客户端
- JWT 验签 mockiam 未就绪前使用本地固定 RS256 公钥(或 DevMode dev-token
- Kafka 订阅 mockmsg 未就绪前不启动 Kafka consumer用本地定时器替代
---
## §5 风险与缓解
| 风险 | 概率 | 影响 | 缓解措施 |
| ---- | ---- | ---- | -------- |
| ISSUE-001~007 仲裁延迟 | 中 | 阻塞批次 4 启动 | ai02 先按建议方案推进,仲裁结果出来后调整 |
| msg 就绪延迟 | 中 | 阻塞任务 4.13/4.16 | 用 mock NotificationEvent 先完成 Kafka 消费逻辑 |
| 10w 连接压测不达标 | 低 | 容量目标降级 | P6 阶段压测,若不达标则 horizontal scaling 兜底 |
| Redis Pub/Sub 消息丢失 | 中 | 跨实例推送丢失 | msg 落库兜底 + P6 升级 Redis Stream |
| shared-go 接口变更 | 低 | 任务 4.8 返工 | 紧跟 coord 0.14 任务,接口冻结后立即对接 |

View File

@@ -1,45 +1,362 @@
# student-bff 工作排期
> 负责人ai04
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/student-bff_contract.md](../contracts/student-bff_contract.md)、[objections/student-bff_issue.md](../objections/student-bff_issue.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 裁决依据:[coord-final-decisions.md](../../coord-final-decisions.md) §2 B1-B8、[president-final-rulings.md](../../president-final-rulings.md) §2.2/§3.4/§6.1
---
## §1 总览
student-bff 为学生端提供 GraphQL 聚合 API,覆盖 Dashboard、考试作答、作业提交等场景。全阶段目标P2 GraphQL schema 骨架 → P3 Dashboard+考试+作业聚合 → P4-P6 持续优化
student-bff 为学生端提供 **GraphQL 聚合 API**B1 裁决P2 起直接 GraphQL + DataLoader下游通过 **gRPC** 调用业务服务B2 裁决:首次实现即 gRPC复用 teacher-bff 产出的 **DownstreamClient 抽象**B8 裁决)
- **阶段归属**P3 核心教学阶段(批次 2
- **端口**3009HTTP GraphQL endpoint
- **路由前缀**`/student`api-gateway 代理 `/api/v1/student/*` → student-bff:3009
- **schema 存放**`packages/shared-ts/contracts/graphql/student-bff.schema.graphql`president §2.2
- **核心场景**:学生 Dashboard 聚合 + 考试作答 + 作业提交 + 成绩查看
### 1.1 关键裁决对齐
| 裁决 | 结论 | 对齐方式 |
| ---- | ---- | -------- |
| B1 API 风格 | P2 起直接 GraphQLYoga + DataLoader | P3 首次实现即 GraphQL禁止 REST |
| B2 下游通信 | 首次实现即 gRPC | @grpc/grpc-js + @bufbuild/protobuf,禁止 HTTP fetch |
| B3 权限装饰器 | BFF 豁免 @RequirePermission | 仅校验 x-user-id 存在,权限交下游 |
| B4 越权防御 | 全部 BFF 强制 | AuthorizationGuard 强制 userId 比对 |
| B5 错误码前缀 | BFF_STUDENT_ | 统一 BFF_ 前缀 |
| B6 缓存策略 | Redis 5-30s 短缓存 | CacheInterceptor + Redis |
| B7 Kafka 订阅 | P2-P4 不订阅P5 后订阅 | P3/P4 纯同步聚合P5 引入 EventSubscriber |
| B8 DownstreamClient | 回写 teacher-bff3 BFF 统一 | 复用 ai03 P2 产出的抽象 |
---
## §2 全阶段甘特图(P2-P6各 AI 自行细化
## §2 全阶段甘特图(批次 1 等待期 + 批次 2-5
```mermaid
gantt
title ai04 student-bff 全阶段排期
title ai04 student-bff 全阶段排期(对齐总裁 §6.1 批次时间线)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a4a, 2026-07-10, Xd
section 批次1等待期
W0.1 GraphQL schema 第一版起草 :crit, w1, 2026-07-10, 3d
W0.2 01/02 文档回写(B1/B2/B5/B8) :crit, w2, after w1, 3d
W0.3 schema 提交 coord 仲裁 :milestone, w3, after w2, 0d
section 批次2 P3 核心
P3.1 NestJS+GraphQL Yoga 骨架 :crit, p1, after w3, 2d
P3.2 DownstreamClient+gRPC client(iam+core-edu) :crit, p2, after p1, 3d
P3.3 核心 Query Resolver(dashboard/homework/grades/exams) :crit, p3, after p2, 3d
P3.4 Mutation(submitHomework)+AuthorizationGuard(B4) :crit, p4, after p3, 2d
P3.5 DataLoader+N+1防御 :p5, after p4, 1d
P3.6 Redis缓存(B6)+ActionState信封 :p6, after p4, 1d
P3.7 /healthz+/readyz探针(iam+core-edu) :p7, after p6, 1d
P3.8 横切关注点(logger/metrics/tracer/error filter) :p8, after p6, 2d
P3.9 单元测试(覆盖率≥80%) :p9, after p8, 2d
section 批次3 P4 扩展
P4.1 content gRPC client(textbooks/chapters/questions) :p10, after p9, 3d
P4.2 data-ana gRPC client(weakness/trend) :p11, after p10, 2d
P4.3 Query 扩展(myTextbooks/myWeakness/myTrend) :p12, after p11, 2d
P4.4 /readyz 扩展探针(content+data-ana) :p13, after p12, 1d
P4.5 Dashboard Resolver 字段扩展 :p14, after p12, 1d
section 批次4 P5 扩展
P5.1 msg gRPC client(notifications) :p15, after p14, 2d
P5.2 ai gRPC client(chat/streamChat SSE) :p16, after p15, 3d
P5.3 Query/Mutation 扩展(myNotifications/markAsRead/aiChat) :p17, after p16, 2d
P5.4 Kafka EventSubscriber(B7 P5订阅) :p18, after p17, 2d
P5.5 push-gateway 推送通道 :p19, after p18, 2d
P5.6 /readyz 扩展探针(msg+ai) :p20, after p19, 1d
section 批次5 P6 硬化
P6.1 熔断器(opossum)完善 :p21, after p20, 2d
P6.2 HPA+全链路可观测 :p22, after p21, 2d
P6.3 灾备演练+99.9%可用性 :p23, after p22, 3d
```
> **注意**:以上为 coord 初始规划ai04 接管后必须自行细化为完整 P2-P6 排期。
> **关键路径**critschema 起草 → 文档回写 → P3 骨架 → gRPC client → Query Resolver → Mutation+Guard
> **总时间线**:批次 1 等待期 6 天 + 批次 2 P3 约 17 天 + 批次 3 P4 约 9 天 + 批次 4 P5 约 12 天 + 批次 5 P6 约 7 天
---
## §3 详细任务
### 全阶段任务
### 3.1 批次 1 等待期2026-07-10 起,约 6 天)
#### W0.1 GraphQL schema 第一版起草
- **负责人**ai04
- **交付物**:⚠️ 由 ai04 自行补充
- **依赖**:见 [contracts/student-bff_contract.md](../contracts/student-bff_contract.md)
- **验收标准**:⚠️ 由 ai04 自行补充
- **依赖**president §2.2 裁决 ai04 在批次 1 等待期起草)
- **交付物**
- `packages/shared-ts/contracts/graphql/student-bff.schema.graphql` 第一版
- 包含 Query/Mutation 清单 + 类型定义 + 权限点标注(`# @permission:`+ DataScope 标注(`# @dataScope: SELF`
- 分页采用 Relay Cursor Connections 规范president §2.2 #5
- 错误格式GraphQL errors 数组 + `extensions.code` + `extensions.traceId`president §2.2 #3
- **验收标准**
- P3 核心 QuerystudentDashboard / myHomework / myGrades / myExams / myClasses / currentUser
- P3 核心 MutationsubmitHomework
- 提交 coord 仲裁president §2.2coord 在批次 2 启动前仲裁第一版)
- **状态**:⏳ 待办(见 objections ISSUE-STU-004
#### W0.2 01/02 文档回写
- **负责人**ai04
- **依赖**:无
- **交付物**
- `services/student-bff/docs/01-understanding.md` 回写ISSUE-STU-001
- §3.2 REST 端点 → GraphQL Query/Mutation 清单
- §3.1/§4 HTTP fetch → gRPC 下游调用
- §3.3/§6 错误码 `STUDENT_BFF_``BFF_STUDENT_`
- §7.2 删除已裁决的"待仲裁"项
- `services/student-bff/docs/02-architecture-design.md` 回写ISSUE-028-ai04
- §4 21 个 REST 端点 → GraphQL Schema 设计
- §9.2 删除 REST→GraphQL 演进,改为 GraphQL 即起点
- §9.3 删除 HTTP→gRPC 演进,改为 gRPC 首次实现即用
- §8.3 删除 8 项已裁决的"待仲裁"标注ISSUE-STU-003
- 修正 §11.4/§11.5 错误引用ISSUE-STU-002
- 补充 GraphQL Schema / DataLoader / gRPC client / AuthorizationGuard 设计
- **验收标准**:与 coord-final-decisions §2 B1-B8 + president §2.2 完全一致
- **状态**:⏳ 待办
### 3.2 批次 2 P3 核心教学(约 17 天)
#### P3.1 NestJS + GraphQL Yoga 骨架
- **负责人**ai04
- **依赖**:批次 1 完成iam gRPC 50052 + ai03 DownstreamClient 抽象就绪president §6.1
- **交付物**
- `services/student-bff/` 服务骨架(克隆 teacher-bff 结构B8 复用 shared/
- `src/app.module.ts` + `src/main.ts`(端口 3009
- GraphQL Yoga endpoint`POST /graphql`+ Playground开发环境
- `package.json`@edu/student-bff+ `tsconfig.json`NodeNext ESM+ `nest-cli.json`
- `Dockerfile`多阶段构建EXPOSE 3009
- **验收标准**`POST /graphql` 返回 200 + schema 内省可用
- **状态**:⏳ 待办
#### P3.2 DownstreamClient + gRPC clientiam + core-edu
- **负责人**ai04
- **依赖**P3.1 + ai03 teacher-bff P2 产出的 DownstreamClient 抽象B8
- **交付物**
- `src/shared/downstream/downstream-client.ts`(复用 teacher-bff 抽象B8
- gRPC client 配置iam:50052 + core-edu:50053
- `src/config/env.ts`IamGrpcUrl + CoreEduGrpcUrl + 超时/重试参数
- gRPC interceptortraceId 透传 + 错误归一化
- **验收标准**:可调用 iam.GetUserInfo + core-edu.HomeworkService.ListHomeworkByClass
- **状态**:⏳ 待办
#### P3.3 核心 Query Resolver
- **负责人**ai04
- **依赖**P3.2 + coord 仲裁的 schema 第一版
- **交付物**
- `src/student/resolvers/dashboard.resolver.ts`studentDashboard聚合 iam + core-edu
- `src/student/resolvers/homework.resolver.ts`myHomeworkcore-edu
- `src/student/resolvers/grades.resolver.ts`myGradescore-eduB4 强制 userId 比对)
- `src/student/resolvers/exams.resolver.ts`myExamscore-edu
- `src/student/resolvers/classes.resolver.ts`myClassescore-edu
- `src/student/resolvers/auth.resolver.ts`currentUser聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports
- 并行编排Promise.allSettled + 部分降级president §2.6 方案 Bdata 内 degraded 字段)
- **验收标准**5 个核心 Query 可执行,返回 ActionState 信封
- **状态**:⏳ 待办
#### P3.4 MutationsubmitHomework+ AuthorizationGuardB4
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/student/resolvers/homework.mutation.resolver.ts`submitHomework Mutation
- `src/student/guards/authorization.guard.ts`B4 自我越权防御
- 接口:`canAccessOwnData(userId, requestedStudentId): Promise<boolean>`
- P3 实现:强制 `studentId === userId`(学生只能操作自己数据)
- 参照 teacher-bff ISSUE-033-ai03 的 AuthorizationGuard 模式president §2.9
- Zod 输入校验SubmitHomeworkInput schema
- **验收标准**submitHomework 可提交越权请求studentId ≠ userId返回 BFF_STUDENT_FORBIDDEN
- **状态**:⏳ 待办
#### P3.5 DataLoader + N+1 防御
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/student/dataloaders/homework.loader.ts`:批量加载作业
- `src/student/dataloaders/grades.loader.ts`:批量加载成绩
- Dashboard 内多学生场景用 DataLoader 批量去重004 §11.3
- **验收标准**N+1 查询场景下下游 gRPC 调用数 ≤ 2
- **状态**:⏳ 待办
#### P3.6 Redis 缓存B6+ ActionState 信封
- **负责人**ai04
- **依赖**P3.3
- **交付物**
- `src/shared/cache/cache.module.ts`Redis CacheInterceptor
- 缓存 Key 规范:`student:dashboard:{userId}`TTL 5-30sB6
- ActionState 信封:`{success, data, meta?}` / `{success: false, error: {code, message, details?, traceId?}}`
- 降级模式:`data.degraded = true` + `data.degradedReason`president §2.6 方案 B
- **验收标准**:缓存命中时 P50 < 100ms降级响应符合方案 B
- **状态**:⏳ 待办
#### P3.7 /healthz + /readyz 探针
- **负责人**ai04
- **依赖**P3.2
- **交付物**
- `src/shared/health/health.controller.ts`/healthzliveness+ /readyzreadiness
- P3 /readyz 探针iam gRPC 50052 + core-edu gRPC 500532 项president §2.4
- 必需依赖失败返回 503可选依赖软失败返回 200 + degraded
- **验收标准**/readyz 返回 2 项探针状态
- **状态**:⏳ 待办
#### P3.8 横切关注点
- **负责人**ai04
- **依赖**P3.1
- **交付物**
- `src/shared/observability/logger.ts`pinoservice: 'student-bff'
- `src/shared/observability/metrics.ts`prom-client11 个 student_bff_* 指标)
- `src/shared/observability/tracer.ts`OTelserviceName: 'student-bff'
- `src/shared/errors/global-error.filter.ts`@Catch()BFF_STUDENT_* 错误码)
- `src/shared/errors/application-error.ts`(错误类层次)
- 优雅关闭SIGTERM → app.close() → shutdownTracer()
- **验收标准**/metrics 可访问GlobalErrorFilter 捕获所有异常
- **状态**:⏳ 待办
#### P3.9 单元测试
- **负责人**ai04
- **依赖**P3.3-P3.8
- **交付物**
- `test/unit/resolvers/*.test.ts`Resolver 聚合逻辑mock gRPC 下游)
- `test/unit/guards/*.test.ts`AuthorizationGuard 越权防御
- `test/unit/dataloaders/*.test.ts`DataLoader 批量逻辑
- `vitest.config.ts`(对齐 classes 测试框架)
- **验收标准**:覆盖率 ≥ 80%
- **状态**:⏳ 待办
### 3.3 批次 3 P4 内容分析扩展(约 9 天)
#### P4.1-P4.2 content + data-ana gRPC client
- **负责人**ai04
- **依赖**:批次 3 启动content gRPC 50054 + data-ana gRPC 50055 就绪)
- **交付物**
- content gRPC clientTextbookService + ChapterService + QuestionService + KnowledgeGraphService
- data-ana gRPC clientAnalyticsService.GetStudentWeakness + GetLearningTrend
- **验收标准**:可调用 content + data-ana gRPC RPC
- **状态**:⏳ 待办(属"跨阶段扩展例外"president §2.3 允许新增下游 gRPC 调用)
#### P4.3-P4.5 Query 扩展 + 探针扩展 + Dashboard 字段扩展
- **交付物**
- Query 扩展myTextbooks / myChapters / myQuestions / myLearningPath / myWeakness / myTrend
- /readyz 扩展探针:+ content 50054 + data-ana 50055共 4 项)
- Dashboard Resolver 字段扩展null 字段 → 真实 data-ana 数据president §2.3 #4 允许)
- **状态**:⏳ 待办
### 3.4 批次 4 P5 沟通 AI 扩展(约 12 天)
#### P5.1-P5.3 msg + ai gRPC client + Query/Mutation 扩展
- **交付物**
- msg gRPC clientNotificationService.ListNotifications + MarkAsRead
- ai gRPC clientAiService.Chat + StreamChatSSE 流式透传)
- Query/Mutation 扩展myNotifications / markAsRead Mutation / aiChat / aiStreamChat
- **状态**:⏳ 待办
#### P5.4-P5.6 Kafka EventSubscriber + push-gateway + 探针扩展
- **交付物**
- `src/student/events/event-subscriber.ts`Kafka 消费者组B7 P5 才订阅)
- 订阅 topicedu.homework.events / edu.exam.events / edu.grade.events / edu.identity.user.role_changed
- 幂等性Redis SETNX event_id 去重
- push-gateway 推送通道POST /push/user/:userId
- /readyz 扩展探针:+ msg 50056 + ai 50058共 6 项)
- **状态**:⏳ 待办
### 3.5 批次 5 P6 硬化(约 7 天)
#### P6.1-P6.3 熔断器 + HPA + 灾备
- **交付物**
- 熔断器opossum完善每个下游 gRPC client 独立熔断器
- HPA 自动扩缩容配置
- 全链路 trace + Grafana 仪表盘
- 灾备演练 + 99.9% 可用性压测
- **状态**:⏳ 待办
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai04 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai04 自行补充
### 4.1 我依赖的上游就绪标志
| 依赖项 | 提供 AI | 就绪信号 | 阻塞阶段 | 状态 |
| ------ | ------- | -------- | -------- | ---- |
| core_edu.proto 补全AttendanceService / GetClassesByTeacher | coordpresident §2.5 | proto message + RPC 签名定义 | 批次 2 P3 | ⏳ |
| buf.gen.yaml gRPC 插件 | coord批次 0.9 | grpc/node 插件配置 | 批次 2 P3 | ⏳ |
| ai03 DownstreamClient 抽象 | ai03B8 裁决) | teacher-bff P2 产出可复用抽象 | 批次 2 P3 | ⏳ |
| iam gRPC 50052 + 12 RPC | ai06I1 裁决) | HealthService.Check = SERVING | 批次 2 P3 | ⏳ |
| core-edu gRPC 50053 + 22 RPC | ai08C2 裁决) | HealthService.Check = SERVING | 批次 2 P3 | ⏳ |
| content gRPC 50054 + 18 RPC | ai09 | HealthService.Check = SERVING | 批次 3 P4 | ⏳ |
| data-ana gRPC 50055 + 12 RPC | ai11 | HealthService.Check = SERVING | 批次 3 P4 | ⏳ |
| msg gRPC 50056 + 13 RPC | ai10 | HealthService.Check = SERVING | 批次 4 P5 | ⏳ |
| ai gRPC 50058 + 6 RPC | ai12 | HealthService.Check = SERVING | 批次 4 P5 | ⏳ |
| coord 仲裁 student-bff schema 第一版 | coordpresident §2.2 | schema 第一版裁定 | 批次 2 P3 | ⏳ |
| api-gateway `/student` 路由 | ai01 | /api/v1/student/* 可代理 | 批次 2 P3 | ⏳ |
### 4.2 我的就绪标志(供下游消费)
| 就绪标志 | 验证方式 | 消费方 |
| -------- | -------- | ------ |
| student-bff GraphQL :3009 启用 | GET /healthz 返回 200 | k8s / 监控 |
| /readyz 返回 200含下游 gRPC 连通性) | GET /readyz 返回 200 + checks | k8s / 监控 |
| GraphQL schema 可内省 | POST /graphql 返回 schema | ai14student-portal |
| 核心 Query 可执行 | studentDashboard / myHomework / myGrades / myClasses / currentUser | ai14 |
| 核心 Mutation 可执行 | submitHomework | ai14 |
| /metrics 可访问 | GET /metrics 返回 prometheus 格式 | Prometheus |
---
## §5 Mock 策略(全并行开发期间)
### 5.1 我提供的 mock供 ai14 student-portal
在 student-bff 真实就绪前,为 ai14 提供 GraphQL mock
- **方式**MSW 拦截 POST /graphql + 固定 response
- **mock 数据**
- currentUser 返回固定学生id="student-001", name="李同学", roles=["student"]
- studentDashboard 返回固定仪表盘pendingHomework=3, upcomingExams=2, unreadNotifications=5
- myHomework 返回固定 3 个作业1 个待提交)
- myGrades 返回固定 5 个成绩
- myExams 返回固定 2 个考试
- myClasses 返回固定 1 个班级
### 5.2 我消费的 mock上游未就绪前
| 上游 | mock 方式 | 切换真实时机 |
| ---- | --------- | ------------ |
| iam gRPC | grpc-mock 拦截 + 固定 UserInfo/Permissions/Viewports | iam 就绪信号 ✅ |
| core-edu gRPC | grpc-mock 拦截 + 固定 Homework/Exam/Grade/Class | core-edu 就绪信号 ✅ |
| content gRPC | grpc-mock 拦截 + 固定 Textbook/Chapter/Question | content 就绪信号 ✅ |
| data-ana gRPC | grpc-mock 拦截 + 固定 Weakness/Trend | data-ana 就绪信号 ✅ |
| msg gRPC | grpc-mock 拦截 + 固定 Notification | msg 就绪信号 ✅ |
| ai gRPC | grpc-mock 拦截 + 固定 Chat response | ai 就绪信号 ✅ |
> 所有上游 mock 通过 gRPC client 拦截器实现,上游就绪后移除拦截器切换真实调用(对齐 matrix.md §7 全并行 Mock 策略)。
---
## §6 风险与缓解
| 风险 | 概率 | 影响 | 缓解措施 |
| ---- | ---- | ---- | -------- |
| schema 仲裁延迟阻塞 P3 启动 | 中 | 高 | ai04 批次 1 等待期优先产出 schema 草案W0.1 |
| ai03 DownstreamClient 抽象未就绪 | 中 | 高 | ISSUE-007 已识别coord 验收批次 1 时检查 |
| core_edu.proto 补全延迟 | 低 | 高 | president §2.5 已明确 coord 负责 proto 定义 |
| GraphQL + gRPC 首次实现复杂度高 | 中 | 中 | 复用 teacher-bff P2 模式B8 DownstreamClient + Yoga endpoint |
| 下游 gRPC mock 与真实行为不一致 | 中 | 低 | 集成测试阶段统一验证matrix.md §9 |

View File

@@ -1,45 +1,271 @@
# student-portal 工作排期
> 负责人ai14
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/student-portal_contract.md](../contracts/student-portal_contract.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 关联:[workline.md](../workline.md)、[coord.md §1 ARB-001](../coord.md)、[coord.md §2 ARB-002](../coord.md)、[contracts/student-portal_contract.md](../contracts/student-portal_contract.md)、[matrix.md](../matrix.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock最后统一集成测试)
> 依据president-final-rulings.md §3.6ai14 P3 功能范围)+ §7.14ai14 工作内容最终清单)+ ARB-002 §2.3P3 首个 Remote+ 02-architecture-design.md v2 阶段能力累积矩阵
---
## §1 总览
student-portal 是学生端微前端,通过 MF Remote 接入主应用覆盖考试作答、作业提交等场景。全阶段目标P2 MF Remote 骨架 → P3 考试作答+作业提交 → P4-P6 持续优化
student-portal 是学生端微前端MF Remote),通过 Module Federation 接入 teacher-portal Shell,覆盖考试作答、作业提交、学情查看等场景。全阶段目标P2 MF Remote 骨架预埋 → P3 考试作答 + 作业提交 + 基础页面 → P4 知识图谱 + 学情诊断 → P5 实时通知 + AI 辅助 → P6 可观测性硬化 + A11y + 性能
**全并行模式**ai14 一口气完成 P2-P6 全部代码,开发期间用 MSW mock 上游student-bff GraphQL / api-gateway HTTP / push-gateway WebSocket上游就绪后在 [matrix.md](../matrix.md) §8 更新就绪信号,最后统一集成测试。
**批次归属**(见 [workline.md §1](../workline.md)
- 批次 2P3ai14 与 ai07 + ai08 + ai04 + ai03扩展 并行启动(`b2d, after b1d, 8d`
- 批次 3P4与 ai09 + ai11 + ai05 + ai15 并行
- 批次 4P5与 ai10 + ai02 + ai12 + ai03扩展 并行
- 批次 5P6与 ai16 + 持续优化 并行
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
title ai14 student-portal 全阶段排期
title ai14 student-portal 全阶段排期(全并行)
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2-P6
[阶段任务] :a14a, 2026-07-10, Xd
```
section P2 预埋
项目骨架+设计令牌三层+独立壳路由 :crit, a14p0, 2026-07-10, 2d
MF Remote配置(NextFederationPlugin remotes) :crit, a14a, after a14p0, 2d
MSW基础设施+GraphQL请求层骨架 :a14b, after a14a, 2d
> **注意**:以上为 coord 初始规划ai14 接管后必须自行细化为完整 P2-P6 排期。
section P3 学生核心
AppShell复用+学生端导航+路由守卫 :crit, a14c, after a14b, 2d
Dashboard页(studentDashboard query) :a14d, after a14c, 2d
我的班级页+我的考试列表+我的作业列表 :a14e, after a14d, 3d
考试作答页(状态机+服务器时间同步) :crit, a14f, after a14e, 4d
IDB断网恢复队列+自动保存+DraftRecovery :crit, a14g, after a14f, 3d
防作弊采集+BroadcastChannel多标签检测 :a14h, after a14g, 2d
作业提交页(submitHomework mutation+乐观更新) :a14i, after a14h, 2d
我的成绩页+我的考勤页 :a14j, after a14i, 2d
section P4 知识与学情
学习路径页(learningPath query+知识点卡片) :a14k, after a14j, 3d
学情诊断页(myWeakness+myTrend query+图表) :a14l, after a14k, 3d
教材章节浏览页(textbooks+chapters) :a14m, after a14l, 2d
section P5 推送与AI
WebSocket通知中心(myNotifications+markAsRead) :a14n, after a14m, 2d
跨Tab通知同步(BroadcastChannel) :a14o, after a14n, 1d
AI辅助答疑(SSE流式,可选) :a14p, after a14o, 3d
section P6 硬化
Sentry+RUM+OTel browser :a14q, after a14p, 2d
A11y审计(WCAG 2.2 AA) :a14r, after a14q, 2d
性能优化+bundle门禁(Remote <80KB) :a14s, after a14r, 2d
P3未尽事项补全+降级策略验证 :a14t, after a14s, 2d
```
---
## §3 详细任务
### 全阶段任务
### P2MF Remote 骨架预埋
- **负责人**ai14
- **交付物**:⚠️ 由 ai14 自行补充
- **依赖**:见 [contracts/student-portal_contract.md](../contracts/student-portal_contract.md)
- **验收标准**:⚠️ 由 ai14 自行补充
- **裁决依据**ARB-002 §2.3P3 首个 Remote但 P2 可预埋骨架)+ 总裁裁决 §3.6ai14 P3 起步)+ §2.17MF GraphQL client 单例方案 A
- **交付物**
- 项目骨架(`apps/student-portal/` 目录结构:`src/app/``src/components/``src/lib/``src/hooks/``src/mocks/`
- 设计令牌三层primitive.css / semantic.css / tailwind-theme.ts与 teacher-portal Shell 一致)
- 独立壳路由next.config.js + app/layout.tsx + app/page.tsxMF 关闭时可独立渲染)
- NextFederationPlugin 配置(`remotes: { teacher: 'teacher@http://localhost:4000/_next/static/chunks/remoteEntry.js' }``exposes: { './StudentApp': './src/app/student-app.tsx' }`
- MF shared singleton 配置react/react-dom/urql/graphql/@tanstack/react-query/zustand/nuqs/@edu/* 全部 singleton
- MSW 基础设施(`src/mocks/handlers.ts` + `src/mocks/fixtures/*.json` + `NEXT_PUBLIC_API_MOCKING=enabled`
- GraphQL 请求层骨架(`src/lib/graphql.ts` 复用 Shell `useGraphQLClient()`,不重复创建 client
- **依赖**
- packages 骨架ui-tokens/ui-components/hooksai13 批次 0.15 已完成)
- teacher-portal MF Shell 配置ai13 P2exposes 就绪)
- **Mock 策略**MSW 拦截 `POST /api/v1/student/graphql` + `POST /api/auth/login`
- **验收标准**
- `pnpm dev` 启动 :4001 可访问
- MF 配置不破坏独立壳渲染(`NEXT_PUBLIC_MF_ENABLED=false` 时独立渲染首页)
- MSW 拦截 GraphQL 请求返回 mock 数据
- lint + typecheck 零错误
### P3考试作答 + 作业提交 + 基础页面(核心)
- **负责人**ai14
- **裁决依据**:总裁裁决 §3.6ai14 P3 功能范围)+ ARB-001 §1.3ActionState 信封 + 降级模式方案 B+ ARB-002 §2.2(复用 Shell 暴露清单)+ 02-architecture-design.md v2 §14考试作答架构设计
- **交付物**
- **AppShell 复用 + 学生端导航**:从 Shell 导入 `AppShell`覆写学生端视口myClasses/myExams/myHomework/myGrades/myAttendance/learningPath/dashboard/notifications
- **路由守卫**:未登录跳转 `http://localhost:4000/login?redirect=student`,登录后回跳
- **Dashboard 页**`/dashboard`):消费 `studentDashboard` queryupcomingHomework + upcomingExams + recentGrades + attendanceRate + learningStreakDays
- **我的班级页**`/my-classes`):消费 `myClasses` query
- **我的考试列表页**`/my-exams`):消费 `myExams` query按状态分组未开始/进行中/已提交/已批改)
- **我的作业列表页**`/my-homework`):消费 `myHomework` query按状态分组
- **考试作答页**`/my-exams/[id]/take`
- 状态机NotStarted → InProgress → AutoSaving → Submitting → Submitted见 02 §14 状态机图)
- 服务器时间同步(`useServerTimeSync` hook5 分钟重新同步,倒计时基于服务器时间)
- IDB 断网恢复队列(`idb-keyval` 存草稿 + 队列,网络恢复后重试)
- 自动保存(每 30s + blur 事件触发,乐观更新本地状态)
- DraftRecovery 草稿恢复(进入作答页时检查 IDB 草稿,提示恢复)
- 防作弊采集visibilitychange/copy/paste/fullscreen/contextmenu 事件监听 + 记录)
- BroadcastChannel 多标签检测(`edu-exam-session` channel检测到多标签警告
- 提交防重复idempotency key + 提交按钮 disabled + 提交中状态)
- **作业提交页**`/my-homework/[id]/submit`
- 消费 `submitHomework` mutation
- 乐观更新useMutation onMutate 回滚 + invalidateQueries
- 附件上传(待 ISSUE-014-05 仲裁后实现,暂走 mock
- **我的成绩页**`/my-grades`):消费 `myGrades` query成绩列表 + 趋势图
- **我的考勤页**`/my-attendance`):消费 `myAttendance` query考勤日历
- **依赖**
- student-bff GraphQL schemaai04 P3`packages/shared-ts/contracts/graphql/student-bff.graphql`,待 ISSUE-014-02 仲裁)
- api-gateway 路由ai01 P3`/api/v1/student/*` 反向代理 student-bff待 ISSUE-014-01 仲裁)
- core-edu gRPCai08 P3提供 ExamService/HomeworkService/GradeService/AttendanceService/ClassService
- iam gRPCai06 P2GetUserInfo + GetEffectivePermissions + GetViewports
- data-ana gRPCai11 P4但 studentDashboard 聚合需要P3 用 mock
- **Mock 策略**
- MSW 拦截 `POST /api/v1/student/graphql`,按 operationName 返回 mock 响应
- mock-socket 模拟 WebSocket 推送(考试延长/强制提交事件)
- IDB 草稿恢复用真实 idb-keyval前端可独立测试
- **验收标准**
- Dashboard 页渲染mock 数据upcomingHomework + upcomingExams + recentGrades 三栏
- 考试作答页状态机完整:进入 → 作答 → 自动保存 → 提交 → 跳转结果页
- 断网恢复:手动 offline → 作答 → 恢复网络 → 草稿自动提交
- 防作弊采集visibilitychange hidden 触发记录mock 上报)
- 多标签检测:开第二个 Tab 作答,第一个 Tab 收到警告
- 作业提交乐观更新:提交后立即 UI 反馈,失败回滚
- lint + typecheck 零错误
### P4知识图谱 + 学情诊断
- **负责人**ai14
- **交付物**
- **学习路径页**`/learning-path`):消费 `learningPath` query知识点卡片列表 + 掌握度进度条
- **学情诊断页**`/dashboard/weakness`):消费 `myWeakness` query薄弱点雷达图recharts
- **学习趋势页**`/dashboard/trend`):消费 `myTrend` query趋势折线图recharts
- **教材章节浏览页**`/textbooks``/textbooks/[id]/chapters`):消费 `textbooks` + `chapters` query
- **依赖**
- student-bff 扩展 content + data-ana gRPC 调用ai04 P4
- content gRPCai09 P4TextbookService + ChapterService + KnowledgeGraphService
- data-ana gRPCai11 P4AnalyticsService.GetStudentWeakness + GetLearningTrend
- **Mock 策略**MSW 返回固定知识点 + 薄弱点 + 趋势数据
- **验收标准**
- 学习路径页渲染知识点卡片 + 掌握度mock
- 学情诊断页雷达图 + 趋势折线图渲染mock
- 教材章节树形导航可用
### P5实时通知 + AI 辅助
- **负责人**ai14
- **交付物**
- **WebSocket 通知中心**`/notifications`
- 消费 `myNotifications` query + `markAsRead` mutation
- WebSocket 连接 `ws://push-gateway:8081/ws`,实时接收通知
- 通知分类(作业/考试/成绩/系统),未读计数
- **跨 Tab 通知同步**BroadcastChannel `edu-notification` channel新通知在所有 Tab 同步)
- **AI 辅助答疑**(可选,`/ai-tutor`
- SSE 流式接收 AI 回答
- 消费 ai 服务(待 ai12 P5 就绪)
- **依赖**
- push-gateway WebSocketai02 P5`/ws` 端点)
- msg gRPCai10 P5NotificationService
- ai 服务 gRPCai12 P5AiService.Chat可选
- **Mock 策略**mock-socket 模拟 WS 推送(每 30 秒 1 条通知)+ MSW 返回固定 AI 响应SSE 用 ReadableStream mock
- **验收标准**
- 通知中心实时接收 WS 推送mock
- 多 Tab 同步Tab A 收到通知Tab B 未读计数同步更新
- AI 辅助答疑流式输出mock
### P6可观测性硬化 + A11y + 性能
- **负责人**ai14
- **交付物**
- **Sentry 错误追踪**`NEXT_PUBLIC_SENTRY_DSN` + beforeSend PII 过滤,学生隐私合规)
- **Web Vitals RUM**LCP/INP/CLS/TTFB → `/api/v1/admin/web-vitals`
- **OTel browser SDK**(自动埋点 fetch/XHR/document load → OTLP collector
- **A11y 审计**WCAG 2.2 AAeslint-plugin-jsx-a11y + @axe-core/playwright + 对比度审计)
- **性能优化**bundle analyzer + size-limit CI 门禁Remote < 80KB / CSS < 50KB
- **P3 未尽事项补全**(根据 ISSUE-014-03/04/05/06/07 仲裁结果补全防作弊策略、附件上传、实时事件响应、DataScope 强制执行)
- **降级策略验证**02 §18.2 降级策略矩阵的 12 个场景端到端验证)
- **依赖**
- push-gateway WebSocket 真实就绪ai02 P5
- Sentry DSN + OTel collector基础设施
- 全部上游就绪(统一集成测试)
- **验收标准**
- 99.9% 可用 + WCAG 2.2 AA + LCP < 2.5s / INP < 200ms / CLS < 0.1P75
- Remote bundle < 80KB / CSS < 50KB
- 12 个降级场景全部验证通过
---
## §4 依赖与就绪信号
- **我依赖**:⚠️ 由 ai14 自行补充(见 contract.md
- **我的就绪信号**:⚠️ 由 ai14 自行补充
### §4.1 我依赖的上游就绪标志
| 上游 | 就绪标志 | 阻塞阶段 | 状态 |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -------- | ----------- |
| packages 骨架ai13 批次 0.15 | ui-tokens/ui-components/hooks 可 import | P2 启动 | ✅ 已就绪 |
| teacher-portal MF Shellai13 P2 | exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
| api-gateway HTTP :8080ai01 P3 | `/api/v1/student/*` 反向代理 student-bff 可用 | P3 启动 | ⏳ 待 ai01 P3 |
| student-bff GraphQLai04 P3 | `POST /graphql` :3009 + 核心 Query/Mutation 可执行 | P3 启动 | ⏳ 待 ai04 P3 |
| student-bff GraphQL schemaai04 + ISSUE-014-02 仲裁) | `packages/shared-ts/contracts/graphql/student-bff.graphql` 创建 | P3 启动 | ⏳ 待仲裁 |
| core-edu gRPC 50053ai08 P3 | ExamService/HomeworkService/GradeService/AttendanceService/ClassService 全部 RPC | P3 启动 | ⏳ 待 ai08 P3 |
| iam gRPC 50052ai06 P2 | GetUserInfo + GetEffectivePermissions + GetViewports | P3 启动 | ⏳ 待 ai06 P2 |
| student-bff content/data-ana 扩展ai04 P4 | learningPath/myWeakness/myTrend/textbooks/chapters query 可用 | P4 启动 | ⏳ 待 ai04 P4 |
| content gRPC 50054ai09 P4 | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
| data-ana gRPC 50055ai11 P4 | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
| msg gRPC 50056ai10 P5 | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
| ai 服务 gRPC 50057ai12 P5可选 | AiService.ChatSSE 流式) | P5 启动 | ⏳ 待 ai12 P5 |
| Sentry DSN + OTel collector基础设施 | Sentry 项目创建 + OTel collector 可接收 OTLP | P6 启动 | ⏳ 待基础设施 |
### §4.2 我的就绪信号(供下游消费)
| 信号 | 就绪标志 | 消费方 |
| ------------------------------- | --------------------------------------------------------- | ------ |
| student-portal dev server :4001 | `pnpm dev` 启动 + 首页可访问 | 无(最前端,但 teacher-portal Shell 需加载 Remote |
| MF Remote 可加载 | teacher-portal Shell 可加载 `student-portal/StudentApp` | teacher-portalai13 P3 集成测试) |
| 登录流程可用 | 未登录跳转 Shell `/login`,登录后回跳 student | 无 |
| GraphQL 查询可执行 | currentUser/studentDashboard/myClasses 返回数据 | 无 |
| 考试作答链路通 | 进入作答 → 自动保存 → 提交 → 跳转结果页 | 无 |
| WebSocket 通知可接收 | 通知中心实时更新mock | 无 |
---
## §5 全并行开发说明
按 [matrix.md](../matrix.md) §全并行模式:
1. ai14 一口气完成 P2-P6 全部代码,开发期间用 MSW mock 上游
2. 上游就绪后在 matrix.md §8 更新就绪信号(`student-portal | ai14 | :4001 可访问 + MF Remote | ⏳ → ✅`
3. 所有模块就绪后统一集成测试matrix.md §9 检查清单)
4. Mock 切换:`NEXT_PUBLIC_API_MOCKING=enabled``disabled`
5. MF 切换:`NEXT_PUBLIC_MF_ENABLED=false`P2 独立壳)→ `true`P3 接入 Shell
---
## §6 阶段能力累积矩阵(与 02-architecture-design.md v2 §20 对齐)
| 阶段 | 能力 | 关键页面/功能 |
| ---- | --------------------------------------------------- | -------------------------------------------------------------------------- |
| P2 | 项目骨架 + MF Remote 配置 + MSW 基础设施 | 独立壳首页(占位) |
| P3 | + 考试作答 + 作业提交 + 基础页面 | Dashboard / myClasses / myExams / myHomework / myGrades / myAttendance |
| P4 | + 知识图谱 + 学情诊断 + 教材章节 | learningPath / myWeakness / myTrend / textbooks / chapters |
| P5 | + 实时通知 + 跨 Tab 同步 + AI 辅助(可选) | notifications / ai-tutor |
| P6 | + 可观测性 + A11y + 性能 + 降级验证 + 尽事项补全 | Sentry / RUM / OTel / WCAG 2.2 AA / bundle 门禁 |
---
## §7 关键风险与缓解
| 风险 | 影响 | 缓解措施 |
| ---------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------- |
| student-bff GraphQL schema 未就绪ai04 P3 延迟) | 高 | MSW mock 全量 query/mutationschema 就绪后切换ai14 自行维护 mock schema 用于 codegen |
| 考试作答断网恢复逻辑复杂IDB 队列 + 服务器时间对齐)| 高 | 02 §14 已设计完整状态机 + 时间同步算法P3 优先实现核心链路P6 验证降级场景 |
| MF Remote 加载失败Shell 未就绪或版本不兼容) | 中 | 02 §3.2 已设计独立壳回退(`NEXT_PUBLIC_MF_ENABLED=false` 时独立渲染) |
| 防作弊策略未仲裁ISSUE-014-03/04 | 中 | P3 先实现采集 + 本地记录P6 根据仲裁结果补全上报逻辑 |
| 附件上传协议未仲裁ISSUE-014-05 | 中 | P3 先实现文本作业提交,附件上传 P6 根据仲裁结果补全 |
| 实时事件命名未确认ISSUE-014-06 | 低 | P3 不依赖实时事件考试作答页基于本地倒计时P5 根据仲裁结果接入 WebSocket 实时事件 |
---
**AI Agent**: ai14student-portal
**Branch**: feat-review-student-portal-docs-9yN6Av
**Coordinator**: coord-ai

View File

@@ -1,18 +1,27 @@
# teacher-bff 工作排期
> 负责人ai03
> 关联:[workline.md](../workline.md)、[coord.md §1 ARB-001](../coord.md)、[contracts/teacher-bff_contract.md](../contracts/teacher-bff_contract.md)
> 关联:[workline.md](../workline.md)、[coord.md §1 ARB-001](../coord.md)、[contracts/teacher-bff_contract.md](../contracts/teacher-bff_contract.md)、[president-final-rulings.md §3.1/§7.3](../../president-final-rulings.md)、[coord-final-decisions.md §2 B1-B8](../../coord-final-decisions.md)
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
> 裁决依据B1 P2 即 GraphQL / B2 首次实现即 gRPC / B3 豁免 @RequirePermission / B4 越权防御 / B5 BFF_TEACHER_ 前缀 / B6 Redis 短缓存 / B7 P2-P4 不订阅 Kafka / B8 DownstreamClient 抽象
---
## §1 总览
teacher-bff 是教学场景域聚合层,全阶段目标:P2 GraphQL schema 第一版 → P3 扩展 exams/homework/grades → P4 学情分析 → P5 通知+SSE → P6 admin 命名空间。
teacher-bff 是教学场景域聚合层BFF,全阶段目标:
| 阶段 | 核心交付 | 裁决依据 |
| ---- | -------- | -------- |
| P2 | GraphQL schema 第一版5 Query + admin 预留)+ DownstreamClient 抽象 + iam gRPC + AuthorizationGuard + ActionState 信封 | B1/B2/B3/B4/B5/B8 + ARB-001 + §3.1 |
| P3 | core-edu gRPC 扩展exams/homework/grades Query + Mutation+ AuthorizationGuard 接入 core-edu + Redis 聚合缓存 | §2.3 跨阶段扩展例外 |
| P4 | content + data-ana gRPC 扩展(学情分析 Query+ DataLoader 全量接入 | §2.3 + §2.8 |
| P5 | ai + msg gRPC 扩展SSE 流式 + notifications Query+ Kafka consumerpush-gateway 落地后) | B7 |
| P6 | admin 命名空间实现 + 硬化(熔断/重试/超时/HPA/mTLS | §5.1 + P6 硬化 |
---
## §2 全阶段甘特图P2-P6,各 AI 自行细化
## §2 全阶段甘特图P2-P6
```mermaid
gantt
@@ -20,39 +29,307 @@ gantt
dateFormat YYYY-MM-DD
axisFormat %m-%d
section P2 GraphQL 基础
schema第一版(ARB-001) :crit, a3a, 2026-07-10, 2d
section P2 GraphQL 基础8d
schema第一版+admin预留(ARB-001) :crit, a3a, 2026-07-10, 2d
Yoga endpoint+5 Query Resolver :crit, a3b, after a3a, 3d
DataLoader+N+1防御 :a3c, after a3b, 1d
ActionState信封+降级模式B :a3d, after a3b, 1d
DownstreamClient+iam gRPC :crit, a3c, after a3a, 2d
AuthorizationGuard越权防御(B4) :crit, a3d, after a3b, 1d
ActionState信封+降级模式B :a3e, after a3b, 1d
/readyz探针注册表+iam探针 :a3f, after a3d, 1d
错误码迁移BFF_TEACHER_+回写02 :a3g, after a3e, 1d
section P3-P6 扩展
exams/homework/grades Query :a3e, after a3d, 3d
学情分析+通知+SSE :a3f, after a3e, 4d
admin命名空间 :a3g, after a3f, 2d
section P3 core-edu 扩展5d
CoreEduClient gRPC(exams/homework/grades) :crit, a3h, after a3g, 2d
Mutation(createExam/assignHomework/recordGrade) :a3i, after a3h, 1d
AuthorizationGuard接入core-edu+Redis缓存 :a3j, after a3h, 1d
/readyz+core-edu探针 :a3k, after a3j, 1d
section P4 content+data-ana 扩展4d
ContentClient+DataAnaClient gRPC :a3l, after a3k, 2d
学情分析Query+DataLoader全量 :a3m, after a3l, 1d
/readyz+content+data-ana探针 :a3n, after a3m, 1d
section P5 ai+msg 扩展8d
AiClient+MsgClient gRPC :a3o, after a3n, 2d
SSE流式透传+notifications Query :a3p, after a3o, 2d
Kafka consumer(push-gateway落地后) :a3q, after a3p, 2d
/readyz+ai+msg探针 :a3r, after a3q, 1d
Should Have补全(OTel/metrics/DataLoader/Redis) :a3s, after a3r, 1d
section P6 admin+硬化3d
admin命名空间实现 :a3t, after a3s, 1d
熔断/重试/超时 :a3u, after a3t, 1d
Nice to Have补全(Zod全量/优雅关闭) :a3v, after a3u, 1d
```
> **注意**:以上为 coord 初始规划ai03 接管后必须自行细化为完整 P2-P6 排期
> **总工期**28dP2 8d + P3 5d + P4 4d + P5 8d + P6 3d与 workline.md §1 总时间线对齐批次1 8d + 批次2 5d + 批次4 8d + P4/P6 合并 7d
---
## §3 详细任务
### P2GraphQL schema + 5 Query + DataLoader + ActionState
### P2GraphQL 基础 + DownstreamClient + 越权防御(批次 18d
> 裁决依据president §3.1 Must Have 13 项teacher-bff 无 DBG10/G11 不适用,实际 11 项 + DownstreamClient + admin 预留)
#### 3.1 GraphQL schema 第一版 + admin 预留2dP0 阻塞 ai13
- **负责人**ai03
- **依赖**coord 仲裁 ARB-001已裁决
- **交付物**
- `packages/shared-ts/contracts/graphql/teacher-bff.graphql` — P2 schema
- Yoga endpoint `POST /graphql`
- 5 Querydashboard / viewports / me / classes / class
- DataLoader + ActionState 信封 + 降级模式 B
- **依赖**iam gRPCai06+ coord 仲裁 ARB-001
- **验收标准**5 Query 可用 + ActionState 信封 + depth ≤ 7
- **完整 P3-P6 任务**:⚠️ 由 ai03 自行补充
- `packages/shared-ts/contracts/graphql/teacher-bff.schema.graphql` — P2 schema5 Query: dashboard/viewports/me/classes/class + admin 命名空间占位)
- admin 命名空间预留schema 中声明 `admin` Query/Mutation 类型骨架(无实际 ResolverP6 实现
- **验收标准**5 Query SDL 定义完整 + admin 占位类型声明 + depth ≤ 7 + cost ≤ 1000
- **裁决引用**ARB-001 §1.2/§1.3 + president §5.1/§2.17
#### 3.2 Yoga endpoint + 5 Query Resolver3dP0 阻塞 ai13
- **负责人**ai03
- **依赖**3.1 schema + iam gRPC 50052ai06 P2.1
- **交付物**
- `POST /graphql` Yoga GraphQL endpoint
- 5 Query Resolverdashboard / viewports / me / classes / class
- Dashboard Resolver P2 实现方式ISSUE-032P2 仅调 iam gRPC未启用字段返回 null + `extensions.warning = "field_unavailable_in_p2"`
- **验收标准**5 Query 可执行 + ActionState 信封 + 降级模式 Bsuccess=true + error=null + data 内 degraded 字段)
- **裁决引用**B1 + ARB-001 §1.4 + president §2.6/§2.8
#### 3.3 DownstreamClient 抽象 + iam gRPC2dP0 阻塞 ai04/ai05
- **负责人**ai03
- **依赖**iam gRPC 50052ai06 P2.1
- **交付物**
- `src/clients/` DownstreamClient 抽象层B8BFF 模式 v2 标准抽象3 个 BFF 统一使用,回写 teacher-bff
- IamClient gRPC 实现:`@grpc/grpc-js` + `@bufbuild/protobuf`,调 iam:50052
- gRPC interceptor注入 trace contexttraceparent+ x-user-id metadata + metrics
- **验收标准**IamClient gRPC 调 iam GetUserInfo/GetViewports/GetEffectiveAccess 成功
- **裁决引用**B2首次实现即 gRPC+ B8DownstreamClient 抽象)
#### 3.4 AuthorizationGuard 越权防御1dP0
- **负责人**ai03
- **依赖**3.2 Resolver
- **交付物**
- `src/middleware/authorization.guard.ts` — AuthorizationGuard 接口(`canAccessClass(userId, classId): Promise<boolean>`
- P2 内部实现DEV_MODE 放行 + 生产拒绝(保守策略)
- 错误码:`BFF_TEACHER_FORBIDDEN_RESOURCE`teacherId 与资源无归属)+ `BFF_TEACHER_IDENTITY_MISMATCH`JWT teacherId 与 body 不一致)
- **验收标准**Guard 接口定义 + DEV_MODE 放行 + 生产拒绝 + 2 个越权错误码
- **裁决引用**B4 + president §2.7(错误码语义)+ §2.9(越权防御 P2 实现)
#### 3.5 ActionState 信封 + 降级模式 B1d
- **负责人**ai03
- **依赖**3.2 Resolver
- **交付物**GlobalErrorFilter + ActionState 信封success/errors/data+ 降级模式 B
- **验收标准**GraphQL errors 数组扩展 ActionState 字段,`extensions.code = BFF_TEACHER_*`
- **裁决引用**G8 + president §2.6
#### 3.6 /readyz 探针注册表 + iam 探针1d
- **负责人**ai03
- **依赖**3.3 IamClient
- **交付物**
- `src/shared/health/readiness.probe.ts` — DownstreamHealthCheck 注册表模式
- P2 探针Redis + iam gRPC 50052teacher-bff 无 DB2 项)
- **验收标准**/readyz 返回 2 项检查结果 + 必需依赖失败返回 503
- **裁决引用**G2 + president §2.4(探针按阶段扩展)
#### 3.7 错误码迁移 + 回写 02 文档1d
- **负责人**ai03
- **依赖**3.2-3.6
- **交付物**
- `application-error.ts` 全量迁移 `TEACHER_BFF_*``BFF_TEACHER_*`
- 回写 02 文档4 处 `GetTeacherDashboardStats``GetTeacherDashboard`ISSUE-035+ B1/B2/B4/B8 裁决对齐
- **验收标准**:源码零 `TEACHER_BFF_*` + 02 文档与裁决一致
- **裁决引用**B5 + G14 + president §3.4 回写义务
#### P2 横切项(贯穿 3.1-3.7
| 项 | 状态 | 说明 |
| -- | ---- | ---- |
| pino 结构化日志G4 | ✅ 已具备 | logger.ts |
| /healthz livenessG3 | ✅ 已具备 | health.controller.ts |
| GlobalErrorFilterG8 | ✅ 已具备 | global-error.filter.ts |
| ESM import .js 后缀G12 | ✅ 已具备 | 源码已用 .js |
| import typeG13 | ✅ 已具备 | 源码已用 import type |
| OTel tracerG6 | ⚠️ Should Have | P2 可降级为 logger-onlyP5 补全 |
| /metrics 业务指标G5 | ⚠️ Should Have | P2 仅暴露 process metrics |
| DataLoaderB1 | ⚠️ Should Have | P2 可先用普通 resolverP4 全量接入 |
| Redis 5-30s 短缓存B6 | ⚠️ Should Have | P3 接入聚合缓存 |
| Zod 全量验证G7 | ⚠️ Nice to Have | P2 先校验核心 QueryP6 全量 |
| 优雅关闭 SIGTERMG9 | ⚠️ Nice to Have | P2 已有基础P6 补全关闭顺序 |
**P2 退出标准**POST /graphql 可用 + 5 Query Resolver + DownstreamClient + AuthorizationGuard + ActionState 信封 + /readyz 2 项探针 + 错误码 BFF_TEACHER_* + admin 命名空间预留。
---
### P3core-edu gRPC 扩展(批次 25d
> 裁决依据§2.3 跨阶段扩展例外(新增下游 gRPC 调用 + AuthorizationGuard 内部实现替换 + /readyz 探针扩展)
#### 3.8 CoreEduClient gRPC2dP0
- **负责人**ai03
- **依赖**core-edu gRPC 50053ai08 P3
- **交付物**
- CoreEduClient gRPC 实现ExamService / HomeworkService / GradeService
- GraphQL Query 扩展exams(classId) / homework(classId) / grades(examId)
- Dashboard Resolver 扩展null 字段替换为 core-edu 真实数据
- **验收标准**3 个 Query 返回 core-edu 数据 + Dashboard null 字段消除
- **裁决引用**§2.3 跨阶段扩展例外 + §2.8 Dashboard Resolver 扩展
#### 3.9 Mutation 透传1d
- **交付物**createExam / assignHomework / recordGrade Mutation透传 core-edu gRPC
- **验收标准**3 个 Mutation 可执行 + 返回 ActionState 信封
#### 3.10 AuthorizationGuard 接入 core-edu + Redis 缓存1d
- **交付物**
- AuthorizationGuard 内部实现替换DEV_MODE 放行 → 真实 gRPC 校验
- Redis 缓存:`GetClassesByTeacher` 结果缓存key: `authz:teacher:{teacherId}:classes`TTL 5min
- **验收标准**:生产环境越权防御生效 + Redis 缓存命中
- **裁决引用**president §2.9P3 接入 core-edu 后替换 Guard 实现)
#### 3.11 /readyz + core-edu 探针1d
- **交付物**/readyz 探针注册表扩展 core-edu gRPC 50053 探针3 项Redis + iam + core-edu
- **验收标准**/readyz 返回 3 项检查结果
**P3 退出标准**core-edu gRPC 3 Query + 3 Mutation + AuthorizationGuard 生产生效 + Redis 缓存 + /readyz 3 项探针。
---
### P4content + data-ana gRPC 扩展4d
> 裁决依据§2.3 跨阶段扩展例外
#### 3.12 ContentClient + DataAnaClient gRPC2d
- **依赖**content gRPC 50054ai09 P4+ data-ana gRPC 50055ai11 P4
- **交付物**
- ContentClient gRPCKnowledgeGraphServiceGetPrerequisites / GetLearningPath
- DataAnaClient gRPCAnalyticsServiceGetClassPerformance / GetStudentWeakness / GetLearningTrend / GetTeacherDashboard
- GraphQL Query 扩展knowledgePath / classPerformance / studentWeakness / learningTrend / teacherDashboard
- **验收标准**5 个 Query 返回真实数据
#### 3.13 DataLoader 全量接入1d
- **交付物**DataLoader 覆盖全部 N+1 风险点UserLoader / ClassLoader / ExamLoader / HomeworkLoader / GradeLoader
- **验收标准**DataLoader per-request 实例 + 批量化窗口 16ms
- **裁决引用**B1 Should Have → P4 全量接入
#### 3.14 /readyz + content + data-ana 探针1d
- **交付物**/readyz 探针扩展 content + data-ana5 项Redis + iam + core-edu + content + data-ana
**P4 退出标准**content + data-ana gRPC + 5 Query + DataLoader 全量 + /readyz 5 项探针。
---
### P5ai + msg gRPC 扩展 + SSE + Kafka批次 48d
> 裁决依据B7P5 push-gateway 落地后再订阅 Kafka
#### 3.15 AiClient + MsgClient gRPC2d
- **依赖**ai gRPC 50057ai12 P5+ msg gRPC 50056ai10 P5
- **交付物**
- AiClient gRPCAiServiceChat / StreamChat streaming / GenerateQuestion / OptimizeExpression
- MsgClient gRPCNotificationServiceListNotifications / SearchNotifications / MarkAsRead
- GraphQL Query 扩展notifications + MutationgenerateQuestion / markNotificationAsRead
#### 3.16 SSE 流式透传2d
- **交付物**`GET /ai/chat/stream` SSE 端点ai.StreamChat gRPC stream → BFF → 前端 EventSource
- **验收标准**SSE 三层透传端到端通 + 背压处理 + 超时取消
- **裁决引用**02 文档 §10
#### 3.17 Kafka consumer2dpush-gateway 落地后)
- **交付物**
- Kafka consumer 订阅 `edu.identity.user.role_changed` / `edu.identity.role.updated`,精确失效 Redis 权限缓存
- 幂等性:基于 event_id 去重Redis SETNX
- **验收标准**:权限变更秒级缓存失效 + 幂等消费
- **裁决引用**B7P5 push-gateway 落地后再订阅)
#### 3.18 /readyz + ai + msg 探针 + Should Have 补全2d
- **交付物**
- /readyz 探针扩展 ai + msg7 项Redis + iam + core-edu + content + data-ana + ai + msg
- Should Have 补全OTel tracer 全链路 + /metrics 业务指标 + Redis 聚合缓存 5-30s
- **验收标准**/readyz 7 项 + OTel 全链路 trace + 缓存命中率 ≥ 60%
**P5 退出标准**ai + msg gRPC + SSE 流式 + notifications Query + Kafka consumer + /readyz 7 项探针 + Should Have 补全。
---
### P6admin 命名空间 + 硬化3d
> 裁决依据president §5.1admin-portal 复用 teacher-bff+ P6 硬化
#### 3.19 admin 命名空间实现1d
- **依赖**admin-portalai16 P6
- **交付物**
- admin schema 命名空间 Resolver 实现P2 预留的占位类型填充实际 Resolver
- admin Query/Mutation用户管理 / 角色权限管理 / 学校设置 / 组织管理 / 审计日志查询
- **验收标准**admin namespace 可内省 + admin-portal 可消费
- **裁决引用**president §5.1admin-portal 复用 teacher-bff GraphQL endpoint
#### 3.20 熔断 / 重试 / 超时1d
- **交付物**
- Circuit Breakeropossumper-downstream-service
- RetrygRPC interceptor仅幂等 RPC指数退避
- Timeoutper-RPC 3s聚合总超时 5s
- **验收标准**:熔断/重试/超时生效 + 降级策略覆盖
#### 3.21 Nice to Have 补全1d
- **交付物**Zod 全量验证 + 优雅关闭顺序HTTP→Redis→gRPC→Kafka→Tracer+ 测试覆盖率 ≥ 80%
- **验收标准**Zod 全 Controller 覆盖 + SIGTERM 顺序关闭 + Vitest 覆盖率 ≥ 80%
**P6 退出标准**admin namespace 实现 + 熔断/重试/超时 + Zod 全量 + 测试 ≥ 80% + SLO 99.9%。
---
## §4 依赖与就绪信号
- **我依赖**iam gRPC 50052ai06+ core-edu gRPC 50053ai08P3+
- **我的就绪信号**POST /graphql 可用 + dashboard Query 返回正确数据
### 4.1 我依赖的上游就绪信号
| 上游 | 就绪信号 | 阶段 | 状态 |
| ---- | -------- | ---- | ---- |
| iamai06 | gRPC 50052 + 8 RPCGetUserInfo/GetViewports/GetEffectivePermissions/GetEffectiveAccess/Logout/GetPublicKey/BatchGetUsers/GetChildrenByParent | P2 | ⏳ |
| core-eduai08 | gRPC 50053 + ExamService/HomeworkService/GradeService | P3 | ⏳ |
| contentai09 | gRPC 50054 + KnowledgeGraphService | P4 | ⏳ |
| data-anaai11 | gRPC 50055 + AnalyticsService含 GetTeacherDashboardISSUE-027 补全) | P4 | ⏳ |
| aiai12 | gRPC 50057 + AiService含 StreamChat | P5 | ⏳ |
| msgai10 | gRPC 50056 + NotificationService | P5 | ⏳ |
| push-gatewayai02 | /internal/push 落地B7 Kafka 订阅前提) | P5 | ⏳ |
| admin-portalai16 | admin schema 需求确认 | P6 | ⏳ |
### 4.2 我的就绪信号(供下游消费)
| 阶段 | 就绪信号 | 消费方 |
| ---- | -------- | ------ |
| P2 | POST /graphql 可用 + 5 Query + admin 预留 | teacher-portalai13 |
| P2 | DownstreamClient 抽象B8 回写) | student-bffai04/ parent-bffai05 |
| P3 | exams/homework/grades Query + Mutation | teacher-portalai13 |
| P4 | 学情分析 Query + DataLoader | teacher-portalai13 |
| P5 | SSE 流式 + notifications Query | teacher-portalai13 |
| P6 | admin namespace 可用 | admin-portalai16 |
---
## §5 跨阶段扩展例外验收清单
> 裁决依据president §2.3ISSUE-020。每次跨阶段扩展时对照检查。
- [ ] 扩展时更新 02-architecture-design.md 下游调用矩阵
- [ ] 扩展时更新 packages/shared-ts/contracts/graphql/teacher-bff.schema.graphql
- [ ] 扩展时运行 `pnpm run arch:scan` 更新 arch.db
- [ ] 未修改已有 RPC 调用签名或返回类型
- [ ] 未删除已实现的 RPC 调用
- [ ] 未修改 GraphQL schema 已有字段类型(仅新增字段)
- [ ] 未修改 /readyz 已有探针检查项(仅新增)

View File

@@ -74,7 +74,7 @@ gantt
- 学生列表页classStudents GraphQL queryiam 数据)
- 个人设置页currentUser + updateUser GraphQL query/mutation
- **依赖**teacher-bff GraphQL schema 第一版ai03 + coord 仲裁 ISSUE-037+ api-gateway 路由ai01
- **Mock 策略**MSW 拦截 POST /api/teacher/graphql + POST /api/auth/login见 contract.md §4.2
- **Mock 策略**MSW 拦截 POST /api/v1/teacher/graphql + POST /api/auth/login见 contract.md §4.2,对齐 matrix.md §5 路径前缀
- **验收标准**MF 配置不破坏单体 + GraphQL 拉取数据 + 登录→Dashboard→班级列表→学生列表链路通
### P3考试/作业/成绩 + 乐观更新 + 多 Tab 同步
@@ -135,7 +135,7 @@ gantt
| --------------------------------------------------------- | ------------------------------------------------- | -------- | ---------------------- |
| packages 骨架ai13 自建) | ui-tokens/ui-components/hooks 可 import | P2 启动 | ✅ 已就绪(批次 0.15 |
| teacher-bff GraphQL schemaai03 + coord 仲裁 ISSUE-037 | packages/shared-ts/contracts/graphql/ 第一版 | P2 启动 | ⏳ 待 coord 仲裁 |
| api-gateway HTTP :8080ai01 | /api/teacher/graphql + /api/auth/login 路由可用 | P2 启动 | ⏳ 待 ai01 |
| api-gateway HTTP :8080ai01 | /api/v1/teacher/graphql + /api/auth/login 路由可用 | P2 启动 | ⏳ 待 ai01 |
| teacher-bff core-edu 扩展ai03 P3 | classExams/classHomework/studentGrades query 可用 | P3 启动 | ⏳ 待 ai03 P3 |
| teacher-bff content/data-ana 扩展ai03 P4 | knowledgeGraph/studentAnalytics query 可用 | P4 启动 | ⏳ 待 ai03 P4 |
| push-gateway WebSocket :8081/wsai02 P5 | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |