Merge worktree branch merge-15-modules-to-main-5ug5xJ
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
# 模块理解确认书 — teacher-portal
|
||||
|
||||
> AI:ai07(TS/React · 教学场景域前端 shell)
|
||||
> AI:ai13(TS/React · 教学场景域前端 shell)
|
||||
> 阶段:阶段 1 交付物
|
||||
> 日期:2026-07-09
|
||||
> 关联:[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/1.1b/§5.4、[AI 分配方案](../../../docs/architecture/ai-allocation.md) §5 ai13/ai07、[pending-features P2](../../../docs/architecture/roadmap/pending-features.md)、[known-issues §2.12](../../../docs/troubleshooting/known-issues.md)
|
||||
> 日期:2026-07-09(2026-07-10 审查回写:对齐 F9 GraphQL P2 起 + ARB-001/002 仲裁)
|
||||
> 关联:[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §1.1a/1.1b/§5.4、[AI 分配方案](../../../docs/architecture/ai-allocation.md) §5 ai13、[pending-features P2](../../../docs/architecture/roadmap/pending-features.md)、[known-issues §2.12](../../../docs/troubleshooting/known-issues.md)、[coord 仲裁 ARB-001/002](../../../docs/architecture/issues/coord.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -12,16 +12,17 @@
|
||||
- **层级**:L2 微前端层(004 §3.1 六层架构中的前端层)
|
||||
- **MF 角色**:**Shell 宿主**(主应用),其余 3 端(student/parent/admin-portal)作为 Remote 子应用挂载
|
||||
- **上游(谁调用我)**:浏览器(教师 / 教导主任 / 教研组长)
|
||||
- **下游(同步)**:api-gateway(REST,经 Next.js `rewrites` 代理 `/api/v1/*`)
|
||||
- **下游(同步)**:api-gateway(经 Next.js `rewrites` 代理 `/api/v1/*`),再反向代理到 teacher-bff `POST /graphql`
|
||||
- **下游(推送,P5)**:push-gateway(WebSocket/SSE)
|
||||
- **BFF 对接**:teacher-bff(GraphQL Yoga + DataLoader,P2-P3 用 REST 过渡)
|
||||
- **通信方式**:HTTP/REST(前端→Gateway)+ WebSocket(前端→push-gateway,P5)
|
||||
- **BFF 对接**:teacher-bff(GraphQL Yoga + DataLoader,P2 起 all-in GraphQL,F9 裁决,无 REST 过渡)
|
||||
- **通信方式**:GraphQL over HTTP(前端→Gateway→teacher-bff)+ WebSocket(前端→push-gateway,P5)+ SSE(前端→ai 服务,P5)
|
||||
- **不直连**:前端不直连任何业务服务或 BFF 后端实例,全部经 api-gateway 代理
|
||||
|
||||
**说明**:
|
||||
|
||||
- 通过 `next.config.js` 的 `rewrites` 将 `/api/v1/*` 代理到 `api-gateway`
|
||||
- MF 架构下,teacher-portal 作为 Shell 宿主提供 AppShell + 共享组件库 + 权限 Hook + API 请求层
|
||||
- 通过 `next.config.js` 的 `rewrites` 将 `/api/v1/*` 代理到 `api-gateway`,api-gateway 再反向代理到 teacher-bff GraphQL endpoint
|
||||
- F9 裁决:P2 起 BFF 用 GraphQL(Yoga + DataLoader),前端用 urql client,**无 REST 过渡阶段**(详见 [03-long-term-architecture.md §1.4](./03-long-term-architecture.md))
|
||||
- MF 架构下,teacher-portal 作为 Shell 宿主提供 AppShell + GraphQLProvider 单例 + 共享组件库 + 权限 Hook(ARB-002 暴露清单)
|
||||
- 场景域 BFF 复用策略(004 §5.4):教导主任/教研组长复用 teacher-portal + 额外管理视口,不单独建 portal
|
||||
|
||||
## 2. 我的限界上下文
|
||||
@@ -49,19 +50,21 @@
|
||||
|
||||
### 3.1 消费的后端 API(经 api-gateway 代理)
|
||||
|
||||
| 路径前缀 | 下游 BFF/服务 | 关键端点 |
|
||||
| ------------------------------------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/api/v1/iam/*` | iam | `POST /iam/login`、`POST /iam/register`、`POST /iam/refresh`、`GET /iam/me`、`GET /iam/rbac/...`、`GET /iam/effective-permissions` |
|
||||
| `/api/v1/teacher/*` | teacher-bff | `GET /teacher/viewports`、`GET /teacher/dashboard`、`GET /teacher/classes/:id/exams`、`GET /teacher/classes/:id/homework`、`GET /teacher/exams/:id/grades` |
|
||||
| `/api/v1/classes/*` | core-edu(classes 模块) | CRUD(黄金模板) |
|
||||
| `/api/v1/exams/*` `/api/v1/homework/*` `/api/v1/grades/*` | core-edu | P3 教学核心 |
|
||||
| `/api/v1/textbooks/*` `/api/v1/knowledge-points/*` `/api/v1/questions/*` | content | P4 内容 |
|
||||
| `/api/v1/ai/*` | ai(SSE 流式) | P5 AI 辅助出题 |
|
||||
| `/api/v1/notifications/*` | msg | 通知中心(P5) |
|
||||
F9 裁决:P2 起 all-in GraphQL,前端不再消费 REST 端点(登录除外)。teacher-bff GraphQL endpoint `POST /graphql`(经 api-gateway `/api/v1/teacher/*` 代理),schema 详见 [coord ARB-001](../../../docs/architecture/issues/coord.md#§1) + [teacher-portal_contract.md §2.4](../../../docs/architecture/issues/contracts/teacher-portal_contract.md)。
|
||||
|
||||
### 3.2 统一响应契约
|
||||
| 阶段 | 消费方式 | 下游 BFF/服务 | 关键 GraphQL Operation(P2 Must Have 加粗) |
|
||||
| ---- | ---------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P2 | GraphQL Query(teacher-bff,ARB-001 第一版) | teacher-bff → iam/classes | **`dashboard`**、**`viewports`**、**`me`**、**`classes`**、**`class(id)`**(5 个 Query,ARB-001 §1.2) |
|
||||
| P2 | HTTP(登录,MF 依赖认证前提,非 GraphQL) | api-gateway → iam | `POST /api/auth/login`(F12:JWT 存 localStorage,P6 迁移 httpOnly cookie) |
|
||||
| P3 | GraphQL Query + Mutation | teacher-bff → core-edu | `classExams` / `createExam` / `classHomework` / `assignHomework` / `studentGrades` / `recordGrade` |
|
||||
| P4 | GraphQL Query | teacher-bff → content/data-ana | `knowledgeGraph` / `studentAnalytics` |
|
||||
| P5 | GraphQL Query + Mutation + SSE/WS | teacher-bff → msg/ai + push-gateway | `myNotifications` / `markAsRead` / `generateQuestion`(SSE 流式)/ `generateLessonPlan` |
|
||||
|
||||
所有后端响应遵循 `ActionState` 结构(迁移指南 §7.5):
|
||||
> P2 不包含:exams/homework/grades Query(P3 core-edu 就绪后)、classPerformance/studentWeakness/learningTrend(P4 data-ana)、notifications(P5 msg)、所有 Mutation(P3+)、Subscription(P6+ 评估)——见 ARB-001 §1.2。
|
||||
|
||||
### 3.2 统一响应契约(ActionState 信封 + GraphQL errors)
|
||||
|
||||
BFF GraphQL 始终返回 `ActionState` 信封(004 §11.5),GraphQL `errors[]` 数组扩展 ActionState 字段(`extensions.code` = `BFF_TEACHER_*`,G14 裁决):
|
||||
|
||||
```typescript
|
||||
type ActionState<T> =
|
||||
@@ -72,18 +75,18 @@ type ActionState<T> =
|
||||
};
|
||||
```
|
||||
|
||||
错误码前缀按服务名大写(如 `IAM_`、`CORE_EDU_`、`CONTENT_`、`MSG_`、`AI_`、`BFF_TEACHER_`、`GW_`)。前端 API 请求层根据 `error.code` 前缀路由到对应的 i18n key。
|
||||
错误码前缀按服务名大写(如 `IAM_`、`CORE_EDU_`、`CONTENT_`、`MSG_`、`AI_`、`BFF_TEACHER_`、`GW_`,见 [matrix.md §6](../../../docs/architecture/issues/matrix.md))。前端 urql client 解析 `errors[].extensions.code` 前缀路由到对应 i18n key(详见 [03 §1.4.3](./03-long-term-architecture.md))。
|
||||
|
||||
### 3.3 推送契约(P5)
|
||||
|
||||
| 协议 | 场景 |
|
||||
| ------------------------- | -------------------------------------------- |
|
||||
| WebSocket(push-gateway) | 学生提交作业通知、考试成绩录入提醒、全校广播 |
|
||||
| SSE(ai 服务) | AI 辅助出题流式响应 |
|
||||
| SSE(ai 服务,经 teacher-bff 代理或直连) | AI 辅助出题流式响应 |
|
||||
|
||||
### 3.4 proto 不直接消费
|
||||
|
||||
前端不调用 gRPC,BFF 把 gRPC 聚合为 REST/GraphQL 暴露给前端。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量(TS 文件,非 proto 生成)。
|
||||
前端不调用 gRPC,BFF 把 gRPC 聚合为 GraphQL 暴露给前端(F9 裁决)。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量(TS 文件,非 proto 生成)。
|
||||
|
||||
## 4. 我的技术栈
|
||||
|
||||
@@ -91,11 +94,12 @@ type ActionState<T> =
|
||||
| --------------------------- | -------------------------------------------------------- | ---------------------------------------------- |
|
||||
| 框架 | Next.js 14+(App Router) | server components 默认,client components 按需 |
|
||||
| 语言 | TypeScript 5.5+(strict) | 沿用 tsconfig.base.json |
|
||||
| 微前端 | Module Federation 2.0(@module-federation/nextjs-mf) | teacher-portal = Shell |
|
||||
| 微前端 | Module Federation 2.0(@module-federation/nextjs-mf) | teacher-portal = Shell(ARB-002 暴露清单) |
|
||||
| **GraphQL client** | **urql**(F9 裁决 P2 起,shell 单例,ARB-002 shared singleton) | cacheExchange + fetchExchange,详见 [03 §1.4](./03-long-term-architecture.md) |
|
||||
| 样式 | Tailwind CSS 3.4+ | 配合设计令牌三层模型 |
|
||||
| UI 组件库 | shadcn/ui(迁移指南 §7.2) | 平移至 `packages/ui-components/`,MF 共享 |
|
||||
| 状态管理 L1 URL | nuqs | 可分享、可刷新状态 |
|
||||
| 状态管理 L2 Server | TanStack Query v5 | 服务端数据缓存、重试、乐观更新 |
|
||||
| 状态管理 L2 Server | urql cacheExchange(GraphQL 数据)+ TanStack Query(非 GraphQL 场景:文件上传/SSE) | F9 起不再双缓存层,详见 [03 §1.4.4](./03-long-term-architecture.md) |
|
||||
| 状态管理 L3 Client Business | Zustand slice | 客户端业务状态 |
|
||||
| 状态管理 L4 Global UI | Zustand ui-store + ModalRoot | 全局 UI 状态 |
|
||||
| 状态管理 L5 Form | react-hook-form + zodResolver | 表单状态 |
|
||||
@@ -103,7 +107,7 @@ type ActionState<T> =
|
||||
| 图表 | recharts | 学情、Dashboard |
|
||||
| i18n | next-intl | BFF/服务返回 i18n key + 参数,前端翻译 |
|
||||
| A11y | eslint-plugin-jsx-a11y(error 级) | WCAG 2.2 AA |
|
||||
| 字体 | Inter(sans)/ Fraunces(serif)/ JetBrains Mono(mono) | next/font/google 加载,CSS 变量暴露 |
|
||||
| 字体 | sans/serif/mono(通过 `var(--font-family-*)` 引用,禁止字面量) | next/font/google 加载,CSS 变量暴露(§3.10 强制) |
|
||||
|
||||
## 5. 我的阶段归属
|
||||
|
||||
@@ -201,5 +205,5 @@ apps/teacher-portal/
|
||||
|
||||
---
|
||||
|
||||
**AI Agent**: ai07 (teacher-portal shell)
|
||||
**Branch**: docs/teacher-portal-stage1-stage2-design-ai07
|
||||
**AI Agent**: ai13 (teacher-portal shell)
|
||||
**Branch**: feat-review-teacher-portal-docs-vErc0L
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# 模块架构设计文档 — teacher-portal
|
||||
|
||||
> AI:ai07(TS/React · 教学场景域前端 shell)
|
||||
> AI:ai13(TS/React · 教学场景域前端 shell)
|
||||
> 阶段:阶段 2 交付物
|
||||
> 日期:2026-07-09
|
||||
> 关联:[阶段 1 理解确认书](./01-understanding.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §5.4、[pending-features P2](../../../docs/architecture/roadmap/pending-features.md)
|
||||
> 状态:待 coord 交叉审查
|
||||
> 日期:2026-07-09(2026-07-10 审查回写:对齐 F9 GraphQL P2 起 + ARB-001/002 仲裁,REST→GraphQL 全量重写)
|
||||
> 关联:[阶段 1 理解确认书](./01-understanding.md)、[长远架构补全](./03-long-term-architecture.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md) §5.4、[coord 仲裁 ARB-001/002](../../../docs/architecture/issues/coord.md)、[pending-features P2](../../../docs/architecture/roadmap/pending-features.md)
|
||||
> 状态:✅ 已对齐 F9(GraphQL P2 起)+ ARB-001(schema 第一版)+ ARB-002(MF Shell 暴露清单)
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块内部分层图(4 端统一 MF 架构)
|
||||
## 1. 模块内部分层图(4 端统一 MF + GraphQL 架构)
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -16,26 +16,27 @@ graph TB
|
||||
URL[URL 路由]
|
||||
end
|
||||
|
||||
subgraph Shell["teacher-portal(Shell 宿主)"]
|
||||
AppShell[AppShell<br/>左栏导航 + 主内容区]
|
||||
subgraph Shell["teacher-portal(Shell 宿主 :4000)"]
|
||||
RootLayout[RootLayout<br/>字体/令牌/i18n Provider]
|
||||
GraphQLProvider[GraphQLProvider<br/>urql client 单例(ARB-002 §2.17 方案 A)]
|
||||
AppShell[AppShell<br/>左栏导航 + 主内容区 + 路由守卫]
|
||||
Router[Next.js App Router]
|
||||
SharedDeps["共享依赖暴露<br/>react/react-dom/@tanstack/react-query/zustand/nuqs"]
|
||||
SharedDeps["共享依赖暴露<br/>react/react-dom/urql/graphql/@edu/*(ARB-002 shared singleton)"]
|
||||
end
|
||||
|
||||
subgraph RemoteTeacher["teacher-portal Remote 模块"]
|
||||
TeacherPages[教学场景页面<br/>dashboard/classes/exams/homework/grades/ai-assist]
|
||||
end
|
||||
|
||||
subgraph RemoteStudent["student-portal(Remote)"]
|
||||
subgraph RemoteStudent["student-portal(Remote,P3+)"]
|
||||
StudentPages[学习场景页面<br/>dashboard/homework/submit/diagnostic/exam-taking]
|
||||
end
|
||||
|
||||
subgraph RemoteParent["parent-portal(Remote)"]
|
||||
subgraph RemoteParent["parent-portal(Remote,P4+)"]
|
||||
ParentPages[家长场景页面<br/>dashboard/children-switch/grades/notifications]
|
||||
end
|
||||
|
||||
subgraph RemoteAdmin["admin-portal(Remote)"]
|
||||
subgraph RemoteAdmin["admin-portal(Remote,P6+)"]
|
||||
AdminPages[管理场景页面<br/>users/roles/permissions/viewports/monitoring]
|
||||
end
|
||||
|
||||
@@ -43,22 +44,27 @@ graph TB
|
||||
UITokens[ui-tokens<br/>三层设计令牌]
|
||||
UIComponents[ui-components<br/>shadcn + A11y + ErrorBoundary]
|
||||
Contracts[contracts<br/>Permissions 常量 + 类型]
|
||||
Hooks[hooks<br/>usePermission/useAuth/useA11y]
|
||||
LibTS[shared-ts<br/>通用工具]
|
||||
Hooks[hooks<br/>usePermission/useAuth/useGraphQLClient]
|
||||
LibTS[shared-ts<br/>GraphQL schema + 通用工具]
|
||||
end
|
||||
|
||||
subgraph Gateway["api-gateway"]
|
||||
subgraph Gateway["api-gateway :8080"]
|
||||
GW[Gin 路由/鉴权/限流]
|
||||
end
|
||||
|
||||
subgraph TeacherBFF["teacher-bff :3003"]
|
||||
BFF[GraphQL Yoga + DataLoader<br/>POST /graphql(ARB-001 schema)]
|
||||
end
|
||||
|
||||
Browser --> URL
|
||||
URL --> RootLayout
|
||||
RootLayout --> AppShell
|
||||
RootLayout --> GraphQLProvider
|
||||
GraphQLProvider --> AppShell
|
||||
AppShell --> Router
|
||||
Router -->|动态加载| RemoteTeacher
|
||||
Router -->|动态加载| RemoteStudent
|
||||
Router -->|动态加载| RemoteParent
|
||||
Router -->|动态加载| RemoteAdmin
|
||||
Router -->|动态加载 P3+| RemoteStudent
|
||||
Router -->|动态加载 P4+| RemoteParent
|
||||
Router -->|动态加载 P6+| RemoteAdmin
|
||||
|
||||
RemoteTeacher --> SharedDeps
|
||||
RemoteStudent --> SharedDeps
|
||||
@@ -74,41 +80,43 @@ graph TB
|
||||
RemoteParent --> UITokens
|
||||
RemoteAdmin --> UITokens
|
||||
|
||||
AppShell -->|fetch /api/v1/iam/effective-permissions| Hooks
|
||||
Hooks -->|透传 token| GW
|
||||
RemoteTeacher -->|fetch /api/v1/teacher/*| GW
|
||||
RemoteStudent -->|fetch /api/v1/student/*| GW
|
||||
RemoteParent -->|fetch /api/v1/parent/*| GW
|
||||
RemoteAdmin -->|fetch /api/v1/iam/* + /api/v1/admin/*| GW
|
||||
GraphQLProvider -->|POST /graphql over HTTP| GW
|
||||
GW -->|反向代理 /api/v1/teacher/*| BFF
|
||||
BFF -->|gRPC 聚合 iam+classes+core-edu+content+data-ana+msg+ai| Services
|
||||
```
|
||||
|
||||
### 1.1 MF 拓扑选型
|
||||
|
||||
| 方案 | 选否 | 理由 |
|
||||
| ------------------------------------ | ---- | --------------------------------------------------------------------------------------- |
|
||||
| 4 端独立部署 + 独立域名 + 各自 Shell | ❌ | 4 套 Shell 重复,登录态/权限/组件库要重复实现 |
|
||||
| 单 Shell + 4 Remote(**采用**) | ✅ | teacher-portal 作为 Shell 宿主,提供 AppShell + 共享依赖;其余 3 端作为 Remote 动态加载 |
|
||||
| 4 端独立部署 + 独立域名 + 各自 Shell | ❌ | 4 套 Shell 重复,登录态/权限/组件库/GraphQL client 要重复实现 |
|
||||
| 单 Shell + 4 Remote(**采用**) | ✅ | teacher-portal 作为 Shell 宿主,提供 AppShell + GraphQLProvider 单例 + 共享依赖;其余 3 端作为 Remote 动态加载 |
|
||||
| 单一 Next.js 应用 + 4 路由组 | ❌ | 违反"微前端独立部署"目标(ADR-012) |
|
||||
|
||||
**Shell 职责**(teacher-portal):
|
||||
**Shell 职责**(teacher-portal,ARB-002 §2.2):
|
||||
|
||||
- RootLayout(字体、设计令牌、i18n Provider、TanStack QueryClientProvider、Zustand StoreProvider)
|
||||
- AppShell(左侧导航 + 主内容区 + 用户信息 + 登出)
|
||||
- 共享依赖暴露(react、react-dom、@tanstack/react-query、zustand、nuqs、ui-components、ui-tokens、contracts、hooks)
|
||||
- RootLayout(字体、设计令牌、i18n Provider、GraphQLProvider、Zustand StoreProvider)
|
||||
- AppShell(左侧导航 + 主内容区 + 用户信息 + 登出 + 路由守卫)
|
||||
- GraphQLProvider(urql client 单例,总裁 §2.17 方案 A,Remote 复用)
|
||||
- 共享依赖暴露(react、react-dom、urql、graphql、@edu/ui-tokens、@edu/ui-components、@edu/hooks)
|
||||
- 路由表(4 端路由前缀:`/teacher/*`、`/student/*`、`/parent/*`、`/admin/*`)
|
||||
- 登录页(统一登录入口,按角色重定向到对应 portal)
|
||||
- 登录页(统一登录入口 `POST /api/auth/login`,非 MF,登录是认证前提)
|
||||
|
||||
### 1.2 MF 配置(teacher-portal/next.config.js)
|
||||
### 1.2 MF 配置(teacher-portal/next.config.js,ARB-002 §2.2 裁决)
|
||||
|
||||
```javascript
|
||||
// teacher-portal/next.config.js(Shell)
|
||||
// teacher-portal/next.config.js(Shell,ARB-002 第一版暴露清单)
|
||||
const NextFederationPlugin = require("@module-federation/nextjs-mf");
|
||||
|
||||
const remotes = (isServer) => ({
|
||||
student: `student_app@http://localhost:3001/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
parent: `parent_app@http://localhost:3002/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
admin: `admin_app@http://localhost:3003/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
});
|
||||
// P2:remotes 为空(NEXT_PUBLIC_MF_ENABLED=false,ARB-002 §2.3),P3+ 逐步接入
|
||||
const remotes = (isServer) => {
|
||||
if (process.env.NEXT_PUBLIC_MF_ENABLED !== "true") return {};
|
||||
return {
|
||||
student: `student_app@http://localhost:4001/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
parent: `parent_app@http://localhost:4002/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
admin: `admin_app@http://localhost:4003/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
reactStrictMode: true,
|
||||
@@ -117,17 +125,28 @@ module.exports = {
|
||||
new NextFederationPlugin({
|
||||
name: "teacher_app",
|
||||
filename: "static/chunks/remoteEntry.js",
|
||||
remotes: remotes(isServer),
|
||||
remotes: remotes(isServer), // P2 为 {},ARB-002 §2.3
|
||||
exposes: {
|
||||
"./AppShell": "./src/components/AppShell",
|
||||
"./shared-deps": "./src/shared/deps",
|
||||
// ARB-002 §2.2 暴露清单
|
||||
"./AppShell": "./src/components/AppShell.tsx",
|
||||
"./GraphQLProvider": "./src/app/providers.tsx",
|
||||
"./useAuth": "./packages/hooks/src/use-auth.ts",
|
||||
"./usePermission": "./packages/hooks/src/use-permission.ts",
|
||||
"./useGraphQLClient": "./packages/hooks/src/use-graphql-client.ts",
|
||||
"./ErrorBoundary": "./packages/ui-components/src/error-boundary.tsx",
|
||||
"./Loading": "./packages/ui-components/src/loading.tsx",
|
||||
"./Empty": "./packages/ui-components/src/empty.tsx",
|
||||
"./RequirePermission": "./packages/ui-components/src/require-permission.tsx",
|
||||
},
|
||||
shared: {
|
||||
// ARB-002 §2.2 singleton 配置
|
||||
react: { singleton: true, requiredVersion: "^18.3.0" },
|
||||
"react-dom": { singleton: true, requiredVersion: "^18.3.0" },
|
||||
"@tanstack/react-query": { singleton: true },
|
||||
zustand: { singleton: true },
|
||||
nuqs: { singleton: true },
|
||||
urql: { singleton: true, requiredVersion: "^2.2.0" },
|
||||
graphql: { singleton: true, requiredVersion: "^16.8.0" },
|
||||
"@edu/ui-tokens": { singleton: true },
|
||||
"@edu/ui-components": { singleton: true },
|
||||
"@edu/hooks": { singleton: true },
|
||||
},
|
||||
extraOptions: { exposePages: false },
|
||||
}),
|
||||
@@ -145,154 +164,181 @@ module.exports = {
|
||||
};
|
||||
```
|
||||
|
||||
> Remote 端配置对称:`name: 'student_app'`,`exposes: { './pages': './src/pages' }`,`remotes: { teacher: 'teacher_app@...' }`。
|
||||
> **P2 Remote 数量 = 0**(ARB-002 §2.3,`NEXT_PUBLIC_MF_ENABLED=false` 默认关)。P3 student-portal 作为首个 Remote 接入(总裁 §1.3 step 4)。feature flag 控制回退单体路由。
|
||||
|
||||
## 2. 领域模型(前端视角)
|
||||
|
||||
前端不持有业务聚合根,仅持有"视图模型"(ViewModel)和"会话状态"。
|
||||
前端不持有业务聚合根,仅持有"视图模型"(ViewModel)和"会话状态"。数据来源统一为 teacher-bff GraphQL(F9 裁决)。
|
||||
|
||||
### 2.1 会话状态(Session)
|
||||
|
||||
```typescript
|
||||
interface Session {
|
||||
user: UserInfo; // { id, email, name, roles, permissions, dataScope }
|
||||
tokens: { accessToken: string; refreshToken: string };
|
||||
viewports: ViewportItem[]; // L1 导航视口
|
||||
user: UserInfo; // { id, email, name, roles, dataScope }(GraphQL me Query)
|
||||
tokens: { accessToken: string; refreshToken: string }; // F12: localStorage(P6 迁移 cookie)
|
||||
viewports: ViewportItem[]; // L1 导航视口(GraphQL viewports Query)
|
||||
permissions: string[]; // 权限点(GraphQL me Query 内嵌或 effective-permissions)
|
||||
expiresAt: number; // access token 过期时间戳
|
||||
}
|
||||
```
|
||||
|
||||
存储:Zustand sessionSlice(L3)+ localStorage 持久化(刷新恢复)+ TanStack Query 缓存 `['session']`(L2)。
|
||||
存储:Zustand sessionSlice(L3)+ localStorage 持久化(刷新恢复,F12)+ urql cache(L2,GraphQL document cache)。
|
||||
|
||||
### 2.2 视口模型(Viewport)
|
||||
|
||||
```typescript
|
||||
// 对齐 ARB-001 §1.2 schema:ViewportItem
|
||||
interface ViewportItem {
|
||||
key: string; // 'dashboard' | 'classes' | ...
|
||||
label: string; // i18n key 或显式文案
|
||||
route: string; // '/teacher/dashboard'
|
||||
icon: string | null; // 图标 key(按需)
|
||||
sortOrder: number; // 排序
|
||||
requiredPermission: string | null; // 'CLASSES_READ' 等
|
||||
scope: "teacher" | "student" | "parent" | "admin"; // 标记归属哪个 portal
|
||||
route: String; // '/teacher/dashboard'(schema 为 String)
|
||||
icon: String | null; // 图标 key(schema 可空)
|
||||
sortOrder: String; // 排序(schema 为 String)
|
||||
requiredPermission: String | null; // 'CLASSES_READ' 等
|
||||
}
|
||||
```
|
||||
|
||||
来源:`GET /api/v1/{scope}/viewports`(BFF 聚合 iam 视口配置)。AppShell 按 `scope` 过滤渲染对应 portal 的导航。
|
||||
来源:GraphQL `viewports` Query(ARB-001 §1.2)。AppShell 按 `requiredPermission` + `usePermission().hasPermission()` 过滤渲染导航。
|
||||
|
||||
### 2.3 权限模型(Permission)
|
||||
|
||||
```typescript
|
||||
interface PermissionState {
|
||||
permissions: string[]; // ['CLASSES_READ', 'EXAMS_CREATE', ...]
|
||||
dataScope: DataScope; // L0-L5
|
||||
permissions: string[]; // ['CLASSES_READ', 'EXAMS_CREATE', ...](GraphQL me Query 返回)
|
||||
dataScope: DataScope; // SELF | CLASS | GRADE | SCHOOL | DISTRICT | ALL(ARB-001 enum)
|
||||
hasPermission: (perm: string) => boolean;
|
||||
hasAnyPermission: (perms: string[]) => boolean;
|
||||
hasAllPermissions: (perms: string[]) => boolean;
|
||||
}
|
||||
```
|
||||
|
||||
来源:`GET /api/v1/iam/effective-permissions` → `{ permissions, viewports, dataScope }`。Redis 缓存 5min(iam 侧),前端 TanStack Query 缓存 5min,角色变更主动 invalidate。
|
||||
来源:GraphQL `me` Query 返回 `{ user, roles, dataScope }`(ARB-001 §1.2)。角色变更由 iam 发 Kafka → msg → push-gateway WebSocket 推送 → 前端 invalidate urql cache(见 [03 §2.5](./03-long-term-architecture.md))。
|
||||
|
||||
## 3. 数据模型(前端)
|
||||
## 3. 数据模型(前端缓存层)
|
||||
|
||||
前端无数据库,仅有缓存层:
|
||||
前端无数据库,仅有 urql 缓存层(03 §1.4.4 缓存策略):
|
||||
|
||||
| 数据类型 | 存储 | TTL | 失效策略 |
|
||||
| ----------------------- | ---------------------- | --------------------------- | -------------------------------------- |
|
||||
| Session(token + user) | localStorage + Zustand | access 15min / refresh 7day | 401 自动 refresh,refresh 失败跳登录 |
|
||||
| 权限列表 | TanStack Query cache | 5min | 角色变更事件 invalidate |
|
||||
| 视口列表 | TanStack Query cache | 5min | 同上 |
|
||||
| 班级/年级列表 | TanStack Query cache | 5min | staleTime 5min,mutation 后 invalidate |
|
||||
| 教学资源详情 | TanStack Query cache | 30s | staleTime 30s |
|
||||
| 学情宽表 | TanStack Query cache | 30s | staleTime 30s(实时性由 BFF 决定) |
|
||||
| URL 状态(分页/筛选) | nuqs | — | 永久(可分享) |
|
||||
| 表单临时态 | react-hook-form | — | 卸载即销毁 |
|
||||
| 数据类型 | 存储 | staleTime | 失效策略 |
|
||||
| ----------------------- | ----------------------------- | ---------------------------------- | --------------------------------------------- |
|
||||
| Session(token + user) | localStorage + Zustand(F12) | access 15min / refresh 7day | 401 自动 refresh,refresh 失败跳登录 |
|
||||
| 权限列表 + 视口 | urql cacheExchange | 0(always fetch) | 角色变更 WebSocket 推送后强制 invalidate |
|
||||
| 班级/年级列表 | urql cacheExchange | 30s | mutation 后 invalidate |
|
||||
| 考试/作业/成绩 | urql cacheExchange | 10s | mutation 后 invalidate + 乐观更新(03 §10.1) |
|
||||
| 教学资源详情 | urql cacheExchange | 30s | staleTime 30s |
|
||||
| 学情宽表 | urql cacheExchange | 60s | ISR revalidate 60s + CSR 交互 |
|
||||
| URL 状态(分页/筛选) | nuqs | — | 永久(可分享) |
|
||||
| 表单临时态 | react-hook-form | — | 卸载即销毁 |
|
||||
|
||||
## 4. API 设计(前端 → 后端)
|
||||
> urql 默认 document cache 适合简单场景;复杂关联(班级↔考试↔成绩)P3 起评估切 `@urql/exchange-graphcache` normalized cache(03 §1.4.4)。不引入 TanStack Query 做 GraphQL 缓存(避免双缓存层);TanStack Query 仅用于非 GraphQL 场景(文件上传/SSE)。
|
||||
|
||||
前端不设计后端 API,仅声明消费的端点。详见 [01-understanding.md §3.1](./01-understanding.md)。
|
||||
## 4. API 设计(前端 → teacher-bff GraphQL)
|
||||
|
||||
### 4.1 统一 API 请求层(lib/api.ts)
|
||||
F9 裁决:P2 起 all-in GraphQL。前端不再设计 REST 请求层,统一通过 urql client 消费 teacher-bff GraphQL(ARB-001 schema 第一版)。
|
||||
|
||||
### 4.1 urql client 单例(packages/hooks + Shell GraphQLProvider)
|
||||
|
||||
```typescript
|
||||
// packages/shared-ts/src/api-client.ts(共享)
|
||||
interface ApiClientOptions {
|
||||
baseUrl?: string; // 默认 ''(走 Next.js rewrites)
|
||||
getToken?: () => string | null;
|
||||
onUnauthorized?: () => void; // 401 → refresh → 重试 / 跳登录
|
||||
onError?: (error: ApiError) => void; // 全局 toast
|
||||
}
|
||||
// packages/hooks/src/use-graphql-client.ts(Shell 暴露,Remote 复用,ARB-002 §2.17 方案 A)
|
||||
import { createClient, Client } from "urql";
|
||||
import { cacheExchange, fetchExchange } from "urql";
|
||||
|
||||
class ApiClient {
|
||||
async get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
||||
async post<T>(path: string, body: unknown): Promise<T>;
|
||||
async put<T>(path: string, body: unknown): Promise<T>;
|
||||
async delete<T>(path: string): Promise<T>;
|
||||
async sse<T>(path: string, body: unknown): AsyncIterable<T>; // AI 流式
|
||||
}
|
||||
const client: Client = createClient({
|
||||
url: process.env.NEXT_PUBLIC_TEACHER_BFF_GRAPHQL_URL!, // teacher-bff POST /graphql(经 api-gateway 代理)
|
||||
fetchOptions: () => {
|
||||
const token = readAuthToken(); // useAuth 注入(F12: localStorage)
|
||||
const traceId = readTraceId();
|
||||
return {
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
"X-Trace-Id": traceId,
|
||||
},
|
||||
};
|
||||
},
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
});
|
||||
|
||||
// 错误结构
|
||||
interface ApiError {
|
||||
code: string; // 'IAM_INVALID_CREDENTIALS'
|
||||
message: string; // 已 i18n 翻译或后端原文
|
||||
details?: unknown;
|
||||
httpStatus: number;
|
||||
}
|
||||
// Shell: src/app/providers.tsx 包裹 <Provider value={client}>,所有 Remote 复用同一 client
|
||||
```
|
||||
|
||||
**职责**:
|
||||
|
||||
- 自动注入 `Authorization: Bearer ${token}`
|
||||
- 401 自动 refresh token 一次,失败调 `onUnauthorized`
|
||||
- 解析 `ActionState`,success=false 抛 `ApiError`
|
||||
- 按 `error.code` 前缀路由 i18n key
|
||||
- 全局错误 toast(除 401)
|
||||
- 请求/响应 trace_id 透传(从响应头 `X-Request-Id` 提取)
|
||||
- Shell 初始化 urql client 单例,通过 React Context 注入给所有 Remote(ARB-002 方案 A)
|
||||
- token 刷新/401 处理由 useAuth 统一拦截(不污染 urql exchange 链,见 [03 §2.7](./03-long-term-architecture.md))
|
||||
- MF shared singleton(react/urql/graphql)避免 Remote 多实例 + 缓存不一致
|
||||
- 请求/响应 trace_id 透传(`X-Trace-Id` header)
|
||||
|
||||
### 4.2 TanStack Query 约定
|
||||
### 4.2 GraphQL Query/Mutation 约定(对齐 ARB-001 schema)
|
||||
|
||||
```typescript
|
||||
// Query Key 命名:[scope, resource, ...args]
|
||||
queryKey: ["teacher", "classes", { gradeId }];
|
||||
queryKey: ["teacher", "exams", classId];
|
||||
queryKey: ["session", "effective-permissions"];
|
||||
queryKey: ["session", "viewports", "teacher"];
|
||||
// P2 Query(ARB-001 §1.2,5 个 Must Have)
|
||||
import { gql } from "urql";
|
||||
|
||||
// Mutation 约定
|
||||
const mutation = useMutation({
|
||||
mutationFn: (input) => api.post("/api/v1/classes", input),
|
||||
onSuccess: () =>
|
||||
queryClient.invalidateQueries({ queryKey: ["teacher", "classes"] }),
|
||||
onError: (e: ApiError) => toast.error(e.message),
|
||||
});
|
||||
const DashboardQuery = gql`
|
||||
query Dashboard {
|
||||
dashboard {
|
||||
user { id email name roles dataScope }
|
||||
classes { id name gradeId studentCount }
|
||||
viewports { key label route icon sortOrder requiredPermission }
|
||||
stats { totalExams pendingGrading todayHomework }
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ViewportsQuery = gql`query { viewports { key label route icon sortOrder requiredPermission } }`;
|
||||
const MeQuery = gql`query { me { id email name roles dataScope } }`;
|
||||
const ClassesQuery = gql`query { classes { id name gradeId studentCount } }`;
|
||||
|
||||
// P3+ Mutation(ARB-001 §1.2 P2 不包含)
|
||||
const CreateExamMutation = gql`
|
||||
mutation CreateExam($input: CreateExamInput!) {
|
||||
createExam(input: $input) { ... on ExamCreated { id } }
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
**urql mutation + 乐观更新**:
|
||||
|
||||
```typescript
|
||||
const [, createExam] = useMutation(CreateExamMutation);
|
||||
// 乐观更新:onMutate 回滚 + invalidateQueries(见 03 §10.1)
|
||||
```
|
||||
|
||||
### 4.3 错误处理(ActionState 信封 + GraphQL errors,ARB-001 §1.3)
|
||||
|
||||
| 场景 | 处理 |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
| GraphQL 错误(errors[]) | 解析 `extensions.code`(BFF_TEACHER_* 前缀,G14),映射到 i18n key `error.teacher_bff.<code_snake>`(F11) |
|
||||
| 网络错误 | 重试 1 次 → 提示网络异常 → 记录 trace_id |
|
||||
| 401 | useAuth 静默 refresh 一次 → 重试;失败跳登录(见 03 §2.7) |
|
||||
| 部分失败(data + errors 共存) | 渲染 data 可用部分 + errors 区域提示,不整体失败(ARB-001 §1.3 降级模式方案 B) |
|
||||
|
||||
- BFF 始终返回 `ActionState` 信封(004 §11.5),前端统一走 `normalizeActionState()` 工具(packages/hooks)
|
||||
|
||||
## 5. 事件设计
|
||||
|
||||
前端不发布 Kafka 事件,仅消费 WebSocket 推送(P5)和 Server-Sent Events(AI 流式)。
|
||||
|
||||
### 5.1 WebSocket 推送(P5)
|
||||
|
||||
| 事件 | 触发 | teacher-portal 前端动作 |
|
||||
| ----------------------- | ------------ | ------------------------------- |
|
||||
| `NotificationRequested` | msg 服务投递 | toast 提示 + 通知中心未读数 +1 |
|
||||
| `HomeworkSubmitted` | 学生提交作业 | toast + 作业批改列表 invalidate |
|
||||
| 事件 | 触发 | teacher-portal 前端动作 |
|
||||
| ----------------------- | ------------ | ----------------------------------------------- |
|
||||
| `NotificationRequested` | msg 服务投递 | toast 提示 + 通知中心未读数 +1 |
|
||||
| `HomeworkSubmitted` | 学生提交作业 | toast + urql cache invalidate 作业批改列表 |
|
||||
| 角色变更(iam) | iam Kafka | urql cache invalidate 权限/视口(强制刷新 me Query) |
|
||||
|
||||
### 5.2 SSE 流式(P5 AI 辅助出题)
|
||||
|
||||
```
|
||||
GET /api/v1/ai/generate-questions (SSE)
|
||||
GET /api/v1/ai/generate-questions (SSE) 或 GraphQL Subscription(P6+ 评估)
|
||||
data: {"delta": "题目"}\n\n
|
||||
data: {"delta": "A. option1"}\n\n
|
||||
data: {"done": true}\n\n
|
||||
```
|
||||
|
||||
前端用 `AsyncIterable<T>` 消费,Tiptap 逐字插入。
|
||||
前端用 `AsyncIterable<T>` 消费(非 GraphQL,TanStack Query 场景),Tiptap 逐字插入。
|
||||
|
||||
## 6. 横切关注点对齐清单
|
||||
|
||||
### 6.1 权限(前端等价)
|
||||
### 6.1 权限(前端等价,F7 命名 `<RESOURCE>_<ACTION>`)
|
||||
|
||||
| 路由 | requiredPermission |
|
||||
| ----------------------------- | ------------------------ |
|
||||
@@ -308,9 +354,9 @@ data: {"done": true}\n\n
|
||||
| `/teacher/lesson-prep` | `LESSON_PREP_VIEW` |
|
||||
| `/teacher/knowledge-graph` | `CONTENT_READ` |
|
||||
|
||||
> 完整权限点常量集中在 `packages/contracts/src/permissions.ts`(待建立,coord 负责 shared-ts,ai07 负责调用)。L3 组件级视口用 `<RequirePermission perm="EXAMS_CREATE"><Button>新建考试</Button></RequirePermission>`。
|
||||
> 完整权限点常量集中在 `packages/contracts/src/permissions.ts`(coord 维护,ai13 消费)。L3 组件级视口用 `<RequirePermission perm="EXAMS_CREATE"><Button>新建考试</Button></RequirePermission>`。
|
||||
|
||||
### 6.2 错误码清单(前端 i18n 路由)
|
||||
### 6.2 错误码清单(前端 i18n 路由,对齐 [matrix.md §6](../../../docs/architecture/issues/matrix.md))
|
||||
|
||||
| 前缀 | 来源服务 | i18n key 模式 |
|
||||
| -------------- | ----------- | ------------------------ |
|
||||
@@ -320,7 +366,7 @@ data: {"done": true}\n\n
|
||||
| `CONTENT_` | content | `content.error.{{code}}` |
|
||||
| `MSG_` | msg | `msg.error.{{code}}` |
|
||||
| `AI_` | ai | `ai.error.{{code}}` |
|
||||
| `BFF_TEACHER_` | teacher-bff | `bff.error.{{code}}` |
|
||||
| `BFF_TEACHER_` | teacher-bff | `error.teacher_bff.{{code_snake}}` |
|
||||
| `GW_` | api-gateway | `gateway.error.{{code}}` |
|
||||
| `NETWORK_` | 前端网络层 | `network.error.{{code}}` |
|
||||
|
||||
@@ -333,7 +379,6 @@ interface Logger {
|
||||
warn(msg: string, meta?: Record<string, unknown>): void;
|
||||
error(msg: string, meta?: Record<string, unknown>): void;
|
||||
}
|
||||
|
||||
// 实现:开发环境 console + 结构化;生产环境 → Sentry(P6)
|
||||
// 必含字段:trace_id(从响应头提取)、user_id、scope、path
|
||||
```
|
||||
@@ -344,7 +389,7 @@ interface Logger {
|
||||
| ----------------------------- | ---- | --------------------------------------------------- |
|
||||
| `teacher_portal_lcp_seconds` | LCP | `next/web-vitals` → `POST /api/v1/admin/web-vitals` |
|
||||
| `teacher_portal_cls` | CLS | 同上 |
|
||||
| `teacher_portal_fid_seconds` | FID | 同上 |
|
||||
| `teacher_portal_inp_seconds` | INP | 同上 |
|
||||
| `teacher_portal_ttfb_seconds` | TTFB | 同上 |
|
||||
|
||||
P6 接入,P2-P5 暂缓。
|
||||
@@ -369,37 +414,38 @@ import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
||||
|
||||
Next.js 无长连接(除 SSE/WS),无需特殊处理。SSE/WS 在 P5 由 push-gateway 管理,前端断线自动重连。
|
||||
|
||||
## 7. 共享组件库(packages/ui-components/,待建立,ai07 维护)
|
||||
## 7. 共享组件库(packages/ui-components/,ai13 维护,ARB-002 暴露)
|
||||
|
||||
| 组件 | 用途 | 来源 |
|
||||
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `AppShell` | 左侧栏 + 主内容区布局 | teacher-portal 现有 → 抽取共享 |
|
||||
| `RequirePermission` | L3 组件级视口控制(无权限不渲染 children) | 新建 |
|
||||
| `ErrorBoundary` | React 渲染异常兜底(fallback UI) | 新建 |
|
||||
| `Loading` | 骨架屏(Skeleton) | 新建 |
|
||||
| `Empty` | 空态(插画 + 文案 + CTA) | 新建 |
|
||||
| `Modal` / `Dialog` | 全局 Modal(ModalRoot + Zustand ui-store) | shadcn/ui |
|
||||
| `Toast` | 全局 toast(错误/成功/警告) | shadcn/ui sonner |
|
||||
| `Button` / `Input` / `Select` / `Textarea` | 基础表单 | shadcn/ui |
|
||||
| `DataTable` | 表格(排序/分页/筛选) | shadcn/ui + TanStack Table |
|
||||
| `Chart` | 图表封装(recharts) | 新建 |
|
||||
| `A11y` 工具集 | useA11yId / mergeA11yProps / describeInput / focus-trap / skip-link / visually-hidden / aria-status | 迁移指南 §7.7 |
|
||||
| `Form` | react-hook-form + zodResolver 封装 | 新建 |
|
||||
| `RichTextEditor` | Tiptap 封装(备课/出题/反馈) | 新建 |
|
||||
| 组件 | 用途 | MF 暴露(ARB-002) |
|
||||
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| `AppShell` | 左侧栏 + 主内容区布局 | ✅ `./AppShell` |
|
||||
| `RequirePermission` | L3 组件级视口控制(无权限不渲染 children) | ✅ `./RequirePermission` |
|
||||
| `ErrorBoundary` | React 渲染异常兜底(fallback UI) | ✅ `./ErrorBoundary` |
|
||||
| `Loading` | 骨架屏(Skeleton) | ✅ `./Loading` |
|
||||
| `Empty` | 空态(插画 + 文案 + CTA) | ✅ `./Empty` |
|
||||
| `Modal` / `Dialog` | 全局 Modal(ModalRoot + Zustand ui-store) | shadcn/ui |
|
||||
| `Toast` | 全局 toast(错误/成功/警告) | shadcn/ui sonner |
|
||||
| `Button` / `Input` / `Select` / `Textarea` | 基础表单 | shadcn/ui |
|
||||
| `DataTable` | 表格(排序/分页/筛选) | shadcn/ui + TanStack Table |
|
||||
| `Chart` | 图表封装(recharts) | 新建 |
|
||||
| `A11y` 工具集 | useA11yId / mergeA11yProps / describeInput / focus-trap / skip-link / visually-hidden / aria-status | 迁移指南 §7.7 |
|
||||
| `Form` | react-hook-form + zodResolver 封装 | 新建 |
|
||||
| `RichTextEditor` | Tiptap 封装(备课/出题/反馈) | 新建 |
|
||||
|
||||
## 8. 共享 Hooks(packages/hooks/,待建立,ai07 维护)
|
||||
## 8. 共享 Hooks(packages/hooks/,ai13 维护,ARB-002 暴露)
|
||||
|
||||
| Hook | 职责 |
|
||||
| --------------------- | --------------------------------------------------- |
|
||||
| `useAuth()` | 会话状态(user/token/refresh/login/logout) |
|
||||
| `usePermission()` | 权限查询(hasPermission/hasAny/hasAll + dataScope) |
|
||||
| `useViewports(scope)` | 视口列表(按 scope 过滤) |
|
||||
| `useApi()` | ApiClient 实例(注入 token + 401 处理) |
|
||||
| `useA11yId()` | 唯一 ARIA ID 生成 |
|
||||
| `useAriaLive()` | aria-live 区域管理 |
|
||||
| `useToast()` | 全局 toast(Zustand ui-store) |
|
||||
| Hook | 职责 | MF 暴露(ARB-002) |
|
||||
| --------------------- | --------------------------------------------------- | ------------------ |
|
||||
| `useGraphQLClient()` | urql client 单例(Shell 注入,Remote 复用) | ✅ `./useGraphQLClient` |
|
||||
| `useAuth()` | 会话状态(user/token/refresh/login/logout) | ✅ `./useAuth` |
|
||||
| `usePermission()` | 权限查询(hasPermission/hasAny/hasAll + dataScope) | ✅ `./usePermission` |
|
||||
| `useViewports(scope)` | 视口列表(GraphQL viewports Query,按 scope 过滤) | — |
|
||||
| `useApi()` | 非 GraphQL 场景(文件上传/SSE)ApiClient 实例 | — |
|
||||
| `useA11yId()` | 唯一 ARIA ID 生成 | — |
|
||||
| `useAriaLive()` | aria-live 区域管理 | — |
|
||||
| `useToast()` | 全局 toast(Zustand ui-store) | — |
|
||||
|
||||
## 9. 设计令牌三层(packages/ui-tokens/,待建立,ai07 维护)
|
||||
## 9. 设计令牌三层(packages/ui-tokens/,ai13 维护)
|
||||
|
||||
```
|
||||
packages/ui-tokens/
|
||||
@@ -429,52 +475,54 @@ packages/ui-tokens/
|
||||
|
||||
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 | 阶段 |
|
||||
| ------ | ------------ | ------------------ | --------------------------------------------------- | ---------------------------- | ---- |
|
||||
| 调用 | api-gateway | HTTP/REST | `/api/v1/*` 代理 | 全部业务请求 | P1+ |
|
||||
| 调用 | push-gateway | WebSocket | `ws://push-gateway/ws` | 实时推送 | P5 |
|
||||
| 调用 | ai | SSE | `GET /api/v1/ai/generate-questions` | AI 流式出题 | P5 |
|
||||
| 调用 | api-gateway | HTTP(GraphQL over HTTP) | `/api/v1/teacher/*` 代理到 teacher-bff `POST /graphql` | 全部 GraphQL 业务请求 | P2+ |
|
||||
| 调用 | api-gateway | HTTP | `POST /api/auth/login` | 教师登录(非 MF,认证前提) | P2 |
|
||||
| 调用 | push-gateway | WebSocket | `ws://push-gateway:8081/ws` | 实时推送 | P5 |
|
||||
| 调用 | ai | SSE | `GET /api/v1/ai/generate-questions` | AI 流式出题(非 GraphQL) | P5 |
|
||||
| 被调用 | — | — | — | 前端不暴露接口给其他服务 | — |
|
||||
| 消费 | teacher-bff | HTTP(经 Gateway) | `GET /teacher/viewports` 等 | 教师场景聚合 | P2+ |
|
||||
| 消费 | iam | HTTP(经 Gateway) | `/iam/*` | 登录/权限/视口/用户管理 | P2+ |
|
||||
| 消费 | core-edu | HTTP(经 Gateway) | `/classes/*` `/exams/*` `/homework/*` `/grades/*` | 教学核心 | P2+ |
|
||||
| 消费 | content | HTTP(经 Gateway) | `/textbooks/*` `/knowledge-points/*` `/questions/*` | 内容资源 | P4+ |
|
||||
| 消费 | data-ana | HTTP(经 Gateway) | `/analytics/*` | 学情分析 | P4+ |
|
||||
| 消费 | msg | HTTP(经 Gateway) | `/notifications/*` | 通知中心 | P5+ |
|
||||
| 消费 | teacher-bff | GraphQL(ARB-001 schema) | `dashboard`/`viewports`/`me`/`classes`/`class` Query(P2)+ P3+ Mutation | 教师场景聚合 | P2+ |
|
||||
| 消费 | push-gateway | WebSocket | `NotificationRequested`/`HomeworkSubmitted`/角色变更 | 实时推送 | P5+ |
|
||||
| 依赖 | coord 维护 | — | `packages/shared-proto` | TS 类型(仅 contracts 部分) | P1+ |
|
||||
| 依赖 | coord 维护 | — | `packages/shared-ts`(待建) | ApiClient/Logger/通用工具 | P2+ |
|
||||
| 依赖 | ai07 维护 | — | `packages/ui-tokens`(待建) | 三层设计令牌 | P2+ |
|
||||
| 依赖 | ai07 维护 | — | `packages/ui-components`(待建) | shadcn + 共享组件 | P2+ |
|
||||
| 依赖 | ai07 维护 | — | `packages/hooks`(待建) | usePermission/useAuth 等 | P2+ |
|
||||
| 依赖 | coord 维护 | — | `packages/contracts`(待建) | Permissions 常量 + 类型 | P2+ |
|
||||
| 依赖 | coord 维护 | — | `packages/shared-ts` + `contracts/graphql/teacher-bff.graphql` | ApiClient/Logger/GraphQL schema/通用工具 | P2+ |
|
||||
| 依赖 | ai13 维护 | — | `packages/ui-tokens`(已建,批次 0.15) | 三层设计令牌 | P2+ |
|
||||
| 依赖 | ai13 维护 | — | `packages/ui-components`(已建,批次 0.15) | shadcn + 共享组件(ARB-002 暴露) | P2+ |
|
||||
| 依赖 | ai13 维护 | — | `packages/hooks`(已建,批次 0.15) | useGraphQLClient/usePermission/useAuth(ARB-002 暴露) | P2+ |
|
||||
| 依赖 | coord 维护 | — | `packages/contracts` | Permissions 常量 + 类型 | P2+ |
|
||||
|
||||
> **proto 不直接消费**:前端不调用 gRPC,BFF 把 gRPC 聚合为 REST/GraphQL 暴露给前端。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量(TS 文件,非 proto 生成)。
|
||||
> **前端不直接调 gRPC**:teacher-bff 把 gRPC 聚合为 GraphQL 暴露给前端(F9 裁决)。前端仅消费 `packages/contracts/src/permissions.ts` 中的权限点常量(TS 文件,非 proto 生成)。
|
||||
|
||||
## 11. 风险与假设
|
||||
|
||||
### 11.1 假设
|
||||
|
||||
1. **假设 coord 建立 `packages/shared-ts`、`packages/contracts`**:包含 ApiClient、Logger、Permissions 常量、通用类型。若 coord 未建立,ai07 自行在 `apps/teacher-portal/src/shared/` 内实现,后续提取到 packages。
|
||||
2. **假设 iam 提供 `GET /iam/effective-permissions`**:返回 `{ permissions, viewports, dataScope }`。当前已实现(known-issues §2.3 iam)。
|
||||
3. **假设 teacher-bff 提供 `GET /teacher/viewports`**:返回 L1 导航视口。当前已实现。
|
||||
4. **假设 core-edu classes 模块维持 `ActionState` 响应结构**:前端 API 请求层依赖此契约。
|
||||
5. **假设 Next.js 14+ Module Federation 2.0 稳定**:`@module-federation/nextjs-mf` 在 Next.js App Router 下可用。若不稳定,降级为 4 端独立部署 + 各自 Shell(重复实现 AppShell)。
|
||||
1. **假设 coord 建立 `packages/shared-ts`、`packages/contracts`**:包含 GraphQL schema、Logger、Permissions 常量、通用类型。✅ packages 骨架(ui-tokens/ui-components/hooks)已于批次 0.15 建立(ISSUE-039)。
|
||||
2. **假设 teacher-bff 提供 `POST /graphql`(ARB-001 schema 第一版)**:返回 dashboard/viewports/me/classes/class 5 个 Query。⏳ 待 ai03 + coord 仲裁。
|
||||
3. **假设 api-gateway 代理 `/api/v1/teacher/*` → teacher-bff:3003**:⏳ 待 ai01。
|
||||
4. **假设 Next.js 14+ Module Federation 2.0 稳定**:`@module-federation/nextjs-mf` 在 Next.js App Router 下可用。若不稳定,降级为 4 端独立部署 + 各自 Shell(重复实现 AppShell)。
|
||||
5. **假设 iam 提供 refresh cookie 端点**:P6 localStorage→httpOnly cookie 迁移前置。⏳ 待 ai06 P6。
|
||||
|
||||
### 11.2 技术风险
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
| ------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| MF SSR 对齐复杂 | Remote 在 SSR 时需 Shell 提供上下文 | 优先 CSR,SSR 仅用于首屏 dashboard;MF 2.0 支持 SSR |
|
||||
| 共享依赖版本漂移 | Remote 与 Shell 的 react/react-dom 版本不一致导致运行时错误 | MF `shared.singleton: true` + CI 检查版本对齐 |
|
||||
| Token 刷新竞态 | 多请求同时 401 触发多次 refresh | ApiClient 全局单例 + refresh promise 复用 |
|
||||
| 权限缓存陈旧 | 角色变更后前端 5min 内仍用旧权限 | iam 角色变更发 Kafka 事件 → msg 推送 WebSocket → 前端 invalidate |
|
||||
| 设计令牌迁移破坏现有样式 | teacher-portal 现有硬编码令牌迁移到三层模型后样式漂移 | 灰度迁移:先建 ui-tokens 包,teacher-portal 引入但不删除旧 globals.css,验证后切换 |
|
||||
| TanStack Query 缓存膨胀 | 长时间使用后缓存项过多 | `gcTime` 5min + `staleTime` 按数据类型分级 |
|
||||
| 共享依赖版本漂移 | Remote 与 Shell 的 react/react-dom/urql/graphql 版本不一致导致运行时错误 | MF `shared.singleton: true`(ARB-002)+ CI 检查版本对齐 |
|
||||
| Token 刷新竞态 | 多请求同时 401 触发多次 refresh | useAuth 全局单例 + refresh promise 复用(03 §2.7) |
|
||||
| 权限缓存陈旧 | 角色变更后前端仍用旧权限 | iam 角色变更发 Kafka → msg → push-gateway WebSocket → urql cache invalidate |
|
||||
| GraphQL schema 演进阻塞 | ai03 schema 变更未同步 ai13 导致前端 query 失效 | SDL-first + schema 注册表 `packages/shared-ts/contracts/graphql/`(ARB-001) |
|
||||
| 设计令牌迁移破坏现有样式 | teacher-portal 现有硬编码令牌迁移到三层模型后样式漂移 | 灰度迁移:先建 ui-tokens 包,teacher-portal 引入但不删除旧 globals.css,验证后切换 |
|
||||
| urql cache 膨胀 | 长时间使用后缓存项过多 | staleTime 分级 + P3 评估 graphcache normalized cache |
|
||||
|
||||
### 11.3 未决设计决策(需 coord 仲裁)
|
||||
### 11.3 已决策设计点(原未决决策,现已对齐仲裁)
|
||||
|
||||
1. **packages 归属**:`ui-tokens` / `ui-components` / `hooks` 是 ai07 维护还是 coord 维护?建议:ai07 维护(前端专属),coord 仅维护 `shared-ts` / `contracts`(跨语言/跨服务)。
|
||||
2. **GraphQL vs REST**:004 §11.3 提到 BFF GraphQL Yoga + DataLoader,但当前 teacher-bff 实现为 REST。前端 API 请求层是否需要 GraphQL client(urql/apollo)?建议:P2-P3 用 REST,P4 起若 BFF 切 GraphQL 再引入 urql。
|
||||
3. **i18n key 命名**:`iam.error.IAM_INVALID_CREDENTIALS` 还是 `error.iam.invalid_credentials`?建议:`error.{{service}}.{{code_snake_case}}`,与错误码前缀对齐。
|
||||
4. **MF 暴露粒度**:Shell 暴露整个 AppShell 还是暴露更细粒度的组件(Sidebar、Header、Content)?建议:暴露 AppShell 整体 + 各 Remote 自行决定内部布局。
|
||||
| 决策点 | 结论 | 裁决依据 |
|
||||
| ------------------- | ----------------------------------------------------------- | ------------------------------- |
|
||||
| packages 归属 | ai13 维护 ui-tokens/ui-components/hooks;coord 维护 shared-ts/contracts | 总裁 §2.18(ISSUE-039) |
|
||||
| GraphQL vs REST | **P2 起 all-in GraphQL(urql),无 REST 过渡** | F9 + ARB-001(ISSUE-036/037) |
|
||||
| i18n key 命名 | `error.{{service}}.{{code_snake_case}}`,与错误码前缀对齐 | F11 |
|
||||
| MF 暴露粒度 | 暴露 AppShell 整体 + GraphQLProvider + hooks + UI 组件(ARB-002 清单) | 总裁 §2.17 + ARB-002(ISSUE-038) |
|
||||
| GraphQL client 单例 | Shell 暴露 GraphQLProvider,Remote 复用(方案 A) | 总裁 §2.17(ISSUE-038) |
|
||||
| P2 Remote 数量 | 0(NEXT_PUBLIC_MF_ENABLED=false),P3 student 首个接入 | ARB-002 §2.3 |
|
||||
|
||||
## 12. coord 交叉审查所需信息
|
||||
|
||||
@@ -482,68 +530,74 @@ packages/ui-tokens/
|
||||
|
||||
| 端 | dev 端口 | 生产端口 | 备注 |
|
||||
| -------------- | -------- | -------- | ---------- |
|
||||
| teacher-portal | 3000 | 3000 | Shell 宿主 |
|
||||
| teacher-portal | 4000 | 4000 | Shell 宿主 |
|
||||
|
||||
> 与 [full-stack-runbook](../../../docs/standards/full-stack-runbook.md) 端口矩阵对齐。
|
||||
> 与 [matrix.md](../../../docs/architecture/issues/matrix.md) §1 端口矩阵对齐(teacher-portal :4000)。
|
||||
|
||||
### 12.2 依赖的共享包(需 coord 建立)
|
||||
### 12.2 依赖的共享包
|
||||
|
||||
| 包 | 路径 | 维护方 | 内容 |
|
||||
| --------------- | ------------------------- | ------------ | ------------------------------------------------- |
|
||||
| `shared-ts` | `packages/shared-ts/` | coord | ApiClient、Logger、通用工具 |
|
||||
| `shared-ts` | `packages/shared-ts/` | coord | ApiClient、Logger、GraphQL schema、通用工具 |
|
||||
| `contracts` | `packages/contracts/` | coord | Permissions 常量、ActionState 类型、UserInfo 类型 |
|
||||
| `ui-tokens` | `packages/ui-tokens/` | ai07(建议) | 三层设计令牌 |
|
||||
| `ui-components` | `packages/ui-components/` | ai07(建议) | shadcn + ErrorBoundary + RequirePermission |
|
||||
| `hooks` | `packages/hooks/` | ai07(建议) | usePermission、useAuth、useViewports |
|
||||
| `ui-tokens` | `packages/ui-tokens/` | ai13 | 三层设计令牌(✅ 已建,批次 0.15) |
|
||||
| `ui-components` | `packages/ui-components/` | ai13 | shadcn + ErrorBoundary + RequirePermission(✅ 已建,ARB-002 暴露) |
|
||||
| `hooks` | `packages/hooks/` | ai13 | useGraphQLClient/usePermission/useAuth(✅ 已建,ARB-002 暴露) |
|
||||
|
||||
### 12.3 依赖的后端契约(需对应 AI 确认)
|
||||
|
||||
| 契约 | 提供方 | 当前状态 |
|
||||
| ------------------------------------------------------------------ | ------------------ | ------------------- |
|
||||
| `POST /iam/login`、`GET /iam/effective-permissions`、`GET /iam/me` | iam | ✅ 已实现 |
|
||||
| `GET /teacher/viewports`、`GET /teacher/dashboard` | teacher-bff | ✅ 已实现 |
|
||||
| `/classes/*` CRUD | core-edu | ✅ 已实现 |
|
||||
| `/exams/*` `/homework/*` `/grades/*` | core-edu | ✅ 已实现(P3) |
|
||||
| `/textbooks/*` `/knowledge-points/*` `/questions/*` | content | ✅ 已实现(P4) |
|
||||
| `/analytics/*` | data-ana | ✅ 已实现(P4 CDC) |
|
||||
| `/notifications/*` + WebSocket 推送 | msg + push-gateway | 📐 待 P5 |
|
||||
| `GET /ai/generate-questions`(SSE) | ai | 📐 待 P5 |
|
||||
| `POST /graphql` + dashboard/viewports/me/classes/class Query(ARB-001) | teacher-bff(ai03)| ⏳ 待 coord 仲裁 |
|
||||
| `POST /api/auth/login` | api-gateway(ai01)→ iam | ⏳ 待 ai01 |
|
||||
| classExams/classHomework/studentGrades Query + Mutation | teacher-bff(ai03 P3)| ⏳ 待 ai03 P3 |
|
||||
| knowledgeGraph/studentAnalytics Query | teacher-bff(ai03 P4)| ⏳ 待 ai03 P4 |
|
||||
| myNotifications/markAsRead Query + Mutation | teacher-bff(ai03 P5)| ⏳ 待 ai03 P5 |
|
||||
| `ws://push-gateway:8081/ws` 推送 | push-gateway(ai02 P5)| ⏳ 待 ai02 P5 |
|
||||
| `GET /api/v1/ai/generate-questions`(SSE) | ai(ai12 P5) | ⏳ 待 ai12 P5 |
|
||||
|
||||
### 12.4 错误码前缀(前端 i18n 路由依赖)
|
||||
### 12.4 错误码前缀(前端 i18n 路由依赖,对齐 [matrix.md §6](../../../docs/architecture/issues/matrix.md))
|
||||
|
||||
前端不产生错误码,仅消费。需各服务确认错误码前缀不重叠:
|
||||
|
||||
| 前缀 | 服务 | 状态 |
|
||||
| ------------------------------ | ----------- | --------- |
|
||||
| `IAM_` | iam | ✅ 已用 |
|
||||
| `CORE_EDU_` | core-edu | ✅ 已用 |
|
||||
| `CLASSES_` | core-edu | ✅ 已用 |
|
||||
| `EXAMS_`/`HOMEWORK_`/`GRADES_` | core-edu | ⚠️ 待确认 |
|
||||
| `CONTENT_` | content | ⚠️ 待确认 |
|
||||
| `MSG_` | msg | ⚠️ 待确认 |
|
||||
| `AI_` | ai | ⚠️ 待确认 |
|
||||
| `BFF_TEACHER_` | teacher-bff | ⚠️ 待确认 |
|
||||
| `BFF_TEACHER_` | teacher-bff | ✅ 已裁决(G14) |
|
||||
| `GW_` | api-gateway | ✅ 已用 |
|
||||
| `NETWORK_` | 前端 | ai07 自有 |
|
||||
| `NETWORK_` | 前端 | ai13 自有 |
|
||||
|
||||
### 12.5 不产生 Kafka 事件
|
||||
|
||||
前端不发布/消费 Kafka 事件。WebSocket 推送由 push-gateway 消费 Kafka 转发。
|
||||
|
||||
## 13. 实施路线(ai07 自用)
|
||||
## 13. 实施路线(ai13 自用,详见 [workline.md](../../../docs/architecture/issues/worklines/teacher-portal_workline.md))
|
||||
|
||||
### P2 收尾(teacher-portal 审计对齐)
|
||||
### P2(MF Shell + GraphQL client + 基础页面,ARB-001/002)
|
||||
|
||||
1. 建 `packages/ui-tokens/`(三层设计令牌)+ `packages/ui-components/`(ErrorBoundary/RequirePermission/Loading/Empty)+ `packages/hooks/`(usePermission/useAuth)
|
||||
2. teacher-portal 引入 TanStack Query + Zustand + nuqs + react-hook-form
|
||||
3. 抽取 `lib/api.ts` 统一 API 请求层
|
||||
4. AppShell 改用 `usePermission()`,删除 `user.roles.join(", ")` 硬编码
|
||||
5. globals.css / tailwind.config.js 迁移到 ui-tokens 三层令牌
|
||||
6. 引入 next-intl + i18n key 路由
|
||||
7. 引入 ESLint flat config 自定义规则(no-hardcoded-fonts / design-tokens)
|
||||
1. ✅ packages 骨架(ui-tokens/ui-components/hooks,批次 0.15 已完成)
|
||||
2. NextFederationPlugin 配置(ARB-002 exposes + shared,**无 remotes**)
|
||||
3. GraphQLProvider + urql client 单例(Shell 暴露,§2.17 方案 A)
|
||||
4. AppShell(Layout + 侧边栏 + 路由守卫 + ErrorBoundary + 设计令牌三层)
|
||||
5. 登录页(`POST /api/auth/login` → JWT 存 localStorage,F12)
|
||||
6. 权限上下文(usePermission + useViewports,权限点 `<RESOURCE>_<ACTION>` F7)
|
||||
7. Dashboard 框架(`dashboard` GraphQL Query)+ 班级列表(`classes`/`class` Query)+ 学生列表 + 个人设置
|
||||
8. 补 ErrorBoundary + /api/health route
|
||||
9. 配置 next.config.js Module Federation(Shell 角色)
|
||||
9. 引入 ESLint flat config 自定义规则(no-hardcoded-fonts / design-tokens)
|
||||
10. 补 Vitest 单测 + Playwright E2E(覆盖率 ≥ 80%)
|
||||
|
||||
### P3(考试/作业/成绩 + 乐观更新 + 多 Tab 同步)
|
||||
|
||||
1. 考试/作业/成绩管理页面(GraphQL Query + Mutation)
|
||||
2. 乐观更新(urql mutation onMutate 回滚 + invalidate)
|
||||
3. 多 Tab 会话同步(BroadcastChannel,03 §2.5)
|
||||
|
||||
### P5(推送 + AI 接入)
|
||||
|
||||
1. teacher-portal 接入 WebSocket(push-gateway)
|
||||
@@ -553,10 +607,11 @@ packages/ui-tokens/
|
||||
|
||||
1. Web Vitals + OTel browser SDK 接入
|
||||
2. A11y WCAG 2.2 AA 审计
|
||||
3. 性能优化(MF shared 单例验证、bundle 分析)
|
||||
3. localStorage → httpOnly Cookie 迁移
|
||||
4. 性能优化(MF shared 单例验证、bundle 分析)
|
||||
|
||||
---
|
||||
|
||||
**AI Agent**: ai07 (teacher-portal shell)
|
||||
**Branch**: docs/teacher-portal-stage1-stage2-design-ai07
|
||||
**AI Agent**: ai13 (teacher-portal shell)
|
||||
**Branch**: feat-review-teacher-portal-docs-vErc0L
|
||||
**Coordinator**: coord-ai
|
||||
|
||||
@@ -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)
|
||||
> 说明:本契约基于 GraphQL(ARB-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 §5:admin 权限点 `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 配置风格) |
|
||||
| shared(singleton) | 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 返回固定 JWT(admin 角色) |
|
||||
| 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 不走 MF,Shell 独占)。管理员登录后按 admin 角色重定向到 `/admin/dashboard`。开发期 mock 登录由 Shell 的 MSW handler 提供(admin 角色 JWT + permissions=["*"])。
|
||||
|
||||
### 2.4 GraphQL 查询域(经 api-gateway 代理到 teacher-bff admin 命名空间)
|
||||
|
||||
> 依赖 teacher-bff admin 命名空间 schema(ARB-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 就绪(ai13,ARB-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 + UserInfo(admin 角色,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 JWT(admin 角色),存入 httpOnly cookie
|
||||
- 连接后每 30 秒推送 1 条 mock 系统通知(审计告警/异常登录)
|
||||
- **JWT mock**:使用固定 mock JWT(admin 角色,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 /ws(admin-portal 实时通知) | ai02 | ⚠️ 待 coord 仲裁(ISSUE-006) |
|
||||
| `packages/contracts` ADMIN_* 权限点常量 | coord | ⏳ |
|
||||
|
||||
@@ -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 | ⏳ 待补 proto(ISSUE-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 | ⏳ 待补 proto(ISSUE-03) |
|
||||
|
||||
### 1.2 HTTP 端点(如有)
|
||||
> **RPC 总数**:P5 目标 6 RPC(ai12 建议,见 ISSUE-03)。备课工作流的"查询状态/确认入库"用 HTTP 端点实现,避免 RPC 膨胀;如 coord 裁定需 gRPC 则扩到 8 RPC(追加 GetLessonPlanStatus / ConfirmLessonPlan)。
|
||||
> **proto 现状**:ai.proto 仅 4 RPC(Chat/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 RPC:StreamChat / 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>` | 学校用量(管理员) | ⏳ 待实现 |
|
||||
|
||||
> **响应信封**:所有响应必须为 ActionState(004 §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 | AIUsageEvent(operation: 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/3002(HTTP 端口),应以 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 Provider(OpenAI/百川/本地) | POST /v1/chat/completions | 调用大模型生成内容 | 开发期使用本地 mock server 返回固定 ChatResponse,不消耗真实 token |
|
||||
| LLM Provider(OpenAI/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 + 字段扩展)—— coord(ISSUE-03)
|
||||
- [ ] events.proto 补 `AIUsageEvent` message —— coord(ISSUE-04)
|
||||
- [ ] ai 用量事件 topic 命名裁决 —— coord(ISSUE-02)
|
||||
- [ ] content gRPC 50054 启用(ai09)—— 知识点维度 + 题库检索 + 入库
|
||||
- [ ] data-ana gRPC 50055 启用(ai11,可选)—— 学生薄弱点(可降级独立运行)
|
||||
- [ ] iam `GetEffectiveDataScope` RPC P4 补全(ai06 + coord,ISSUE-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 返回固定 ChatResponse(content="这是 AI 助手的模拟回复")
|
||||
- AiService.StreamChat 返回固定流(3 个 ChatChunk,最后一个 done=true)
|
||||
- AiService.GenerateQuestion 返回固定 GeneratedQuestion(question/answer/explanation)
|
||||
- AiService.GenerateLessonPlan 返回固定 LessonPlan(3 个 LessonSection)
|
||||
- AiService.StreamGenerateQuestion 返回固定流(2 个 GeneratedQuestion)
|
||||
- AiService.GenerateLessonPlan 返回固定 LessonPlanResponse(workflow_id + 3 个题目)
|
||||
- AiService.StreamGenerateQuestion 返回固定流(2 个题目逐字 chunk)
|
||||
- AiService.OptimizeExpression 返回固定 OptimizedExpression
|
||||
- **Kafka mock**:ai 就绪前不发布真实 AIUsageEvent,data-ana 仪表盘 AI 用量显示"暂无数据"
|
||||
|
||||
### 4.2 我消费的 mock
|
||||
@@ -105,4 +182,22 @@
|
||||
- **LLM Provider mock**:本地启动 mock server,POST /v1/chat/completions 返回固定 JSON(不消耗真实 token,不产生费用)
|
||||
- **content 知识点**:内置固定知识点表(数学 20 个知识点 + 前置依赖关系),不依赖 content gRPC
|
||||
- **data-ana 薄弱点**:内置固定学生薄弱点(2 个 weak_points),不依赖 data-ana gRPC
|
||||
- **事件订阅**:不订阅 content 事件,知识点维度表静态
|
||||
- **iam DataScope**:内置固定 DataScope(SCHOOL 级),不依赖 iam GetEffectiveDataScope
|
||||
- **事件订阅**:P5 不订阅任何事件,无 mock 需要
|
||||
|
||||
---
|
||||
|
||||
## §5 契约待裁决项汇总
|
||||
|
||||
> 以下字段待 coord 裁决后最终定稿,ai12 当前按建议方案先行实现。
|
||||
|
||||
| 待裁决项 | ISSUE | ai12 建议方案 | 影响章节 |
|
||||
| --------------------------------- | ------ | ---------------------------------------------- | -------------- |
|
||||
| ai gRPC 端口(50057 vs 50058) | ISSUE-01 | 50058(port-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 |
|
||||
| 备课工作流 Temporal(P6 决策点) | ISSUE-06 | P5 用 BackgroundTasks + Redis,P6 评估 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 |
|
||||
|
||||
@@ -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
|
||||
> 版本:v2(2026-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 | Path(Gateway 外部路径) | 目标服务 | 端口 | 鉴权 | 公开子路径 | 阶段 |
|
||||
| ------ | --------------------------------------------------- | ------------------------- | ----- | ---- | --------------------------------------- | ---- |
|
||||
| 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-bff(GraphQL) | 3003 | JWT | — | P2 |
|
||||
| ANY | /api/v1/student/v1/*path + /api/v1/student/v1 | student-bff(GraphQL) | 3009 | JWT | — | P3 |
|
||||
| ANY | /api/v1/parent/v1/*path + /api/v1/parent/v1 | parent-bff(GraphQL) | 3010 | JWT | — | P4 |
|
||||
| ANY | /api/v1/classes/v1/*path + /api/v1/classes/v1 | core-edu(classes 合并) | 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 验签,不解析 GraphQL(GraphQL 由 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 公钥集(JWKS),TTL 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 Fetcher(HTTP 拉 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/push(WebSocket 协作评估)| 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**:使用 MSW(Mock Service Worker)或本地 nginx 拦截
|
||||
- /api/auth/login 返回固定 JWT(mock 签发)+ 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` 返回固定 JWT(mock 签发)+ UserInfo
|
||||
- `/api/v1/teacher/v1/*` `/api/v1/student/v1/*` `/api/v1/parent/v1/*` 直接返回各 BFF 的 mock GraphQL 响应
|
||||
- `/healthz` `/readyz` 返回 200
|
||||
- **JWT mock**:前端开发期使用固定 mock JWT(api-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 JWT(DevMode 下生效)
|
||||
- **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 |
|
||||
|
||||
@@ -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 对外契约统一为 gRPC(REST 端点仅自身管理面用,不作为下游消费契约)
|
||||
|
||||
---
|
||||
|
||||
## §0 契约状态总览
|
||||
|
||||
| 维度 | 当前状态 | 目标状态(P4 完成) |
|
||||
| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
|
||||
| gRPC | ❌ 未实现([content.proto](../../../packages/shared-proto/proto/content.proto) 仅 5 RPC 定义) | ✅ 21 RPC(4 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 总数**:21(TextbookService 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 = +2;TextbookService 补 Update + Delete = +2;QuestionService 原 contract 已含 Publish/Search,design 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 | KnowledgePointEvent(action: created/updated/prerequisite_added/prerequisite_removed) | data-ana / ai / Neo4j Sync Worker / ES Sync Worker |
|
||||
| edu.content.question.events | QuestionEvent(action: created/updated/published/deleted) | data-ana |
|
||||
message Chapter {
|
||||
string id = 1;
|
||||
string textbook_id = 2;
|
||||
string title = 3;
|
||||
int32 order = 4; // DB 列名 order_num,proto 字段名 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 两类 topic,Textbook/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-005):content 是 core-edu 的上游(core-edu 调 content 查知识点),不是下游。content 不消费 core-edu 事件。
|
||||
|
||||
### 2.2 Kafka 事件订阅(异步)
|
||||
|
||||
| Topic | Event | 发布方 | mock 策略 |
|
||||
| ---------------- | --------------------------------- | --------------- | ----------------------------------------------------- |
|
||||
| edu.class.events | ClassEvent(action: 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 mock(grpc-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 就绪前不发布真实事件,下游使用本地 stub(data-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 个 Question(source=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 topic(kp + 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 RPC(TextbookService 3 + KnowledgeGraphService 2),无 Chapter/Question | 21 RPC(4 Service 完整) |
|
||||
| events.proto | 4 message(ClassEvent/ExamEvent/HomeworkEvent/GradeEvent) | 8 message(+ TextbookEvent/ChapterEvent/KnowledgePointEvent/QuestionEvent) |
|
||||
|
||||
---
|
||||
|
||||
## §6 变更记录
|
||||
|
||||
| 日期 | 版本 | 变更内容 | 变更人 |
|
||||
| ---------- | ---- | ---------------------------------------------------------------------------------------------- | ------ |
|
||||
| 2026-07-09 | v1.0 | 初版(18 RPC,2 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 |
|
||||
|
||||
@@ -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 RPC(ExamService 5 + HomeworkService 4 + GradeService 5)
|
||||
- P3 目标态:5 Service / 27 RPC(+ClassService 4 + AttendanceService 4 + P3 新增 5 RPC)
|
||||
- 增量:13 RPC(ClassService 4 + AttendanceService 4 + PublishExam/SubmitExam/GradeExam/GradeHomework/UpdateGrade 5)
|
||||
|
||||
### 1.2 HTTP 端点(REST,端口 3004)
|
||||
|
||||
> 当前为 REST 入口(P2 已就绪),P3 启用 gRPC 后 REST 保留为 BFF 兼容入口
|
||||
> 所有端点走 AuthMiddleware + PermissionGuard + Zod ValidationPipe + GlobalErrorFilter(ActionState 信封)
|
||||
|
||||
| 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) | ✅ P2(P3 补 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 | ExamEvent(action: created/updated/deleted) | msg / data-ana |
|
||||
| edu.homework.events | HomeworkEvent(action: assigned/submitted/graded) | msg / data-ana |
|
||||
| edu.grade.events | GradeEvent(action: recorded/updated) | msg / data-ana |
|
||||
| edu.class.events | ClassEvent(action: 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 | Event(action) | 触发时机 | 消费方 | 状态 |
|
||||
| ----- | --------------- | -------- | ------ | ---- |
|
||||
| `edu.teaching.exam.created` | ExamEvent.created | CreateExam 事务内 | msg / data-ana / push-gateway | ⏳ P3(TOPIC_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 | ⏳ P3(topic 待 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 校验) | P4(content 就绪后) |
|
||||
| core-edu | temporal | Workflow.start(examPublishWorkflow) | 考试发布编排工作流 | P3(Temporal 部署后) |
|
||||
|
||||
> 注:core-edu **不主动调 iam gRPC**,通过 Kafka 事件接收 iam 用户变更(见 §2.2)。
|
||||
> P3 不调 content(题库 question_id 仅作外键引用,不校验存在性)。
|
||||
|
||||
### 2.2 Kafka 事件订阅(异步)
|
||||
|
||||
| Topic | Event | 发布方 | mock 策略 |
|
||||
| ------------------- | --------------------------------------------------------- | ---------- | -------------------------------------------------------------------- |
|
||||
| edu.iam.user.events | UserEvent(action: created/updated/deleted/role_changed) | iam (ai06) | iam 就绪前不订阅,使用本地内置用户数据(teacher_id/student_id 固定) |
|
||||
| edu.iam.role.events | RoleEvent(action: 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 去重 | P4(P3 可选) |
|
||||
|
||||
> 注:当前 `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 50054(P4) | ai09 | HealthService.Check = SERVING | ⏳(阻塞 P4.2 知识点关联) |
|
||||
| data-ana gRPC 50055(P4) | ai11 | HealthService.Check = SERVING | ⏳(阻塞 P4.1 mastery 消费) |
|
||||
| msg gRPC 50056(P5) | 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 + /readyz(DB 探针)+ REST CRUD(exams/homework/grades)+ Outbox | teacher-bff(REST 调用) | ✅ |
|
||||
| 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.GetKnowledgePoints,lessons.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) |
|
||||
|
||||
@@ -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)
|
||||
> 修订:v2(2026-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 RPC(GetClassPerformance / 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 | MasteryEvent(action: 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 缓存 5min(key: `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 message(ISSUE-002),P5 前需 coord 补全。
|
||||
|
||||
### 2.3 HTTP 调用(如有)
|
||||
|
||||
无。
|
||||
无。data-ana 不通过 HTTP 调用上游服务。
|
||||
|
||||
### 2.4 CDC 数据源(补充)
|
||||
### 2.4 CDC 数据源
|
||||
|
||||
| 数据源 | 用途 | mock 策略 |
|
||||
| ----------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------- |
|
||||
| core-edu MySQL(exams/homework/grades/attendance 表) | Debezium CDC → Kafka 同步读模型 | core-edu 就绪前使用 ClickHouse 内置模拟数据集(30 学生 × 5 考试 × 10 作业) |
|
||||
| content MySQL(knowledge_points 表) | Debezium CDC → 知识点元数据同步 | content 就绪前使用内置固定知识点表(数学 50 个知识点) |
|
||||
| iam MySQL(users 表) | 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 RPC(ai06/coord,ISSUE-001)—— P4 阻塞项
|
||||
- [ ] analytics.proto 扩展至 12 RPC(coord/ai11,ISSUE-003)—— gRPC stub 生成前提
|
||||
- [ ] events.proto 补全 AIUsageEvent message(coord,ISSUE-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 就绪前不发布真实 MasteryEvent,msg 使用本地 stub 预警
|
||||
- **Kafka mock**:data-ana 就绪前不发布真实 MasteryUpdated / WarningTriggered,msg 使用本地 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`
|
||||
|
||||
@@ -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.16):REST 供 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 端点(如有)
|
||||
> 端口 50052,P2 即启用(I1 裁决)。⚠️ 当前 iam.proto 仅 4 RPC,待 coord 补全至 12 RPC(ISSUE-005)。
|
||||
|
||||
无对外 HTTP 端点,仅 gRPC。
|
||||
| Service | RPC | 请求 | 响应 | 端口 | 消费方 |
|
||||
| ---------- | ----------------------- | ------------------------------ | ---------------------------- | ----- | ---------------------------------------- |
|
||||
| IamService | Register | RegisterRequest | AuthResponse | 50052 | api-gateway(REST 透传)/ admin-portal |
|
||||
| IamService | Login | LoginRequest | AuthResponse | 50052 | api-gateway(REST 透传)/ admin-portal |
|
||||
| IamService | RefreshToken | RefreshTokenRequest | TokenPair | 50052 | api-gateway(REST 透传) |
|
||||
| IamService | Logout | LogoutRequest | LogoutResponse | 50052 | api-gateway(REST 透传) |
|
||||
| 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-gateway(JWKS 验签) |
|
||||
| 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 Set(Gateway 拉取验签) | 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` | 无 | readiness(5 依赖检查) | k8s / 监控 |
|
||||
| GET | `/metrics` | 无 | Prometheus 指标 | Prometheus |
|
||||
|
||||
### 1.3 GraphQL schema(如 BFF)
|
||||
|
||||
不适用。
|
||||
不适用。iam 是业务服务,不暴露 GraphQL(GraphQL 由 BFF 层提供)。
|
||||
|
||||
### 1.4 Kafka 事件发布(如有)
|
||||
|
||||
@@ -66,16 +88,23 @@
|
||||
|
||||
### 3.1 我依赖的上游就绪标志
|
||||
|
||||
无上游依赖。
|
||||
iam 是身份根服务,无业务上游依赖。但依赖以下基础设施契约(coord 提供):
|
||||
|
||||
| 依赖项 | 提供方 | 就绪标志 | 状态 |
|
||||
| ------ | ------ | -------- | ---- |
|
||||
| iam.proto 补全至 12 RPC | coord | proto 含 12 RPC + 全部 message | ❌ 仅 4 RPC(ISSUE-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 返回固定 AuthResponse(user.id="mock-user-001", tokens.access_token="mock-access-token")
|
||||
- **gRPC mock**(供 BFF):使用 grpc-mock 拦截 50052 端口,Register/Login 返回固定 AuthResponse(user.id="mock-user-001", tokens.access_token="mock-access-token")
|
||||
- **REST mock**(供 gateway / admin-portal):MSW 或 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 是身份根服务,无业务上游依赖)。
|
||||
|
||||
@@ -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-008(topic 命名)、ISSUE-009(RPC 数量)、ISSUE-013(events.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 RPC,02-architecture-design.md 设计 17 RPC。下表列出设计文档完整 17 RPC,标注基线 13 RPC(✅基线 / ➕扩展待仲裁)。coord 裁决后裁剪或放宽。
|
||||
|
||||
### 1.2 HTTP 端点(如有)
|
||||
#### NotificationService(9 RPC:5 基线 + 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)
|
||||
#### NotificationPreferenceService(2 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 | NotificationEvent(action: sent/read/recalled/failed) | push-gateway / data-ana |
|
||||
#### NotificationTemplateService(4 RPC:4 基线 + 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 RPC(9 基线表中 ✅ × 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_EXCEEDED(P5 新增 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-005):004 §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 | ExamEvent(action: created/updated/deleted) | core-edu (ai08) | core-edu 就绪前不订阅,使用本地 stub 事件触发 mock 通知 |
|
||||
| edu.homework.events | HomeworkEvent(action: assigned/submitted/graded) | core-edu (ai08) | 同上 |
|
||||
| edu.grade.events | GradeEvent(action: recorded/updated) | core-edu (ai08) | 同上 |
|
||||
| edu.class.events | ClassEvent(action: transferred) | core-edu (ai08) | 同上 |
|
||||
| edu.data_ana.mastery.events | MasteryEvent(action: 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 topic(edu.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 补齐 proto(D1)。补齐前用通用 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 走 gRPC,HTTP 仅为降级)。
|
||||
|
||||
---
|
||||
|
||||
@@ -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 | D1,ISSUE-013 |
|
||||
| events.proto GradeEvent 补 class_id;全部补 student_ids[] | coord | 🔴 阻塞 fan-out | D2,ISSUE-006 |
|
||||
| msg.proto 补 5 扩展 RPC + Preference/Template Service | coord | 🔴 阻塞 gRPC | D3-D4,ISSUE-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 就绪前不发布真实 NotificationEvent,push-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-event(02-architecture-design.md §5 + 004 §7.2) |
|
||||
| ISSUE-009 | RPC 数量(13 vs 17) | 🟡 | 17(02-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 单向) |
|
||||
|
||||
@@ -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,不暴露 gRPC(C2 仲裁) |
|
||||
| API 风格 | GraphQL Yoga(U3 仲裁,P4 直接 GraphQL,不走 REST 过渡) |
|
||||
| 权限校验 | BFF 豁免 @RequirePermission(U4 仲裁),仅校验 x-user-id + ChildGuard 越权防御 |
|
||||
|
||||
---
|
||||
|
||||
## §1 我提供什么(对外接口)
|
||||
|
||||
### 1.1 gRPC 接口(如有)
|
||||
### 1.1 gRPC 接口
|
||||
|
||||
无对外 gRPC。parent-bff 是 GraphQL 聚合层。
|
||||
无对外 gRPC。parent-bff 是 GraphQL 聚合层,对上游仅暴露 HTTP/GraphQL(C2 仲裁)。
|
||||
|
||||
### 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 就绪前返回固定 UserInfo(parent 角色) |
|
||||
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | iam 就绪前返回家长权限集 |
|
||||
| iam (ai06) | IamService.GetViewports | 家长导航菜单 | iam 就绪前返回固定视口列表 |
|
||||
| iam (ai06) | IamService.GetChildrenByParent | 查询关联孩子列表(核心) | iam 就绪前返回固定 2 个 ChildInfo(I3/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 | 获取当前家长信息 | 返回固定 UserInfo(parent 角色) | ✅ 已有 |
|
||||
| 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 #5,P3 补全) |
|
||||
| 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 #3,ai05 提请,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-007),GraphQL `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)—— **核心依赖 GetChildrenByParent(I3/ISSUE-047 裁决)**
|
||||
- [ ] iam gRPC 50052 启用(ai06)—— **核心依赖 GetChildrenByParent(I6 裁决,P0 阻塞)**
|
||||
- [ ] iam 补 GetViewports / GetEffectivePermissions RPC(coord-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 + Redis,02 §9 #7)
|
||||
- [ ] GraphQL schema 可内省(POST /graphql 返回 schema)
|
||||
- [ ] 核心 Query 可执行:currentUser / myChildren / childSummary / childGrades
|
||||
- [ ] 核心 Mutation 可执行:markAsRead
|
||||
- [ ] 数据范围校验生效(家长只能查自己孩子的数据,基于 iam.GetChildrenByParent 返回的 user_id 校验)
|
||||
- [ ] 核心 Query 可执行:dashboard / children / childGrades / childAnalytics
|
||||
- [ ] 核心 Mutation 可执行:selectChild(P4)/ markNotificationRead(P5)
|
||||
- [ ] 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**(P5):fetch mock 返回 success
|
||||
- **Redis**:Testcontainers 真实 Redis 实例(不用 mock)
|
||||
- **Kafka**(P5):kafkajs 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_id(student-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 仲裁 |
|
||||
|
||||
@@ -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-001(BFF GraphQL)、ARB-002(MF 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-006):parent-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 角色 | Remote(Shell = 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 shared(singleton) | react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks(ARB-002) |
|
||||
| dev/prod 端口 | 4002([port-allocation.md](../../../infra/port-allocation.md) §4) |
|
||||
| feature flag | `NEXT_PUBLIC_MF_ENABLED`(ARB-002,P4 默认开) |
|
||||
|
||||
> **注**(ISSUE-007):MF 配置文件统一为 `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 返回固定 JWT(parent 角色) |
|
||||
|
||||
> **注**(ISSUE-004):
|
||||
> - 登录端点统一为 `POST /api/v1/iam/login`(与 [matrix.md](./matrix.md) §5 `/api/v1/iam/*` + 01 §3.1 前缀一致)
|
||||
> - 登录是 parent-portal 唯一走 REST(非 GraphQL)的端点:登录前无 JWT,GraphQL 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-001(BFF 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.GetChildrenByParent(I3/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 shared(singleton):react / react-dom / urql / graphql / @tanstack/react-query / zustand / nuqs / @edu/ui-tokens / @edu/ui-components / @edu/hooks(ARB-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 exposes(AppShell + 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-010):iam `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 + UserInfo(parent 角色)
|
||||
- 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 + UserInfo(parent 角色)
|
||||
- **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-001(REST vs GraphQL) | §2.4 | 按 GraphQL 编写(依 ARB-001),待 coord 确认 |
|
||||
| ISSUE-004(登录端点) | §2.3 | 暂用 `POST /api/v1/iam/login`,待 coord 确认 |
|
||||
| ISSUE-006(HTTP 端点分类) | §1.2 | 已修正为"无对外 HTTP API" |
|
||||
| ISSUE-007(MF 配置文件名) | §1.6 | 已修正为 `next.config.js` |
|
||||
| ISSUE-008(GraphQL 路径前缀) | §2.4 | 已修正为 `/api/v1/parent/graphql` |
|
||||
| ISSUE-009(switchChild Mutation) | §2.4 | 列为待仲裁,标注两种方案 |
|
||||
| ISSUE-010(iam GetChildrenByParent 缺失) | §3.1 | P0 阻塞,用 mock 开发 |
|
||||
|
||||
详见 [objections/parent-portal_issue.md](../objections/parent-portal_issue.md)。
|
||||
|
||||
@@ -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)
|
||||
> 版本:v2(2026-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/online),HTTP 同步响应更直接;广播走 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 RS256(query `?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
|
||||
- 服务端自动回 Pong(gorilla/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 | NotificationEvent(action: 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 批次 1):iam gRPC 50052 + `/.well-known/jwks.json` 可访问
|
||||
- [ ] **msg gRPC + Kafka topic**(ai10 批次 4):msg gRPC 50056 + `edu.notification.requested` topic 有事件发布
|
||||
- [x] **Redis 基础设施**(coord P1):Redis 7.x 可访问 ✅ 已就绪
|
||||
- [x] **Kafka 基础设施**(coord P1):Kafka 可访问 ✅ 已就绪
|
||||
- [ ] **ISSUE-001~007 仲裁**(coord):[coord.md](../coord.md) 追加 ARB-003+ 仲裁章节
|
||||
|
||||
### 3.2 我的就绪标志(供下游消费)
|
||||
|
||||
- [ ] push-gateway HTTP :8081 启用(/healthz 返回 200)
|
||||
- [ ] /readyz 返回 200(含 Kafka 连通性检查通过)
|
||||
- [ ] 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 返 200(含 Redis/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 NotificationEvent(action=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 |
|
||||
|
||||
@@ -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 起直接 GraphQL(GraphQL 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 不订阅 Kafka,P5 后再订阅 | §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 | 降级模式方案 B(data 内 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 起直接 GraphQL(GraphQL 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.1:3 个 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. **消费方**:ai14(student-portal)基于仲裁版 schema 消费
|
||||
4. **变更流程**:
|
||||
- schema 变更需 PR + ai04(BFF)+ 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 |
|
||||
|
||||
> **分页**:所有列表 Query(myClasses / 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(通知未读数)+ ai(AI 助教入口)数据源
|
||||
- 未启用的下游字段返回 `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 key(F4 规范:`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 200,GraphQL `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 就绪前返回固定 UserInfo(student 角色) |
|
||||
| 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 就绪前返回固定 5 个 Grade |
|
||||
| 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 就绪前返回固定 UserInfo(student 角色) |
|
||||
| iam (ai06) | IamService.GetEffectivePermissions | 权限校验 | P3 | iam 就绪前返回学生权限集 |
|
||||
| iam (ai06) | IamService.GetViewports | 学生导航菜单 | P3 | iam 就绪前返回固定视口列表 |
|
||||
| iam (ai06) | IamService.GetEffectiveDataScope | DataScope 透传 | P3 | iam 就绪前返回 `SELF` |
|
||||
| core-edu (ai08) | ClassService.GetClass | 我的班级详情 | P3 | core-edu 就绪前返回固定 ClassInfo |
|
||||
| core-edu (ai08) | ClassService.ListStudentsByClass | 班级同学名单 | P3 | core-edu 就绪前返回固定 30 个 StudentInfo |
|
||||
| core-edu (ai08) | ExamService.ListExamsByClass | 我的考试 | P3 | core-edu 就绪前返回固定 2 个 Exam |
|
||||
| core-edu (ai08) | HomeworkService.ListHomeworkByClass | 我的作业 | P3 | core-edu 就绪前返回固定 3 个 Homework |
|
||||
| core-edu (ai08) | HomeworkService.SubmitHomework | 提交作业 | P3 | core-edu 就绪前返回 success=true |
|
||||
| core-edu (ai08) | GradeService.ListGradesByStudent | 我的成绩 | P3 | core-edu 就绪前返回固定 5 个 Grade |
|
||||
| core-edu (ai08) | AttendanceService.ListAttendanceByStudent | 我的考勤 | P3 | core-edu 就绪前返回固定 10 条 Attendance |
|
||||
| content (ai09) | TextbookService.ListTextbooks | 教材列表 | P4 | content 就绪前返回固定 5 个教材 |
|
||||
| content (ai09) | ChapterService.ListChapters | 章节列表 | P4 | content 就绪前返回固定章节树 |
|
||||
| content (ai09) | KnowledgeGraphService.GetLearningPath | 学习路径 | P4 | content 就绪前返回固定 8 个知识点推荐顺序 |
|
||||
| data-ana (ai11) | AnalyticsService.GetStudentDashboard | 学生仪表盘 | P4 | data-ana 就绪前返回固定仪表盘 |
|
||||
| data-ana (ai11) | AnalyticsService.GetStudentWeakness | 我的薄弱点 | P4 | data-ana 就绪前返回固定 3 个 weak_points |
|
||||
| data-ana (ai11) | AnalyticsService.GetLearningTrend | 学习趋势 | P4 | data-ana 就绪前返回固定趋势数据 |
|
||||
| msg (ai10) | NotificationService.ListNotifications | 学生通知 | P5 | msg 就绪前返回固定 10 条通知 |
|
||||
| msg (ai10) | NotificationService.GetUnreadCount | 通知未读数 | P5 | msg 就绪前返回固定 count=3 |
|
||||
| msg (ai10) | NotificationService.MarkAsRead | 标记已读 | P5 | msg 就绪前返回 success=true |
|
||||
|
||||
> **命名对齐**(coord §5.2):
|
||||
> - `AnalyticsService.GetStudentDashboard`(无 Stats 后缀,统一命名)
|
||||
> - `KnowledgeGraphService.GetLearningPath`(禁用 ContentService 命名)
|
||||
|
||||
### 2.2 Kafka 事件订阅(异步)
|
||||
|
||||
无。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 降级方案**:权限缓存用短 TTL(5min)兜底,不订阅 Kafka 事件。
|
||||
|
||||
### 2.3 HTTP 调用
|
||||
|
||||
**无**。依据 B2 裁决,student-bff 对下游全部走 gRPC,禁止 HTTP fetch。
|
||||
|
||||
### 2.4 DownstreamClient 抽象层(B8 裁决)
|
||||
|
||||
依据 B8 裁决:
|
||||
|
||||
1. **回写 teacher-bff**:DownstreamClient 作为 BFF 模式 v2 标准抽象,回写到 teacher-bff,3 个 BFF(teacher-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-edu(3 项)。
|
||||
|
||||
---
|
||||
|
||||
## §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 返回 200(P3 探针 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 真实就绪前,为 ai14(student-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 success(P5 启用前) |
|
||||
|
||||
> 所有上游 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 |
|
||||
|
||||
@@ -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)
|
||||
> 版本:v2(ai14 接管审计与补全版,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 Remote,ARB-002 对齐)
|
||||
|
||||
| 角色 | 说明 |
|
||||
| ---------------------- | ----------------------------------------------------------------- |
|
||||
| MF Remote | 学生门户是微前端远程模块,由 teacher-portal AppShell 或独立壳加载 |
|
||||
| 暴露的 remote 模块 | StudentApp(学生端完整应用)、shared 学生端组件 |
|
||||
| module federation 配置 | `apps/student-portal/module-federation.config.ts` |
|
||||
| 角色 | 说明 |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| MF Remote | student-portal 是微前端远程模块(P3 首个 Remote,ARB-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 / RequirePermission(ARB-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-ts(ARB-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 /ws(WebSocket) | 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/*` 保持命名一致性
|
||||
> - 由 ai01(api-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 Query(读,16 个)
|
||||
|
||||
| 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 Mutation(写,8 个)
|
||||
|
||||
| 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 Shell(ai13 P2)| exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
|
||||
| api-gateway HTTP :8080(ai01 P3) | `/api/v1/student/*` 反向代理 student-bff 可用 | P3 启动 | ⏳ 待 ai01 P3 |
|
||||
| student-bff GraphQL(ai04 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 50053(ai08 P3) | ExamService/HomeworkService/GradeService/AttendanceService/ClassService | P3 启动 | ⏳ 待 ai08 P3 |
|
||||
| iam gRPC 50052(ai06 P2) | GetUserInfo + GetEffectivePermissions + GetViewports | P3 启动 | ⏳ 待 ai06 P2 |
|
||||
| content gRPC 50054(ai09 P4) | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
|
||||
| data-ana gRPC 50055(ai11 P4) | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
|
||||
| push-gateway WebSocket :8081/ws(ai02 P5) | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
|
||||
| msg gRPC 50056(ai10 P5) | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
|
||||
| ai 服务 gRPC 50057(ai12 P5,可选) | AiService.Chat(SSE 流式) | 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 + UserInfo(student 角色)
|
||||
- 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 mock(MSW)
|
||||
|
||||
- `POST /api/v1/student/graphql` → 按 operationName 返回对应 mock 响应(见 §2.4)
|
||||
- `POST /api/auth/login` → 返回固定 JWT + UserInfo(student 角色)由 Shell 处理
|
||||
- `POST /api/v1/student/upload` → 返回固定 signed URL(待 ISSUE-014-05 仲裁后实现)
|
||||
- 所有 mock 响应定义在 `apps/student-portal/src/mocks/fixtures/*.json`
|
||||
|
||||
#### 4.2.2 WebSocket mock(mock-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` 存储(前端可独立测试断网恢复逻辑)
|
||||
- 不需要 mock,IDB 在浏览器原生支持
|
||||
|
||||
---
|
||||
|
||||
## §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.3(auth/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**: ai14(student-portal)
|
||||
**Branch**: feat-review-student-portal-docs-9yN6Av
|
||||
**Coordinator**: coord-ai
|
||||
|
||||
@@ -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)
|
||||
> 版本:v2(2026-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 启动前仲裁第一版。
|
||||
> 前端 AI(ai13/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.5,P2 班级列表来自 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.AttendanceService(C4 裁决 P3 补全) | P3+ |
|
||||
| **content** | textbooks / chapters / knowledgePoints / questions | content 4 个 Service | P4+ |
|
||||
| **notifications** | myNotifications / markAsRead | msg.NotificationService | P5+ |
|
||||
| **ai** | aiChat / generateQuestion / generateLessonPlan | ai.AiService(A4 裁决 P5 补全 GenerateLessonPlan) | P5+ |
|
||||
| **admin.\*** | admin.schoolStats / admin.listClasses / admin.listTeachers 等 | admin 命名空间,复用 teacher-bff endpoint(president §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 endpoint,ai03 在 P6 实现 admin Resolver 真实数据
|
||||
- **理由**:admin 操作低 QPS,无需独立 admin-bff 服务;减少 ai16 工作量
|
||||
|
||||
### 1.4 Kafka 事件发布(如有)
|
||||
|
||||
无。teacher-bff 不发布事件,仅做 gRPC 聚合。
|
||||
无。teacher-bff 不发布事件,仅做 gRPC 聚合(B7 裁决:P2-P4 不订阅 Kafka,P5 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 就绪前返回固定 UserInfo(teacher 角色) |
|
||||
| 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 | 返回固定 UserInfo(teacher 角色) |
|
||||
| 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 RPC(Register/Login/RefreshToken/GetUserInfo),matrix.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=true(student-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=true(C4 裁决补全) |
|
||||
| AttendanceService.GetClassAttendance | 查询考勤 | ❌ | P3 | 返回固定考勤数据(C4 裁决补全) |
|
||||
|
||||
> core_edu.proto 现状 14 RPC(ExamService 5 + HomeworkService 4 + GradeService 5),matrix.md §2 标称 22 RPC 5 Service,待 coord 按 C4/C5 裁决补 AttendanceService + ClassService(GetClassesByTeacher / 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 RPC(TextbookService 3 + KnowledgeGraphService 2),matrix.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+ | 流式 mock(D4 裁决补全) |
|
||||
|
||||
> analytics.proto 现状 3 RPC,matrix.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 RPC(NotificationService),matrix.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 | 流式 mock(SSE) |
|
||||
| AiService.GenerateQuestion | AI 出题 | ✅ | P5 | 返回固定题目 |
|
||||
| AiService.OptimizeExpression | 表达优化 | ✅ | P5 | 返回固定优化结果 |
|
||||
| AiService.GenerateLessonPlan | AI 备课 | ❌ | P5 | 返回固定教案(A4 裁决补全) |
|
||||
| AiService.StreamGenerateQuestion (stream) | 流式出题 | ❌ | P5 | 流式 mock(A4 裁决补全) |
|
||||
|
||||
> ai.proto 现状 4 RPC,matrix.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 裁决),上游就绪后移除拦截器切换真实调用。
|
||||
|
||||
@@ -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 存入 localStorage,F12;P6 迁移 httpOnly cookie)
|
||||
- [ ] GraphQL 查询可执行(dashboard / viewports / me / classes / class Query,ARB-001)
|
||||
- [ ] WebSocket 通知可接收(push-gateway 推送 → 前端通知中心更新)
|
||||
|
||||
---
|
||||
@@ -118,9 +118,9 @@ teacher-portal 是最前端,无下游消费方。但对开发体验提供:
|
||||
|
||||
- **HTTP/GraphQL mock**:使用 MSW(Mock 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 公钥配对),存入 localStorage(F12;P6 迁移 httpOnly cookie)
|
||||
- **环境切换**:通过 `NEXT_PUBLIC_API_MOCKING=enabled` 环境变量控制是否启用 MSW,上游就绪后设为 `disabled`
|
||||
|
||||
@@ -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-001(teacher-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 的归属与时间点——是否由 ai03(teacher-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-002(MF 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 | 同类 portal(parent-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-ai16:01/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) 头部均标注"AI:ai07(TS/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 标识与分支命名更正为 ai16;ai07 在 admin-portal 的产出视为历史草稿,由 ai16 接管修订。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
- **提请方**:aiXX
|
||||
- **日期**:YYYY-MM-DD
|
||||
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
|
||||
- **描述**:[详细描述问题]
|
||||
- **建议方案**:[AI 的建议]
|
||||
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X)
|
||||
-->
|
||||
### ISSUE-002-ai16:01/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-ai16:01/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/002(2026-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-ai16:01/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-ai16:teacher-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-ai16:01/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-ai16:matrix.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 历史问题
|
||||
|
||||
(暂无已裁决问题)
|
||||
|
||||
@@ -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-001(coord.md §1) | teacher-bff GraphQL schema 第一版 | ❌ 否 | 与 ai 无关,不适用。 | — |
|
||||
| ARB-002(coord.md §2) | MF Shell 暴露清单 | ❌ 否 | 与 ai 无关,不适用。 | — |
|
||||
| port-allocation.md §7 | 50058 让给 ai(push-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.md(50058 已定)与 004 §2.3(Temporal 部分仲裁)。ai 文档对"004 §7.2/§1.2"的两处引用失实,需修正引用源。
|
||||
|
||||
---
|
||||
|
||||
## 问题列表
|
||||
|
||||
### ISSUE-01-ai12:matrix.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 50058(2026-07-09 coord 仲裁"push-gateway 豁免 gRPC,释放 50057;50058 让给 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-ai12:ai 用量事件 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` message,004 无 topic 记录。三义导致 data-ana 消费方无法对齐。
|
||||
- **建议方案**:ai12 建议采用 `edu.ai.usage`(与 matrix.md 一致,最简短,符合 `edu.<domain>.<action>` 简洁约定)。请 coord 裁定并在 004 §7.2 补登 + events.proto 补 `AIUsageEvent` message(schema 见 02-architecture-design.md §3.3)。ai12 据裁决同步 01/02 文档与 contract。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-03-ai12:ai.proto 待补全(备课工作流 RPC + 字段扩展)
|
||||
|
||||
- **提请方**:ai12
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确 / 前置依赖缺失
|
||||
- **描述**:ai.proto 现状仅 4 RPC(Chat / 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 RPC;02-architecture-design.md §4.2 单方面扩到 8 RPC(追加 GetLessonPlanStatus / ConfirmLessonPlan)。需 coord 裁定 P5 目标 RPC 清单。
|
||||
- **建议方案**:ai12 建议 P5 目标 6 RPC(Chat / 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-ai12:events.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 文档已提请(A3),matrix.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-ai12:contracts/ai_contract.md 与设计文档多处矛盾(ai12 自查自纠清单)
|
||||
|
||||
- **提请方**:ai12
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确 / 文档不同步
|
||||
- **描述**:现有 contracts/ai_contract.md(coord 模板,ai12 接管前未细化)与 01/02 设计文档存在 5 处矛盾:
|
||||
1. §1.1 gRPC 端口 50057(应为 50058,见 ISSUE-01)
|
||||
2. §1.2 "无对外 HTTP 端点,仅 gRPC"——错误。api-gateway 代理 `/api/v1/ai/*` → ai HTTP(main.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:备课工作流是否引入 Temporal(P6 决策点)
|
||||
|
||||
- **提请方**:ai12
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:工作量超批 / 前置依赖缺失
|
||||
- **描述**:004 §2.3 列出 Temporal 用于"AI 编排",属部分仲裁。ai12 在 02-architecture-design.md §8.3 建议:P5 用 FastAPI BackgroundTasks + Redis(24h TTL)实现 4 步备课工作流;P6 评估迁移 Temporal(支持长运行跨天审核 + 复杂状态机)。需 coord 在 P6 决策点确认是否引入,避免 P5 实现被推翻重做。
|
||||
- **建议方案**:P5 采用 BackgroundTasks + Redis(02-architecture-design.md §2.4 状态机已设计);P6 由 coord 评估 Temporal 引入时机。请 coord 在 roadmap 标注 P6 决策点。
|
||||
- **状态**:待 coord 仲裁(P6 决策点)
|
||||
|
||||
### ISSUE-07-ai12:iam GetEffectiveDataScope gRPC RPC 待 P4 补全
|
||||
|
||||
- **提请方**:ai12
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:ai 多租户用量配额校验需调用 `IamService.GetEffectiveDataScope` 查询用户 DataScope。01/02 文档已提请(A4),coord 已仲裁 P4 补全(§15.3 #5),但 iam.proto 现状未见此 RPC。ai P5 实施时依赖,若 P4 未补全将阻塞配额校验功能。
|
||||
- **建议方案**:请 coord 确认 iam (ai06) 在 P4 已补全 `GetEffectiveDataScope` RPC;ai12 在 P5 实施时调用,Redis 缓存 5min。若 P4 未补全,ai 降级为"仅按 user_id 配额,不按 school_id"。
|
||||
- **状态**:待 coord 确认 P4 补全情况
|
||||
|
||||
### ISSUE-08-ai12:proto 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 文档已提请(A5),contracts 未记录。P5 实施必须整改。
|
||||
- **建议方案**:P5 实施时所有 HTTP 端点 + gRPC RPC 返回值改为 ActionState;degraded 作为 `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)
|
||||
-->
|
||||
|
||||
(暂无问题)
|
||||
|
||||
@@ -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)
|
||||
-->
|
||||
|
||||
(暂无问题)
|
||||
> 负
|
||||
@@ -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 必须引入 Outbox(004 §12.2 强制条款) | §3.1.5 content_outbox_events 表 + §5.4 Outbox Publisher | ✅ 已落实 |
|
||||
| C4 | P4 必须实现 gRPC controller | §4.2 gRPC API(4 个 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-ai09:REST 端点设计文档与现有实现不一致
|
||||
|
||||
- **提请方**: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-ai09:content 发布事件 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-ai09:Textbook/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-ai09:gRPC 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 | 3(Create/List/Get) | 4(含 Update,无 Delete) | — |
|
||||
| KnowledgeGraphService | 4 | 4 | — |
|
||||
| QuestionService | 6(无 Publish/Search) | 7(含 Publish/Search) | — |
|
||||
| **合计** | **18** | **18** | **18** |
|
||||
|
||||
- design doc 缺 QuestionService.PublishQuestion / SearchQuestions(contract 有)
|
||||
- contract 缺 TextbookService.Update/Delete(design doc 有)
|
||||
- design doc ChapterService 缺 Update(contract 有);contract ChapterService 缺 Delete(design doc 也缺)
|
||||
- **建议方案**:以 contract.md 为契约唯一源(已对齐 matrix.md 18 RPC 总数),反向修正 design doc:
|
||||
1. TextbookService 补 Update/Delete(与 contract 对齐)
|
||||
2. ChapterService 补 Update + Delete(design 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-ai09:core-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.3:AI 禁止 `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-ai09:questions 表 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-ai09:knowledge-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-ai09:Neo4j 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 仲裁 |
|
||||
|
||||
@@ -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-ai08:core_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 RPC:GetClass / GetClassesByTeacher / BatchGetClasses / ListStudentsByClass)
|
||||
- `AttendanceService`(4 RPC:RecordAttendance / 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 RPC(5+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-ai08:events.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 message(ClassEvent/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.md(core-edu) | coord.md §1.2 GraphQL schema(teacher-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` |
|
||||
| SubmissionStatus(exam) | `in_progress / submitted / graded` | `NOT_SUBMITTED / SUBMITTED / GRADED` | core-edu 用 `in_progress`,coord 用 `NOT_SUBMITTED` |
|
||||
| SubmissionStatus(homework) | `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-ai08:class.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`(ClassEvent,action: 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-ai08:core-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 8(5+3 新增)+ HomeworkService 5(4+1 新增)+ GradeService 6(5+1 新增)+ AttendanceService 4 = **27 RPC**。
|
||||
- **影响**:
|
||||
- matrix.md §2 声明 22 RPC 但含 ClassService,02 文档声明 22 RPC 但不含 ClassService,下游 AI 无法判断应实现多少 RPC
|
||||
- 就绪信号"core-edu gRPC 50053 + 22 RPC"含义模糊
|
||||
- **建议方案**:
|
||||
coord 统一 RPC 统计口径:
|
||||
- matrix.md §2 改为 "27 RPC(P3 全量: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-ai08:core-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-ai08:core-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 仲裁
|
||||
|
||||
@@ -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` RPC,P4 补全,data-ana gRPC 调用 | iam | ⚠️ **未落实**:iam.proto 当前仅 4 RPC(Register/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 message(Class/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 服务信封改为 ActionState(degraded 放 details 子字段) | ai11 | ✅ **已对齐**:02 §4.3 ActionState 实现已修正,degraded 移至顶层 details |
|
||||
| 7 | §6 #4 | 同 #6,ai06 修正 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.5,01/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 直接相关的 proto(iam/analytics/events)。§8.2 声明与实际文件严重不符,可能原因:(a) 声明为计划态但未执行;(b) 执行后未提交到本 worktree 分支;(c) 在其他分支已执行但未合并。无论哪种原因,data-ana 的 P4 实现依赖这些 proto 补全,当前实际状态构成 P4 阻塞。
|
||||
|
||||
---
|
||||
|
||||
## §1 问题列表
|
||||
|
||||
### ISSUE-001-ai11:iam.proto 缺 GetEffectiveDataScope RPC(P4 阻塞)
|
||||
|
||||
- **提请方**:ai11
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:coord-cross-review.md §2.2 #3 已仲裁"iam P4 补全 `GetEffectiveDataScope` RPC,data-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-ai11:events.proto 缺 AIUsageEvent message(P5 阻塞,P4 预备)
|
||||
|
||||
- **提请方**:ai11
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:coord-cross-review.md §3.2 已仲裁"补登 `edu.insight.ai.usage` topic + events.proto 补 `AIUsageEvent` message",但 events.proto 当前仅 4 message(ClassEvent/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-ai11:analytics.proto 仅 3 RPC,coord-cross-review §8.2 声称已扩展至 12 RPC 但实际未落实(P4 阻塞)
|
||||
|
||||
- **提请方**:ai11
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失 + 声明与实际不符
|
||||
- **描述**:coord-cross-review.md §8.2 声称"analytics.proto 扩展 ✅ 12 RPC(含 Stream)",但实际文件仅 3 RPC(GetClassPerformance/GetStudentWeakness/GetLearningTrend)。ai-allocation.md §5 与 matrix.md §2 均要求 data-ana 提供 12 RPC,02-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-ai11:coord-cross-review §6 整改清单 coord 责任项未落实,导致 004 章节引用断裂
|
||||
|
||||
- **提请方**:ai11
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:coord-cross-review.md §6 整改清单中标注"coord"责任的 4 项整改未在 004 正文中落实:
|
||||
- #7:004 §7.2 补登 6 个 topic + CDC 命名规范 → 004 正文无对应段落
|
||||
- #8:004 §1.2 新增 HTTP/gRPC 端口两列 → 004 §1.2 服务清单无端口列
|
||||
- #9:004 §4.1 补充 gRPC 启用阶段矩阵 → 004 正文无 §4.2 子节
|
||||
- #10:004 §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-ai11:data-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-ai11:coord-cross-review §8.2 批次 0 产出声明与 proto 实际文件状态严重不符
|
||||
|
||||
- **提请方**:ai11
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:其他(声明与实际不符)
|
||||
- **描述**:coord-cross-review.md §8.2 声称批次 0 已完成 iam.proto(12 RPC)/ analytics.proto(12 RPC)/ events.proto(9 message)补全,但 ai11 逐文件核查发现实际均未补全(详见 §0.3 核查表)。这影响所有依赖这些 proto 的下游 AI 的排期评估——若 AI 信任 §8.2 声明,会在排期中忽略 proto 补全的等待时间,导致排期失真。
|
||||
- **建议方案**:coord 核实 §8.2 声明真实性。若实际已补全但未合并到各 worktree 分支,请协调合并;若实际未补全,请更新 §8.2 状态为"计划中"或"待执行",并明确补全时间点,以便下游 AI 据此排期。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
@@ -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-I8(iam 专项)核查
|
||||
|
||||
| 裁决 | 内容摘要 | 核查结论 | 证据 |
|
||||
| ---- | -------- | -------- | ---- |
|
||||
| I1 | P2 即启用 gRPC server 50052,REST + gRPC 并存 | ⚠️ **02 文档未回写**:[02-architecture-design.md](../../../services/iam/docs/02-architecture-design.md) §1/§7.1/§8.1 决策点 5 仍写"P2 仅 REST,P3 随 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` map(admin/teacher);02 文档 §8.1 决策点 9 描述为"待改造" | [permission.guard.ts](../../../services/iam/src/middleware/permission.guard.ts) |
|
||||
| I4 | 首次实现即注册 AuthMiddleware,Controller 用 @Req() 注入 | ❌ **源码未注册**:[app.module.ts](../../../services/iam/src/app.module.ts) 仅注册 PermissionGuard 为 APP_GUARD,未在 configure() 消费 AuthMiddleware;02 文档 §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.1(8 RPC 核心)+ P2.2(扩展) | ⚠️ workline.md 仅粗略列出 P2.1,P2.2-P6 未细化(见 worklines/iam_workline.md) |
|
||||
| §5.5 | 审计日志归 iam(AuditEvent + 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/AuditEvent(coord-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-ai06:01/02 文档未回写 I1-I8 裁决,中间过渡方案残留
|
||||
|
||||
- **提请方**:ai06
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确 / 其他
|
||||
- **描述**:coord-final-decisions §0.1 强制覆盖声明要求各 AI 在 3 个工作日内回写 02 文档,删除所有"中间过渡方案"。president-final-rulings §3.4 也明确要求 ai06 回写 iam 02(I1-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 决策点 5:P2 仅 REST → P3 gRPC(违反 I1)
|
||||
- 02 §8.1 决策点 4:iam 自建 Outbox(违反 I2)
|
||||
- 02 §8.1 决策点 9:PermissionGuard 待改造(违反 I3)
|
||||
- 02 §1.1/§1.2/§8.1 决策点 10:AuthMiddleware P2 不注册(违反 I4)
|
||||
- 02 §3.1.2:表名 iam_parent_student_relations(违反 I6)
|
||||
- 02 §4.1:API 路径无 /v1 前缀(违反 I7)
|
||||
- 01 §1:teacher-bff HTTP 调用 iam(违反 B2,应 gRPC)
|
||||
- **建议方案**:ai06 立即回写 01/02 文档,删除全部中间过渡方案描述,对齐 I1-I8 + §2.15/§2.16/§5.5 最终方案。
|
||||
- **状态**:待 coord 仲裁(确认回写范围与验收标准)
|
||||
|
||||
### ISSUE-002-ai06:events.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-ai06:Topic 命名三方不一致(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,非 identity),coord 同步修正 004 §7.2 的 `edu.identity.*` → `edu.iam.*`。同时明确 Topic 粒度:matrix.md 用聚合 topic(`edu.iam.user.events` 含多 action),004 §7.2 用具体动作 topic(`edu.iam.user.created`),需统一为一种风格。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-004-ai06:DataScope 枚举三方不一致
|
||||
|
||||
- **提请方**:ai06
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:DataScope 6 级枚举存在三方不一致:
|
||||
- [004 §5.3 表](../../../docs/architecture/004_architecture_impact_map.md) 行 463-470:SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL
|
||||
- [004 §5.3](../../../docs/architecture/004_architecture_impact_map.md) 行 505:all/grade_managed/class_taught/children/owned + 自定义(语义命名,与表不同)
|
||||
- president §3.2 P2.2:ALL/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-ai06:iam.proto 仅 4 RPC,未补全至 12 RPC
|
||||
|
||||
- **提请方**:ai06
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:coord-final-decisions §5.1 要求 iam.proto 在"P2 启动前"补全 8 个新 RPC(GetViewports / GetEffectivePermissions / GetEffectiveAccess / Logout / GetPublicKey / BatchGetUsers / GetEffectiveDataScope / GetChildrenByParent),president §6 批次 0 任务 0.3 也明确"coord 补全 iam.proto 8 RPC"。但实际 [iam.proto](../../../packages/shared-proto/proto/iam.proto) 仍仅 4 RPC(Register/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-ai06:01/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) 署名"AI:ai02(TS / 身份认证)"、"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-gateway(coord-final-decisions §3.7)。文档署名错误会导致多 AI 协作时身份混淆。
|
||||
- **建议方案**:回写时将 01/02 文档署名从 ai02 改为 ai06。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-007-ai06:contract.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)。
|
||||
|
||||
@@ -6,19 +6,189 @@
|
||||
|
||||
---
|
||||
|
||||
## 问题列表
|
||||
## §0 已有仲裁核查(ai10 复核)
|
||||
|
||||
<!--
|
||||
追加条目格式:
|
||||
> 本节核查 01-understanding.md / 02-architecture-design.md 中引用的已有仲裁,对照源码/proto 验证准确性。
|
||||
|
||||
### ISSUE-[编号]-[AI标识]:[标题]
|
||||
### ISSUE-001-ai10:M8 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-ai10:M12 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-ai10:M2 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 必须实现 Outbox(P5 强制)
|
||||
- **状态**:已裁决(核查通过)
|
||||
|
||||
### ISSUE-004-ai10:core-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-ai10:Push 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-ai10:events.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-ai10:proto 包名规则冲突(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-ai10:Kafka topic 命名三套约定并存
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:msg 相关的 Kafka topic 命名存在三套约定:
|
||||
- **约定 A(004 §7.2,per-event topic)**:`edu.identity.user.created` / `edu.teaching.exam.published` / `edu.notification.sent`
|
||||
- **约定 B(matrix.md §4 + events.proto 注释,aggregate topic)**:`edu.iam.user.events` / `edu.exam.events` / `edu.notification.requested`
|
||||
- **约定 C(msg_contract.md §1.4,aggregate topic + action 字段)**:`edu.msg.notification.events`(action: sent/read/recalled/failed)
|
||||
- 02-architecture-design.md §5.1/§5.2 采用约定 A;msg_contract.md §1.4 采用约定 C;matrix.md 采用约定 B。known-issues §全局 L182 已标记此冲突。
|
||||
- **建议方案**:coord 统一为一套约定。ai10 倾向约定 A(per-event topic),理由:
|
||||
- 004 §7.2 已采用,是架构设计意图唯一源
|
||||
- per-event topic 便于消费者按需订阅,避免反序列化无关事件
|
||||
- 与 NotificationSent / NotificationRead 等事件命名(PascalCase)对齐
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-009-ai10:RPC 数量超预算(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 RPC(SendNotification / BatchSendNotification / ListNotifications / GetUnreadCount / MarkAsRead / BatchMarkAsRead / MarkAllAsRead / SearchNotifications / RecallNotification)
|
||||
- NotificationPreferenceService 2 RPC(GetPreferences / UpdatePreferences)
|
||||
- NotificationTemplateService 6 RPC(CreateTemplate / 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-ai10:markAsRead 权限点与设计不一致
|
||||
|
||||
- **提请方**: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 为准(READ),P5 实现时修正 controller 权限点
|
||||
- **状态**:待 coord 确认
|
||||
|
||||
### ISSUE-011-ai10:DB→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 未覆盖:
|
||||
- **三层幂等防线**(L344):L1 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-ai10:events.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-ai10:msg_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-ai10:msg_contract.md Kafka 发布事件与设计文档不一致
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:
|
||||
- msg_contract.md §1.4:`edu.msg.notification.events` topic,单一 NotificationEvent 含 action 字段
|
||||
- 02-architecture-design.md §5.2:4 个 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)
|
||||
|
||||
@@ -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 豁免 gRPC,HTTP /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-ai05:01-understanding.md 未同步 ai05 接手与多项仲裁
|
||||
|
||||
- **提请方**:ai05
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:文档同步缺失
|
||||
- **描述**:01-understanding.md 头部仍标 "AI 标识:ai04"、"状态:待 coord 审核",未反映 ai05 已正式接手(ai-allocation.md §3.2)。同时以下仲裁已裁决但 01 未同步:
|
||||
- U3(GraphQL P2 引入):01 §4.1 仍建议"P4 先对齐 teacher-bff REST 现状",与 U3 仲裁冲突
|
||||
- U4(BFF 豁免 @RequirePermission):01 §6 表格"权限装饰器"行标"⚠️ 不对齐",未引用 U4 仲裁
|
||||
- C1(错误码前缀 BFF_PARENT_):01 §3.3 仍用 `PARENT_BFF_` 旧前缀
|
||||
- **建议方案**:01-understanding.md 头部更新为 ai05 复审版,同步 U3/U4/C1 仲裁结论;或由 coord 确认 01 作为"阶段 1 历史快照"保留原样,以 02 为准
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-002-ai05:02 文档内部 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-ai05:contract.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-ai05:004 §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-ai05:parent-portal 01 文档与 parent-bff GraphQL 决策跨模块冲突
|
||||
|
||||
- **提请方**:ai05
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:跨模块契约冲突
|
||||
- **描述**:U3 仲裁决定 parent-bff P4 直接用 GraphQL(02 §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-bff(ai04 设计)",ai04 已过时(现 ai05)
|
||||
此冲突若不解决,parent-portal(ai15)会按 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-ai05:proto 包名引用不一致(缺失 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-ai05:GraphQL 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-ai05:core_edu.proto 缺 ClassService,02 §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-ai05:ai-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 责任方为 "ai06(iam 现归属)" 但 §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-ai05:02 缺少 ADR / NFR / 容量规划 / 威胁建模(业界规范差距)
|
||||
|
||||
- **提请方**:ai05
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:架构文档规范缺失
|
||||
- **描述**:对照业界通用架构文档规范(如 C4 model + ADR + NFR),02-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)
|
||||
-->
|
||||
|
||||
(暂无问题)
|
||||
|
||||
@@ -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-001(teacher-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.3(GraphQL 端点 :3010)和 [matrix.md](../matrix.md) §3(parent-bff GraphQL)直接冲突。提请 ISSUE-001。
|
||||
|
||||
### 0.2 ARB-002(MF Shell 暴露清单)核查
|
||||
|
||||
| 核查项 | ARB-002 结论 | parent-portal 落地情况 | 状态 |
|
||||
| ------ | ------------ | ---------------------- | ---- |
|
||||
| Shell 暴露 GraphQLProvider | ✅ 已裁决 | 01 §4 技术栈未列 urql/GraphQL client;02 §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-ai15:01/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-001(coord.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 用 REST,contract.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.1(X-Fields 字段裁剪改为 GraphQL query 字段选择)、02 §4.1(`useParentApi` 改为 GraphQL hooks)、§4.2(TanStack Query 约定配合 GraphQL operations)、§11.3 未决设计决策 #2(移除,已裁决)
|
||||
3. 若 coord 另有裁决(如 parent-portal 特殊走 REST),以 coord 裁决为准
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-002-ai15:MF 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-002(coord.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 client(urql),与 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-ai15:switch-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)的端点,因登录前无 JWT,GraphQL endpoint 需鉴权。请 coord 确认登录是否走 REST `/api/v1/iam/login`,其余走 GraphQL
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-005-ai15:02 §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-001(coord.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-001,parent-portal 用 GraphQL 消费 parent-bff"
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-006-ai15:contract.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-ai15:contract.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-ai15:contract.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-ai15:parent-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-001),parent-bff 需提供 `mutation switchChild(childId: ID!): SwitchChildPayload!`
|
||||
- 但 parent-bff contract 未列此 Mutation,且 iam.GetChildrenByParent 已返回子女列表,切换子女是否需后端记录(还是纯前端 localStorage)需明确
|
||||
- **建议方案**:
|
||||
1. 请 coord 协调 ai05(parent-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-ai15:iam 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)—— 核心依赖 GetChildrenByParent(I3/ISSUE-047 裁决)"
|
||||
- 此为 parent-portal 多子女场景的 P0 阻塞项,ai06(iam)需在 P3 收尾前补全
|
||||
- ai15 在此提请,请 coord 跟踪 ai06 进度并确认补全时间点
|
||||
- **建议方案**:
|
||||
1. coord 确认 ai06 补全 `GetChildrenByParent` 的时间点(应在 P4 启动前)
|
||||
2. 在补全前,parent-portal 用 mock(固定 2 个子女)开发,mock 数据与 parent-bff mock 一致(student-001 + student-002)
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
---
|
||||
|
||||
## §1 已裁决问题
|
||||
|
||||
(暂无已裁决问题)
|
||||
|
||||
@@ -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-ai02:SSE 端点是否提供(跨文档三方冲突)
|
||||
|
||||
- **提请方**: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 明确建议"不支持 SSE,WebSocket 已够用,避免协议膨胀"
|
||||
- 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 兜底)、ai10(msg 不需调 /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 隔离,共享密钥足够
|
||||
- **影响方**:ai10(msg 调用方需用相同头名)、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-30KB,10w 连接约 2-3GB,单节点可承载
|
||||
3. 50k 目标过于保守,与横向扩展方案不匹配
|
||||
- **影响方**:coord(更新 modules/README.md §7)、ai02(02 §11 已正确)
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-004-ai02:contract.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 定位,避免引入持久化层增加运维复杂度。
|
||||
- **影响方**:ai10(msg 需确认审计字段是否覆盖 push-gateway 推送结果)、coord(澄清 ai-allocation §5 表述)
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-006-ai02:ISSUE-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-ai02:02 文档缺 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/网络带宽估算缺失
|
||||
- **建议方案**:在批次 4(P5)补全 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 历史问题
|
||||
|
||||
(暂无)
|
||||
|
||||
@@ -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 02:B1 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-ai04(02 文档回写)。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-ai04:01-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-ai04:02-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 + B5;ActionState 信封规范应改为 [coord-final-decisions.md](../../coord-final-decisions.md) G8 + F9
|
||||
- **建议方案**:02 文档回写时修正引用源;coord 确认是否需要在 004 补充 §11.4/§11.5 章节,或统一指向 coord-final-decisions
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-STU-003-ai04:02 文档 §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 风格 | A(P3 REST) | **B1:P2 起直接 GraphQL** | coord-final-decisions §2 B1 |
|
||||
| 2 | BFF 是否做权限校验 | A(不校验) | **B3:BFF 豁免 @RequirePermission** | coord-final-decisions §2 B3 |
|
||||
| 3 | 自我越权防御 | B(做) | **B4:全部 BFF 强制自我越权防御** | coord-final-decisions §2 B4 |
|
||||
| 4 | /readyz 检查逻辑 | A(P3 直接 ok) | **G2 + §2.4:按阶段扩展探针,必需依赖失败 503,可选依赖软失败** | president §2.4 |
|
||||
| 5 | Kafka 事件订阅时机 | A(P3 不订阅) | **B7:P2-P4 不订阅 Kafka,P5 后订阅** | coord-final-decisions §2 B7 |
|
||||
| 6 | 缓存策略 | B(Redis 5-30s) | **B6:Redis 5-30s 短缓存** | coord-final-decisions §2 B6 |
|
||||
| 8 | 错误码前缀 | BFF_STUDENT_ | **B5:BFF_STUDENT_** | coord-final-decisions §2 B5 |
|
||||
| 9 | DownstreamClient 回写 | B(回写) | **B8:回写 teacher-bff,3 个 BFF 统一** | coord-final-decisions §2 B8 |
|
||||
|
||||
仅剩 #7(端口 3009)、#10(CQRS 不引入)、#11(SSE 实现)、#12(熔断器引入)属合理的设计决策,但 #12 熔断器 president §2.4 已暗示按阶段评估。
|
||||
- **建议方案**:02 文档回写时删除已裁决项的"待仲裁"标注,改为"已裁决(见 coord-final-decisions §2 BX)"
|
||||
- **状态**:待 coord 确认(与 ISSUE-028-ai04 合并处理)
|
||||
|
||||
### ISSUE-STU-004-ai04:student-bff GraphQL schema 第一版尚未起草
|
||||
|
||||
- **提请方**:ai04
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:president §2.2 裁决"ai04 起草 student-bff schema(批次 1 等待期),coord 在批次 2 启动前仲裁第一版"。当前批次 1 已启动(2026-07-10),ai04 处于批次 1 等待期,但 schema 草案尚未产出。02-architecture-design.md 仍是 REST 设计,未定义 GraphQL Query/Mutation/Type。
|
||||
- **影响**:阻塞 ai14(student-portal)P3 启动(依赖 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-ai04:ai04 02-architecture-design.md 与 coord B1+B2 裁决冲突需回写
|
||||
|
||||
- **提请方**:ai04(student-bff + parent-bff)
|
||||
- **日期**:2026-07-09
|
||||
- **类型**:裁决冲突(文档回写义务)
|
||||
- **描述**:02-architecture-design.md 与 coord-final-decisions B1(P2 起直接 GraphQL)+ B2(首次实现即 gRPC)存在 2 项重大冲突(§4 API 设计决策为 REST;§9.2 演进路线 REST→GraphQL)
|
||||
- **裁决**:president §3.4 — ai04 须在批次 2 启动前回写 student-bff 02:B1 GraphQL + B2 gRPC + B8 DownstreamClient
|
||||
- **状态**:⚠️ 未执行(详见 §0 核查)
|
||||
|
||||
### ISSUE-029-ai04:ai04 P3 启动前置依赖确认(4 项强阻塞)
|
||||
|
||||
- **提请方**:ai04(student-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-ai04:ai04⟷ai14 student-bff GraphQL schema 第一版仲裁时机
|
||||
|
||||
- **提请方**:ai04(student-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-ai04:issues.md 编号冲突
|
||||
|
||||
- **提请方**:ai04(student-bff + parent-bff)
|
||||
- **日期**:2026-07-09
|
||||
- **类型**:工作归属不明(文档规范)
|
||||
- **描述**:issues.md ISSUE-024/025 编号冲突(ai11 与 ai09 重复)
|
||||
- **裁决**:president §0.4 — 保留原始内容,用 `ISSUE-XXX-<提请AI>` 格式定位,不重新编号
|
||||
- **状态**:✅ 已裁决
|
||||
|
||||
@@ -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-001(teacher-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 全量 Query(currentUser/myClasses/myExams/myHomework/myGrades/myAttendance/studentDashboard) | ✅ 已落实 |
|
||||
| P2 Mutation 范围 | ⚠️ 部分参考。student-portal P3 起步即需要 submitHomework mutation(作业提交是 P3 核心场景) | ai14 P3 即消费 submitHomework mutation;ai04 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、复杂度限制)适用于所有 BFF,student-portal 已在 02-architecture-design.md v2 中全面落实。
|
||||
|
||||
---
|
||||
|
||||
### 1.2 ARB-002(MF Shell 暴露清单)对 student-portal 的影响核查
|
||||
|
||||
| 裁决点 | 对 student-portal 的适用性 | ai14 落实方案 | 状态 |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
||||
| Shell 身份 | ✅ 适用。teacher-portal 是 MF Shell,student-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 暴露 GraphQLProvider,student-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` 控制是否走 MF;P3 默认 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-ai14:GraphQL 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`
|
||||
- 由 ai01(api-gateway)确认路由:`/api/v1/student/*` → `student-bff:3009/*`(即 `/api/v1/student/graphql` → `student-bff:3009/graphql`)
|
||||
- 由 ai04(student-bff)确认 GraphQL endpoint 路径为 `POST /graphql`(与 teacher-bff 一致)
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
---
|
||||
|
||||
### ISSUE-014-02-ai14:student-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 类型
|
||||
- 由 ai04(student-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?
|
||||
- 这些决策影响 ai04(student-bff)是否需要提供 `recordExamViolation` mutation,以及 ai08(core-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` 事件拦截,但学生作答主观题时可能需要粘贴(如从草稿本粘贴长文本)
|
||||
- 策略不明确:全部禁止粘贴?仅主观题允许?仅客观题禁止?
|
||||
- 影响学生体验和防作弊效果平衡
|
||||
- **建议方案**:
|
||||
- **客观题**:禁止粘贴(防作弊优先)
|
||||
- **主观题(简答/论述)**:允许粘贴,但记录粘贴事件 + 粘贴内容长度,教师端批改时可见
|
||||
- **作文题**:允许粘贴(学生体验优先),不记录
|
||||
- 由 ai04(student-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 multipart(graphql-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 鉴权
|
||||
- 由 ai01(api-gateway)确认是否提供 `/api/v1/student/upload` 路由,由 ai04(student-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`?由 ai08(core-edu)发布?
|
||||
2. **题目重排**(教师重排题目顺序):事件名 `ExamQuestionReordered`?是否需要前端实时重排?
|
||||
3. **考试强制提交**(教师强制收卷):事件名 `ExamForceSubmitted`?前端收到后立即提交?
|
||||
- 这些事件影响 student-portal 考试作答页的实时响应逻辑
|
||||
- **建议方案**:
|
||||
- **考试延长**:ai08(core-edu)发布 `ExamExtended` 事件到 `edu.exam.events` topic,msg(ai10)消费后通过 push-gateway 推送,student-portal 收到后更新倒计时
|
||||
- **题目重排**:P3 不实现(题目顺序固定),P4 评估是否需要实时重排
|
||||
- **考试强制提交**:ai08 发布 `ExamForceSubmitted` 事件,student-portal 收到后立即触发提交流程
|
||||
- 由 ai08(core-edu)确认事件命名,由 ai10(msg)确认推送路径
|
||||
- **状态**:待 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)
|
||||
- 由 ai04(student-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)
|
||||
-->
|
||||
|
||||
(暂无问题)
|
||||
|
||||
@@ -21,4 +21,38 @@
|
||||
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X)
|
||||
-->
|
||||
|
||||
(暂无问题)
|
||||
### ISSUE-001-ai03:admin 命名空间 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.1(ISSUE-044)裁决 "ai03 P2 预留 admin schema 命名空间(如 `admin.*` Query/Mutation)",且 §7.3 ai03 工作清单明确 "批次 1(P2):Must Have 13 项 + DownstreamClient 抽象 + admin schema 命名空间预留"。两处对 P2 admin 命名空间的要求不一致——总裁裁决要求 P2 预留(schema 中声明占位),coord 仲裁说 P2 不包含。
|
||||
- **建议方案**:以总裁裁决为准(裁决优先级 president > coord),修正 ARB-001 §1.3 为 "P2 预留 admin 命名空间占位(schema 中声明 `admin` Query/Mutation 类型骨架,无实际 Resolver),P6 admin-portal 阶段实现具体 Resolver"。ai03 在 P2 schema 第一版中预留 `admin` 命名空间类型声明。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-002-ai03:P2 dashboard classes 数据来源与 ARB-001 §1.4 调用链冲突
|
||||
|
||||
- **提请方**:ai03
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:coord.md ARB-001 §1.4 ai03 执行项第 4 条写 "dashboard Resolver 并行调用 iam(3 RPC)+ classes(1 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 无 gRPC,teacher-bff 不能用 REST 调 classes(B2 禁止 REST 过渡)。
|
||||
3. **president §3.5 功能范围**:president-final-rulings.md §3.5 裁决 P2 实现 "班级列表(iam 数据)",即 P2 班级列表数据来自 iam,不是 classes 服务。
|
||||
- **建议方案**:采纳 president §3.5,P2 dashboard 的 classes 数据从 iam gRPC 获取(iam `GetEffectiveAccess` 或 `GetViewports` 返回的关联班级),不调 classes 服务。ARB-001 §1.4 第 4 条修正为 "dashboard Resolver 并行调用 iam gRPC(GetUserInfo + GetViewports + GetEffectiveAccess),classes 列表从 iam 返回数据推导,P3 core-edu gRPC 就绪后切换为 `GetClassesByTeacher` RPC"。这样 P2 不依赖 classes gRPC,与 B2 + president §3.5 一致。
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-003-ai03:GraphQL 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 仲裁
|
||||
|
||||
---
|
||||
|
||||
@@ -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 架构,**未同步**以下已裁决事项:
|
||||
- F9(ISSUE-036):P2 起 all-in GraphQL,无 REST 过渡 — 01 §1/§3.1 仍写"P2-P3 用 REST 过渡";02 全文基于 REST(ApiClient/TanStack Query/契约清单)
|
||||
- ARB-001(ISSUE-037):teacher-bff GraphQL schema 第一版 5 Query — 01 §3.1 列 REST 端点;02 §4/§10 全 REST
|
||||
- ARB-002(ISSUE-038/039):MF Shell 暴露清单(GraphQLProvider + hooks + UI 组件 + urql/graphql singleton)— 01 未提;02 §1.2 exposes 仅 AppShell+shared-deps、P2 配 3 remotes、shared 无 urql
|
||||
- 总裁 §2.17(ISSUE-038):GraphQL client 单例方案 A — 02 §11.3.2 仍列"GraphQL vs REST"未决(已裁决)
|
||||
- 对照已回写的 `03-long-term-architecture.md §1.4`(GraphQL 最终方案),01/02 严重滞后
|
||||
- **建议方案**:
|
||||
1. 01-understanding.md:ai07→ai13;§1 删除 REST 过渡;§3.1 REST 端点→GraphQL queries(ARB-001);§4 技术栈补 urql;补 ARB-002 暴露清单
|
||||
2. 02-architecture-design.md:ai07→ai13;全文 REST→GraphQL 重写(MF 配置对齐 ARB-002;API 层改 urql client;契约清单改 GraphQL;§11.3 未决决策改已决策)
|
||||
- **状态**:✅ 已回写闭合(2026-07-10,审查批次)
|
||||
- **coord 裁决**:无需新裁决(复用 ISSUE-036~040 已有裁决),本次为文档同步执行
|
||||
- **回写执行**:
|
||||
- `01-understanding.md`:已修正 ai07→ai13、§1 REST→GraphQL、§3.1 REST 端点→GraphQL queries(ARB-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**: ai13(teacher-portal)
|
||||
**Branch**: feat/teacher-portal-issues-migrate-ai13
|
||||
**Coordinator**: coord-ai
|
||||
|
||||
@@ -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,最后统一集成测试)
|
||||
> 说明:本排期基于 GraphQL(ARB-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-001),admin 权限点使用 `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 排期。
|
||||
> 关键路径(crit):16.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-002,ai13 P2 交付)
|
||||
- **交付物**:
|
||||
- `apps/admin-portal/next.config.js`(NextFederationPlugin,Remote 角色,`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 JWT(admin 角色,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` schema(ISSUE-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` 页(树形 + DataTable,school/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` Query(ISSUE-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` 页(recharts:total_teachers / total_students / school_avg_score / 趋势图)
|
||||
- **验收标准**:adminDashboard Query 返回聚合数据;60s 轮询监控指标 + 5min 轮询统计
|
||||
|
||||
### P6 阶段(硬化)
|
||||
|
||||
#### 16.13 A11y WCAG 2.2 AA 审计 + 修复
|
||||
|
||||
- **交付物**:eslint-plugin-jsx-a11y(error 级)+ 手动审计修复
|
||||
- **验收标准**:0 个 error 级违规
|
||||
|
||||
#### 16.14 Web Vitals + OTel browser SDK 接入
|
||||
|
||||
- **交付物**:`next/web-vitals` → `POST /api/admin/web-vitals`;OTel browser SDK(复用 Shell TracerProvider,scope='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 就绪(ai13,ARB-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 providers,Shell 就绪后切换 | ⏳ |
|
||||
| MF SSR 对齐复杂 | Remote SSR 上下文依赖 Shell | 优先 CSR,SSR 仅首屏 dashboard | ⏳ |
|
||||
|
||||
@@ -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,最后统一集成测试)
|
||||
> 阶段归属:批次 4(P5),见 [workline.md §1](../workline.md) 甘特图 `b4c: ai12 ai服务 gRPC 50058, after b3a, 13d`
|
||||
|
||||
---
|
||||
|
||||
## §1 总览
|
||||
|
||||
ai 是智能服务,提供 AiService(6 个 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 全阶段甘特图(P5,13 天)
|
||||
|
||||
> 对齐 [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 排期。
|
||||
**关键路径**(红色 crit):LLMProvider 抽象 → gRPC server → PromptTemplateService → GenerateQuestion → gRPC client → 备课工作流编排
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### M14 基础架构(第1-4天)
|
||||
|
||||
#### P5-12.1:LLMProvider 抽象 + 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.2:ProviderFailoverChain + 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.3:gRPC server + ActionState 整改(P0 阻塞)
|
||||
|
||||
- **负责人**:ai12
|
||||
- **依赖**:P5-12.1;**前置**:ISSUE-03(coord 升级 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.4:Redis 多维度限流 + 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.5:PromptTemplateService + 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.6:GenerateQuestion + StreamGenerateQuestion 逐字流式
|
||||
|
||||
- **负责人**:ai12
|
||||
- **依赖**:P5-12.5;**前置**:ISSUE-03(ai.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-judge,5 维度加权评分)
|
||||
- `services/ai/src/ai/evaluation/quality_gate.py`(阈值 0.7,不达标重试 < 3 次)
|
||||
- **验收标准**:评估通过率 > 80%;不达标自动重试;重试耗尽返回 `AI_EVALUATION_FAILED`
|
||||
|
||||
#### P5-12.8:UsageRecorder + KafkaProducer + QuotaEnforcer
|
||||
|
||||
- **负责人**:ai12
|
||||
- **依赖**:P5-12.6;**前置**:ISSUE-02(topic 裁决)+ ISSUE-04(events.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.10:gRPC client(content / data-ana / iam)+ interceptor
|
||||
|
||||
- **负责人**:ai12
|
||||
- **依赖**:P5-12.3;**前置**:content gRPC 50054 就绪(ai09 P4)、ISSUE-07(iam 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`(GetEffectiveDataScope,Redis 缓存 5min)
|
||||
- `grpc.aio.ClientInterceptor`(trace 注入 + 重试 + 熔断)
|
||||
- **验收标准**:下游 gRPC 不可达时降级(跳过学情查询 + `degraded:true`);DataScope 缓存命中
|
||||
|
||||
#### P5-12.11:LessonPreparationWorkflow 4 步编排 + 状态机
|
||||
|
||||
- **负责人**:ai12
|
||||
- **依赖**:P5-12.10;**前置**:ISSUE-03(ai.proto 补 `GenerateLessonPlan` RPC)
|
||||
- **交付物**:
|
||||
- `services/ai/src/ai/services/lesson_preparation_workflow.py`(4 步:分析学情 → 推荐知识点 → 生成题目 → 教师审核)
|
||||
- 状态机实现(02-architecture-design.md §2.4:Pending→Analyzing→Recommended→Generating→PendingReview→Persisted)
|
||||
- gRPC RPC:`GenerateLessonPlan`
|
||||
- HTTP 端点:`POST /ai/v1/lesson/preparation` + `GET /ai/v1/lesson/preparation/{id}`
|
||||
- **验收标准**:端到端跑通 4 步;评估未通过自动重试 < 3 次;工作流状态可查询
|
||||
|
||||
#### P5-12.12:WorkflowStateStore + 教师审核 + 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-python,ai.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 key(OpenAI / 百川 / 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. ai12:ai 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 启动前补全 proto(ISSUE-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 评估迁移 Temporal(ISSUE-06) |
|
||||
|
||||
@@ -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 RS256(HTTP 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 排期。
|
||||
**关键路径**(红色 crit):P2.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 GetPublicKey(ai06)
|
||||
- **验收标准**:路由双入口 + 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.1:shared-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/logger(zap 或 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.2:JWT 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 5min(shared-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.16(HTTP 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.4:slog + 业务 metrics + tracer 资源属性
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:P2.1 shared-go 接入完成
|
||||
- **交付物**:
|
||||
- 按 ISSUE-004 仲裁结果统一 logger(zap 或 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`(Counter,method/endpoint/status)
|
||||
- `api_gateway_http_request_duration_seconds`(Histogram,method/endpoint)
|
||||
- `api_gateway_circuit_breaker_state`(Gauge,service/state)
|
||||
- `api_gateway_rate_limited_total`(Counter,ip)
|
||||
- `api_gateway_proxy_upstream_duration_seconds`(Histogram,upstream)
|
||||
- `api_gateway_jwks_refresh_total`(Counter,result)
|
||||
- `api_gateway_auth_failures_total`(Counter,reason)
|
||||
- [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.6:DevMode 生产防护
|
||||
|
||||
- **负责人**: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-003(API 版本化路由规则);iam (ai06) / teacher-bff (ai03) P2 就绪
|
||||
- **交付物**:
|
||||
- 按 ISSUE-003 仲裁结果更新路由(方案 A/B/C 之一)
|
||||
- 若方案 A:iam 路由改为 `/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.1:student-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.2:core-edu 路由
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:core-edu (ai08) P3 就绪;classes 服务已合并入 core-edu(C1 裁决)
|
||||
- **交付物**:
|
||||
- `main.go` classes 路由目标改为 core-edu(`cfg.ClassesServiceURL` → `cfg.CoreEduServiceURL`)
|
||||
- 或保留 classes 路由别名,proxy 到 core-edu
|
||||
- exams / homework / grades 路由已在 P1 实现,无需修改
|
||||
- **验收标准**:`/api/v1/classes/*` 代理到 core-edu:3004
|
||||
|
||||
### P3.3:API 版本化 /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.1:parent-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.2:content 路由
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:content (ai09) P4 就绪
|
||||
- **交付物**:
|
||||
- `main.go` 已有 content 路由(P1 实现:textbooks / chapters / knowledge-points / questions)
|
||||
- 验证路由目标 `cfg.ContentServiceURL`(:3005)正确
|
||||
- 按 P3.3 版本化规则加 `/v1` 前缀
|
||||
- **验收标准**:content 路由可用 + 版本化
|
||||
|
||||
### P4.3:data-ana 路由
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:data-ana (ai11) P4 就绪
|
||||
- **交付物**:
|
||||
- `main.go` 已有 data-ana 路由(P1 实现:analytics)
|
||||
- 增加 `dashboard` 路由别名:`/api/v1/dashboard` + `/*path` → data-ana
|
||||
- 按版本化规则加 `/v1` 前缀
|
||||
- **验收标准**:data-ana 路由可用 + dashboard 别名可用
|
||||
|
||||
---
|
||||
|
||||
### P5.1:msg 路由
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:msg (ai10) P5 就绪
|
||||
- **交付物**:
|
||||
- `main.go` 已有 msg 路由(P1 实现:notifications)
|
||||
- 增加 `messages` 路由别名:`/api/v1/messages` + `/*path` → msg
|
||||
- 按版本化规则加 `/v1` 前缀
|
||||
- **验收标准**:msg 路由可用 + messages 别名可用
|
||||
|
||||
### P5.2:ai 路由
|
||||
|
||||
- **负责人**:ai01
|
||||
- **依赖**:ai (ai12) P5 就绪
|
||||
- **交付物**:
|
||||
- `main.go` 已有 ai 路由(P1 实现)
|
||||
- 按版本化规则加 `/v1` 前缀
|
||||
- **验收标准**:ai 路由可用
|
||||
|
||||
### P5.3:push-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.2:per-服务实例熔断评估
|
||||
|
||||
- **负责人**: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 RPC(ai06)
|
||||
- **我的就绪信号**: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 启用(不影响 Gateway,Gateway 走 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 仲裁 | 持续 |
|
||||
|
||||
@@ -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 Service(Textbook/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+ 长远演进:教材版本管理 / 跨租户内容共享 / 个性化学习路径推荐
|
||||
|
||||
**关键路径位置**:批次 3(P4),依赖批次 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 producer(idempotent + transactionalId)
|
||||
|
||||
- **负责人**:ai09
|
||||
- **依赖**:P4.2
|
||||
- **交付物**:
|
||||
- 新建 `src/shared/kafka/producer.ts`(kafkajs 客户端,idempotent=true,transactionalId=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 节点最终一致出现(延迟 < 2s);Neo4j 故障时事件不丢失,恢复后补齐
|
||||
|
||||
#### 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`
|
||||
- **验收标准**:业务事务内不再直接写 Neo4j;Neo4j 写入全部走异步 Sync Worker(解决 ISSUE-010 + 01-understanding C9)
|
||||
|
||||
#### P4.6 content.proto 补 ChapterService / QuestionService
|
||||
|
||||
- **负责人**:ai09
|
||||
- **依赖**:无(自身 proto 现状)
|
||||
- **交付物**:
|
||||
- [content.proto](../../../packages/shared-proto/proto/content.proto) 补 ChapterService(CreateChapter/ListChapters/GetChapter/UpdateChapter/DeleteChapter)
|
||||
- 补 QuestionService(CreateQuestion/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` 无破坏性变更(新增字段 OK);contract.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=degraded;DB 不可用 → 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 过滤
|
||||
- **验收标准**:检索延迟 < 200ms(P5 退出标准);返回分页结构
|
||||
|
||||
#### 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 对齐
|
||||
|
||||
- 批次 3(P4):ai09 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 天(或协调压缩测试任务)
|
||||
|
||||
@@ -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 全部代码,最后统一集成测试)
|
||||
> 批次归属:批次 2(P3 核心教学),关键路径
|
||||
|
||||
---
|
||||
|
||||
## §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 + /readyz(DB 探针) |
|
||||
| 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.2(TOPIC_MAP)→ P3.3(状态机)→ P3.4(gRPC)→ P3.5/P3.6/P3.7/P3.8/P3.9/P3.10/P3.11(并行)→ P3.12(测试)
|
||||
|
||||
**预计工期**:
|
||||
- P3:13 天(含 2 天等待仲裁)
|
||||
- P4:5 天
|
||||
- P5:3 天
|
||||
- P6:4 天
|
||||
- 合计:25 天(P3 后)
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### P3.0 等待 coord 仲裁 ISSUE-001 ~ ISSUE-006
|
||||
|
||||
- **负责人**:ai08(等待 coord)
|
||||
- **依赖**:无
|
||||
- **交付物**:coord 出具仲裁结论(见 [coord.md](../coord.md) 后续章节)
|
||||
- **验收标准**:6 项 ISSUE 全部裁决,状态更新为"已裁决"
|
||||
- **阻塞说明**:
|
||||
- ISSUE-001(proto 实际状态):阻塞 P3.4 gRPC 实现
|
||||
- ISSUE-002(events.proto 未同步):阻塞 P3.2 TOPIC_MAP 重命名
|
||||
- ISSUE-003(状态命名不一致):阻塞 P3.3 状态机实现
|
||||
- ISSUE-004(class.transferred topic):阻塞 P3.11 classes 合并
|
||||
- ISSUE-005(RPC 数量口径):阻塞就绪信号声明
|
||||
- ISSUE-006(7 项设计决策):阻塞 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 ValidationPipe(exams/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 RPC(P1)
|
||||
|
||||
- **负责人**: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 RPC:CreateExam / GetExam / ListExamsByClass / UpdateExam / DeleteExam / PublishExam / SubmitExam / GradeExam)
|
||||
3. `src/homework/homework.grpc-controller.ts`(HomeworkService 5 RPC:AssignHomework / GetHomework / ListHomeworkByClass / SubmitHomework / GradeHomework)
|
||||
4. `src/grades/grades.grpc-controller.ts`(GradeService 6 RPC:RecordGrade / GetGrade / ListGradesByStudent / ListGradesByExam / ListGradesByHomework / UpdateGrade)
|
||||
5. `src/attendance/attendance.grpc-controller.ts`(AttendanceService 4 RPC:RecordAttendance / GetAttendance / ListAttendanceByStudent / ListAttendanceByClass)
|
||||
6. `src/classes/classes.grpc-controller.ts`(ClassService 4 RPC:GetClass / 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 + PermissionGuard(gRPC 上下文适配)
|
||||
|
||||
### P3.5 排课考勤数据模型 + AttendanceService(P2)
|
||||
|
||||
- **负责人**: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 决策 #3(scope 优先级)
|
||||
- **交付物**:
|
||||
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-edu(P3)
|
||||
|
||||
- **负责人**: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 50055(P4) | ai11 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.1 mastery 消费 |
|
||||
| content gRPC 50054(P4) | ai09 | HealthService.Check = SERVING | ⏳ | 阻塞 P4.2 知识点关联 |
|
||||
| msg gRPC 50056(P5) | ai10 | HealthService.Check = SERVING | ⏳ | 阻塞 P5.1 事件联调 |
|
||||
|
||||
### 4.2 我的就绪标志(供下游消费)
|
||||
|
||||
| 阶段 | 就绪信号 | 消费方 | 状态 |
|
||||
| ---- | -------- | ------ | ---- |
|
||||
| P2(已就绪) | HTTP 3004 可访问 + /healthz + /readyz(DB 探针) | teacher-bff(REST 调用) | ✅ |
|
||||
| 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)。
|
||||
|
||||
@@ -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),批次 1(P2)2026-07-10 启动
|
||||
|
||||
---
|
||||
|
||||
## §1 总览
|
||||
|
||||
data-ana 是数据分析服务,提供 AnalyticsService(12 个 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 排期。
|
||||
> **关键路径**(crit):ActionState 信封重构 → gRPC 50055 启用 → analytics.proto 扩展 → iam GetEffectiveDataScope 集成
|
||||
> **总工期**:约 51 天(2026-07-10 ~ 2026-08-30),其中 P4 主战场 11 天为关键交付期
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### 3.1 P2 预备期(2026-07-10 ~ 2026-07-18,8d)
|
||||
|
||||
#### 任务 2.1:ClickHouse DDL 5 宽表建表
|
||||
|
||||
- **负责人**:ai11
|
||||
- **交付物**:⚠️ 由 ai11 自行补充
|
||||
- **依赖**:见 [contracts/data-ana_contract.md](../contracts/data-ana_contract.md)
|
||||
- **验收标准**:⚠️ 由 ai11 自行补充
|
||||
- **依赖**:无(ClickHouse 实例就绪,由 infra 提供)
|
||||
- **交付物**:`infra/clickhouse/ddl/data_ana.sql`(5 宽表 DDL:student_dashboard_view / student_errors / mastery_snapshot / attendance_logs / ai_usage_log)
|
||||
- **验收标准**:5 表在 ClickHouse 中创建成功,ReplacingMergeTree 引擎 + ORDER BY + PARTITION BY 符合 02 §3 DDL 设计
|
||||
|
||||
#### 任务 2.2:CDC 消费骨架
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:Kafka 就绪
|
||||
- **交付物**:`src/data_ana/cdc_consumer.py` 重构(aiokafka AIOKafkaConsumer + EventHandler 路由框架)
|
||||
- **验收标准**:能消费 mock CDC 事件并打印路由日志,consumer group = `data-ana-cdc`
|
||||
|
||||
#### 任务 2.3:mock 数据集
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 2.1
|
||||
- **交付物**:`scripts/seed_clickhouse.py`(批量导入 30 学生 × 5 考试 × 10 作业 × 30 天出勤模拟数据)
|
||||
- **验收标准**:ClickHouse 5 表有数据,可查询返回非空结果
|
||||
|
||||
#### 任务 2.4:ActionState 信封重构(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.details),ruff 零错误
|
||||
|
||||
#### 任务 2.5:config.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-26,8d)
|
||||
|
||||
#### 任务 3.1:gRPC server 骨架(3 RPC,不正式启用)
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:analytics.proto 当前 3 RPC(无需 coord 补全)
|
||||
- **交付物**:`src/data_ana/grpc_server.py`(grpc.aio Server 骨架 + 3 RPC 实现,绑定 :50055 但不启动对外)
|
||||
- **验收标准**:本地可启动 gRPC server,3 RPC 可调用返回 mock 数据
|
||||
|
||||
#### 任务 3.2:core-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.3:ExamCache 内存 LRU
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 3.2
|
||||
- **交付物**:`src/data_ana/exam_cache.py`(内存 LRU dict,max 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.5:ClickHouseRepository 封装
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 2.1
|
||||
- **交付物**:`src/data_ana/clickhouse_client.py` 重构(查询封装 + FINAL/argMax 去重 + DataScope WHERE 注入接口)
|
||||
- **验收标准**:查询 student_dashboard_view 返回去重后最新版本数据
|
||||
|
||||
---
|
||||
|
||||
### 3.3 P4 主战场期(2026-07-26 ~ 2026-08-06,11d,批次 3)
|
||||
|
||||
#### 任务 4.1:gRPC 50055 正式启用
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 3.1
|
||||
- **交付物**:main.py lifespan 启动 gRPC server :50055,HealthService.Check 返回 SERVING
|
||||
- **验收标准**:gRPC server 对外可访问,HealthService.Check = SERVING
|
||||
|
||||
#### 任务 4.2:analytics.proto 扩展 12 RPC
|
||||
|
||||
- **负责人**:ai11(本分支内补全 proto,提请 coord 合并)
|
||||
- **依赖**:ISSUE-003 解决(coord 确认或 ai11 自行补全)
|
||||
- **交付物**:`packages/shared-proto/proto/analytics.proto` 扩展至 12 RPC(3 现有 + 9 新增 message 定义)
|
||||
- **验收标准**:`buf lint` 零错误,`buf generate` 生成 Python stub 成功,12 RPC 全部可调用
|
||||
|
||||
#### 任务 4.3:4 端 Dashboard RPC 实现
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 4.2
|
||||
- **交付物**:GetTeacherDashboard / GetStudentDashboard / GetParentDashboard / GetAdminDashboard 4 RPC 实现
|
||||
- **验收标准**:4 RPC 返回 ActionState[DashboardData],DataScope 过滤生效,降级时返回骨架数据 + degraded: true
|
||||
|
||||
#### 任务 4.4:WarningService + 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.5:GetMasteryDistribution + GetStudentMastery
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 3.4 + 任务 4.2
|
||||
- **交付物**:2 RPC 实现(班级掌握度分布 + 学生知识点掌握度明细)
|
||||
- **验收标准**:返回 mastered/progressing/weak 三档分布数据
|
||||
|
||||
#### 任务 4.6:iam.GetEffectiveDataScope 集成(降级兜底)
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:ISSUE-001 解决(iam.proto 补全 GetEffectiveDataScope)。若 P4 时 iam 未就绪,使用降级兜底
|
||||
- **交付物**:`src/data_ana/iam_client.py`(gRPC 调 iam.GetEffectiveDataScope + Redis 缓存 5min + 降级兜底)
|
||||
- **验收标准**:iam 可用时调 gRPC 获取 DataScope;iam 不可用时按 role 映射默认 DataScope + degraded: true
|
||||
|
||||
#### 任务 4.7:DataScope 6 级 WHERE 注入
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 4.6 + 任务 3.5
|
||||
- **交付物**:ClickHouseRepository 查询方法注入 DataScope WHERE 子句(SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL)
|
||||
- **验收标准**:教师只能查自己班级数据,学生只能查自己数据,管理员可查全校数据
|
||||
|
||||
#### 任务 4.8:attendance + content CDC 消费
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 3.2(CDC 框架)
|
||||
- **交付物**:EventHandler 扩展 attendance_logs 表 CDC + content_knowledge_points 表 CDC
|
||||
- **验收标准**:考勤事件落 attendance_logs 表,知识点事件更新 mastery_snapshot 元数据
|
||||
|
||||
#### 任务 4.9:MasteryEvent + 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.10:HTTP 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-19,13d,批次 4 并行)
|
||||
|
||||
#### 任务 5.1:SubscribeMasteryUpdate Server Streaming RPC
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 4.2 + 任务 4.9
|
||||
- **交付物**:SubscribeMasteryUpdate RPC 实现(server-streaming,客户端订阅 student_id/class_id,掌握度更新时推送)
|
||||
- **验收标准**:客户端订阅后,掌握度计算完成时收到 MasteryUpdateEvent 流
|
||||
|
||||
#### 任务 5.2:AIUsageEvent 消费 → 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` + 手动 commit(at-least-once)
|
||||
- **验收标准**:ClickHouse 写入成功后才 commit offset,重启后无重复消费(依赖 ReplacingMergeTree 去重)
|
||||
|
||||
#### 任务 5.4:Admin Dashboard AI 用量区块
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 5.2
|
||||
- **交付物**:GetAdminDashboard RPC 补全 AI 用量统计区块(按 provider/model/时间窗聚合)
|
||||
- **验收标准**:Admin Dashboard 返回 AI 用量数据,无数据时显示"暂无数据"
|
||||
|
||||
---
|
||||
|
||||
### 3.5 P6 硬化期(2026-08-19 ~ 2026-08-30,11d,批次 5 并行)
|
||||
|
||||
#### 任务 6.1:CDC 多实例水平扩展
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 5.3(手动 commit)
|
||||
- **交付物**:CdcConsumer 支持多实例分摊 partition(consumer group 不变)
|
||||
- **验收标准**:2+ 实例消费同一 topic 无重复无遗漏
|
||||
|
||||
#### 任务 6.2:ExamCache 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.5:readyz 深度硬化
|
||||
|
||||
- **负责人**:ai11
|
||||
- **依赖**:任务 4.10
|
||||
- **交付物**:/readyz 检查项完善(ClickHouse 查询超时 1s + Redis ping + iam gRPC 超时 2s + CDC consumer lag < 1000)
|
||||
- **验收标准**:任一依赖不健康时 /readyz 返回 503,K8s 摘流量
|
||||
|
||||
---
|
||||
|
||||
## §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/coord,ISSUE-001)—— DataScope 解析
|
||||
- [ ] **analytics.proto 扩展至 12 RPC**(coord/ai11,ISSUE-003)—— gRPC stub 生成前提
|
||||
- [ ] **events.proto 补全 AIUsageEvent message**(coord,ISSUE-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-bff(ai03) | gRPC 50055 GetTeacherDashboard / GetClassPerformance 等 | P4 |
|
||||
| student-bff(ai04) | gRPC 50055 GetStudentDashboard / GetStudentWeakness 等 | P4 |
|
||||
| parent-bff(ai05) | gRPC 50055 GetParentDashboard | P4 |
|
||||
| ai 服务(ai12) | gRPC 50055 反向调用查学情(GetStudentMastery / GetLearningTrend) | P5 |
|
||||
| core-edu(ai08) | Kafka `edu.insight.mastery.updated`(推荐个性化练习) | P4 |
|
||||
| msg(ai10) | Kafka `edu.insight.warning.triggered`(推送通知) | P4 |
|
||||
|
||||
---
|
||||
|
||||
## §5 风险与缓解
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
| -------------------------------------------- | ---- | ------------------------------------------------------------------------------------------ |
|
||||
| iam.proto 未补全 GetEffectiveDataScope | P4 | 降级兜底:按 role 映射默认 DataScope + degraded: true(ISSUE-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 ExamCache;P4 阶段监控 consumer lag 告警 |
|
||||
| 掌握度算法精度不足 | P4 | v1 用加权滑动平均,P5+ 评估引入遗忘曲线 max 叠加(02 §9 已设计 MasteryMethod 枚举预留) |
|
||||
|
||||
@@ -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):
|
||||
|
||||
- I1:P2 即启用 gRPC server 50052(REST + gRPC 双入口并存,非"P2 仅 REST → P3 gRPC")
|
||||
- I2:直接用 shared-ts Outbox 工具包(非 iam 自建)
|
||||
- I3:首次实现即 DB 驱动 + Redis 缓存 PermissionGuard(废弃硬编码 ROLE_PERMISSIONS map)
|
||||
- I4:首次实现即注册 AuthMiddleware(Controller 通过 @Req() 注入用户上下文)
|
||||
- I5:P2 本地文件 RS256 密钥(IAM_PRIVATE_KEY_PATH / IAM_PUBLIC_KEY_PATH),P6 迁 Vault
|
||||
- I6:P2 即补全 iam_student_guardians 表 + GetChildrenByParent RPC + GET /iam/children
|
||||
- I7:/iam/v1/* 前缀(Controller 加 v1 前缀,Gateway 透传)
|
||||
- I8:统一 GET /iam/permissions/effective
|
||||
|
||||
**工作量分级**(president §3.2):iam 实际工作量 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.1:gRPC + 12 RPC + /readyz + DataScope
|
||||
### P2.1:gRPC + 8 RPC + RS256 + AuthMiddleware + Outbox(阻塞批次 2)
|
||||
|
||||
- **负责人**:ai06
|
||||
- **批次**:批次 1(P2)
|
||||
- **预估**:5-7 天
|
||||
- **前置依赖**:
|
||||
- coord 补全 iam.proto 至 12 RPC(ISSUE-005,阻塞项)
|
||||
- coord 补全 events.proto UserEvent/RoleEvent(ISSUE-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')`)+ 端点路径统一 I8(I7)
|
||||
6. iam_student_guardians 表 + GET /iam/v1/children REST + GetChildrenByParent gRPC(I6)
|
||||
7. shared-ts Outbox 接入,发布 UserEvent/RoleEvent(I2)
|
||||
8. DB 驱动 PermissionGuard 基础(废弃 permission.guard.ts 硬编码 ROLE_PERMISSIONS map,改调 IamService.getEffectivePermissions)(I3)
|
||||
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.user(userId/roles/dataScope)
|
||||
- /readyz 返回 5 项依赖状态
|
||||
- **就绪信号**:gRPC 50052 启用 + GetPublicKey RPC 可用 + HealthService.Check 返回 SERVING
|
||||
|
||||
### P2.2:三层角色 + DataScope + 视口 + 审计 + 缓存(P3 期间持续补,不阻塞)
|
||||
|
||||
- **负责人**:ai06
|
||||
- **批次**:批次 2-3 期间(P3 进行中持续补)
|
||||
- **预估**:12-15 天
|
||||
- **前置依赖**:P2.1 完成 + ISSUE-004(DataScope 枚举裁决)
|
||||
- **交付物**:
|
||||
1. 三层角色模型(system / organization / temporary,iam_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 topic,president §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-P6:RBAC CRUD + 2FA + Vault 迁移 + 硬化
|
||||
|
||||
- **负责人**:ai06
|
||||
- **批次**:批次 4-5(P5-P6 期间)
|
||||
- **预估**:8-10 天
|
||||
- **交付物**:
|
||||
1. RBAC CRUD 完整化(角色/权限/视口增删改,系统角色禁止删除)
|
||||
2. 2FA 实现(TOTP,pending-features §P2 提及)
|
||||
3. JWT 密钥迁移 Vault(P6,president 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 RPC(ISSUE-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 直连)
|
||||
|
||||
@@ -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 是消息服务,提供 NotificationService、PreferenceService、TemplateService,并基于 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 多渠道
|
||||
- P5:gRPC 50056 启用 + PushGatewayClient gRPC + 12 类事件 consumer + ES mapping
|
||||
- P6:测试覆盖 ≥ 80% + /readyz 硬化 + 黄金模板对齐 + README 修正
|
||||
|
||||
**批次归属**:批次 4(P5),依赖批次 3 content(ai09)就绪后启动,预估 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-T2:schema 迁移(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` 表 schema(code + type + title_template + content_template + default_channels + variables + locale + status),UNIQUE INDEX `(code, locale)`
|
||||
- **验收标准**:schema 定义 + 迁移脚本,符合 02-architecture-design.md §3.1.3
|
||||
|
||||
#### T4:新建 msg_outbox_events 表 + Outbox Publisher worker
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T1-T2
|
||||
- **交付物**:
|
||||
- `msg_outbox_events` 表 schema(event_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-003(Outbox 强制)
|
||||
|
||||
#### 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 + IdempotencyGuard(SETNX)
|
||||
|
||||
- **负责人**: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 主体实现
|
||||
|
||||
#### T7:ChannelDispatcher 多渠道抽象
|
||||
|
||||
- **负责人**: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-010(markAsRead 权限改 READ)
|
||||
|
||||
#### T12:NotificationPreference CRUD
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T1-T2
|
||||
- **交付物**:`preferences/` 目录(controller + service + repository + schema + dto)
|
||||
- **验收标准**:GET / PUT preferences 端点可用
|
||||
|
||||
#### T13:NotificationTemplate CRUD + render
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T3
|
||||
- **交付物**:`templates/` 目录(controller + service + repository + schema + dto),含 `{{variable}}` 占位符替换渲染
|
||||
- **验收标准**:CreateTemplate / GetTemplate / ListTemplates / RenderTemplate 可用
|
||||
|
||||
#### T15:createBatch 改批量 INSERT
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T10
|
||||
- **交付物**:createBatch 改为 `db.insert(notifications).values([...])` 批量 INSERT
|
||||
- **验收标准**:1 万条广播通知 < 5s(性能验收)
|
||||
|
||||
### P5 gRPC + 事件 + ES
|
||||
|
||||
#### T8:PushGatewayClient gRPC
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:D5(push-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(调用方向澄清)
|
||||
|
||||
#### T9:12 类 Kafka 事件 consumer
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T5、T6、D1-D2(events.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-013(events.proto 缺 4 类 message,阻塞)
|
||||
|
||||
#### T14:ES 索引 mapping + ensureIndex + 同步
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:
|
||||
- `config/elasticsearch.ts` 补 `notifications` 索引 mapping(ik_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-D4(msg.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 返回 SERVING,13 RPC 可调用
|
||||
- **关联 ISSUE**:ISSUE-009(RPC 数量待仲裁 13 vs 17)
|
||||
|
||||
### P6 硬化与对齐
|
||||
|
||||
#### T16:NotificationsModule 补 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 → down;Redis/ES/Kafka down → degraded)
|
||||
|
||||
#### T19:统一关闭到 LifecycleService
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:移除 main.ts 重复 closeDb/closeEs,统一到 LifecycleService,8 步关闭序列
|
||||
- **验收标准**:符合 02-architecture-design.md §6.7 优雅关闭顺序
|
||||
|
||||
#### T20-T21:DB 改 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 message(coord 维护)— 🔴 阻塞 T9
|
||||
- [ ] **D2**:events.proto GradeEvent 补 class_id;全部事件补 student_ids[](coord 维护)— 🔴 阻塞 T9 fan-out
|
||||
- [ ] **D3**:msg.proto 补 BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / RecallNotification RPC(coord 维护)— 🔴 阻塞 gRPC(依赖 ISSUE-009 仲裁)
|
||||
- [ ] **D4**:msg.proto 补 NotificationPreferenceService + NotificationTemplateService(coord 维护)— 🔴 阻塞 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 就绪后切换 |
|
||||
|
||||
@@ -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.GetChildrenByParent(I6 裁决)+ 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 监控 + 灰度发布 | — |
|
||||
|
||||
**关键路径**:批次 0(coord 补 proto)→ 批次 1(iam gRPC + GetChildrenByParent)→ 批次 2(core-edu gRPC)→ **批次 3(parent-bff P4 MVP)** → 批次 4(msg 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-007,P5 阶段阻塞)
|
||||
|
||||
---
|
||||
|
||||
## §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.2:GraphQL 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 ≤ 7,cost ≤ 1000,02 §9 #5)
|
||||
- `packages/shared-ts/contracts/graphql/parent-bff.graphql`(SDL-first 集中管理,对齐 coord ARB-001 模式)
|
||||
- **验收标准**:POST /graphql 可内省 schema;深度超 7 的查询被拒;cost 超 1000 被拒
|
||||
|
||||
### P4.3:DownstreamClient 抽象 + gRPC mock
|
||||
|
||||
- **负责人**:ai05
|
||||
- **依赖**:P4.1 + ai03 DownstreamClient 抽象模式(teacher-bff 参考)
|
||||
- **交付物**:
|
||||
- `src/clients/grpc/grpc.factory.ts`(gRPC client 创建 + interceptor:trace/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.4:ChildGuard + 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.5:Dashboard / 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.6:Redis 缓存层 + 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-instrumentations(http/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.1:msg 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.2:Notification 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.3:push-gateway HTTP /internal/push 接入
|
||||
|
||||
- **负责人**:ai05
|
||||
- **依赖**:push-gateway /internal/push 就绪(ai02)或 mock
|
||||
- **交付物**:
|
||||
- `src/clients/http/push-http.client.ts`(HTTP POST /internal/push,U2 仲裁)
|
||||
- env.ts 启用 PushGatewayUrl
|
||||
- **验收标准**:mock 模式下 pushViaHttp 返回 success
|
||||
|
||||
### P5.4:Kafka 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 去重
|
||||
- DLQ:edu.parent-bff.dlq
|
||||
- **验收标准**:
|
||||
- 收到 edu.teaching.grade.recorded 后 bff:parent:grades:{childId} 缓存失效
|
||||
- 家长关闭"成绩推送"偏好时,该家长不收到推送
|
||||
- 重复 event_id 不重复处理
|
||||
|
||||
### P5.5:通知偏好过滤逻辑
|
||||
|
||||
- **负责人**:ai05
|
||||
- **依赖**:P5.4
|
||||
- **交付物**:通知偏好过滤逻辑集成到 notification-push.handler(02 §5.4)
|
||||
- 拉取家长 NotificationPreferences(Redis 缓存 300s)
|
||||
- 按 eventTypeMap 映射事件类型 → 偏好开关
|
||||
- 取 prefs.channels 与 event.channels 交集
|
||||
- **验收标准**:偏好开关为 false 时不推送;channels 无交集时不推送
|
||||
|
||||
### P6.1:opossum 熔断器 per-service
|
||||
|
||||
- **负责人**:ai05
|
||||
- **依赖**:P5 完成
|
||||
- **交付物**:
|
||||
- `src/clients/grpc/circuit-breaker.ts`(opossum,per-downstream-service 独立 circuit:iam/core-edu/data-ana/msg)
|
||||
- 熔断开启时抛 BFF_PARENT_SERVICE_UNAVAILABLE(503)
|
||||
- metrics: parent_bff_circuit_state Gauge
|
||||
- **验收标准**:下游连续失败 5 次熔断开启;30s 后半开探测
|
||||
|
||||
### P6.2:HPA + 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.3:SLO 告警规则 + 灰度发布
|
||||
|
||||
- **负责人**: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 | P4(P0 阻塞) | ⏳ |
|
||||
| iam_student_guardians 表 | 表已建 + Repository 查询方法可用 | ai06 | P4(P0 阻塞) | ⏳ |
|
||||
| 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-portal(ai15) |
|
||||
| 核心 Query 可执行 | dashboard / myChildren / childGrades / childAnalytics | parent-portal |
|
||||
| 核心 Mutation 可执行 | selectChild / markNotificationRead(P5) | 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 拦截 + 固定 UserInfo(parent 角色)+ 固定 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 | P4(P0) | 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 集中管理 |
|
||||
|
||||
@@ -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 角色**:Remote(Shell = teacher-portal :4000)
|
||||
- **端口**:4002(dev/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 周)
|
||||
> **关键路径**(红色 crit):MF 骨架 → GraphQL client → ChildSwitcher → 质量保障 → WebSocket → 通知中心
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### P4 阶段任务
|
||||
|
||||
#### P4-1:MF Remote 骨架 + next.config.js + 健康检查
|
||||
|
||||
- **负责人**:ai15
|
||||
- **交付物**:⚠️ 由 ai15 自行补充
|
||||
- **依赖**:见 [contracts/parent-portal_contract.md](../contracts/parent-portal_contract.md)
|
||||
- **验收标准**:⚠️ 由 ai15 自行补充
|
||||
- **依赖**:teacher-portal Shell MF 配置就绪(ARB-002,ai13 P2 交付);ISSUE-002 仲裁(MF shared 清单)
|
||||
- **交付物**:
|
||||
- `apps/parent-portal/next.config.js`(NextFederationPlugin,Remote 角色,`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.json,strict)
|
||||
- `apps/parent-portal/package.json`(依赖对齐 Shell:react 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.js(MF 拓扑验证)
|
||||
4. feature flag `NEXT_PUBLIC_MF_ENABLED` 可控制 MF 开关
|
||||
|
||||
#### P4-2:GraphQL client 接入 + MSW mock 层
|
||||
|
||||
- **负责人**:ai15
|
||||
- **依赖**:P4-1;ISSUE-001 仲裁(确认 GraphQL);teacher-portal Shell 暴露 GraphQLProvider(ARB-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-3:ChildSwitcher + useChildSwitcher + Zustand slice
|
||||
|
||||
- **负责人**:ai15
|
||||
- **依赖**:P4-2;ISSUE-009 仲裁(switchChild 是 GraphQL Mutation 还是纯前端状态)
|
||||
- **交付物**:
|
||||
- `apps/parent-portal/src/stores/childSwitcherSlice.ts`(Zustand slice:children / 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 子女显示 EmptyChildState;1 子女不显示 TabBar;2-3 子女显示 Tab
|
||||
4. 切换子女竞态:快速连续切换,旧请求 abort,新数据正确显示
|
||||
|
||||
#### P4-4:Dashboard 页面 + 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-9:Vitest 单测 + 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-10:Dockerfile 多阶段构建
|
||||
|
||||
- **负责人**:ai15
|
||||
- **依赖**:P4-1
|
||||
- **交付物**:`apps/parent-portal/Dockerfile`(builder + runtime,node:22-alpine)
|
||||
- **验收标准**:
|
||||
1. `docker build` 成功
|
||||
2. HEALTHCHECK 指向 `/api/health`
|
||||
3. 镜像体积 < 300MB
|
||||
|
||||
### P5 阶段任务
|
||||
|
||||
#### P5-1:WebSocket 接入 + 事件处理
|
||||
|
||||
- **负责人**:ai15
|
||||
- **依赖**:push-gateway :8081/ws 就绪(ai02);parent-portal P4 完成
|
||||
- **交付物**:
|
||||
- `apps/parent-portal/src/hooks/useWebSocket.ts`(连接 push-gateway ws,JWT 鉴权,自动重连)
|
||||
- 事件处理:`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-1:Web Vitals + OTel browser SDK
|
||||
|
||||
- **交付物**:`next/web-vitals` 上报 + OTel browser SDK(复用 Shell 暴露的 TracerProvider)
|
||||
- **验收标准**:LCP/CLS/TTFB 指标上报到 Gateway
|
||||
|
||||
#### P6-2:A11y 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-5:PWA(Service 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 exposes(AppShell + 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/Logger(coord 维护) | coord | ⏳ | 基础工具 |
|
||||
| contracts | Permissions 常量(coord 维护) | coord | ⏳ | 权限校验 |
|
||||
| ui-tokens / ui-components / hooks | 三层令牌 + shadcn + usePermission/useAuth(ai07/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 返回固定 JWT(parent 角色) | 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 预排期;仲裁若改 REST,P4-2 重写(预计 1d) |
|
||||
| iam GetChildrenByParent 缺失(ISSUE-010) | 多子女场景无法落地 | mock 固定 2 子女开发;coord 跟踪 ai06 P3 补全 |
|
||||
| MF SSR 对齐复杂 | Remote SSR 需 Shell 上下文 | 优先 CSR,仅 Dashboard 首屏 SSR;P4-1 PoC 验证 |
|
||||
| parent-bff 契约未最终确认 | GraphQL schema 可能变动 | P4 启动前与 ai05 对齐 schema;MSW 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 强制)
|
||||
- A11y:jsx-a11y error 级零违规
|
||||
|
||||
> 提交前校验见 [project_rules §8](../../../../.trae/rules/project_rules.md),commit 遵循 Conventional Commits:`feat(parent-portal): ...`
|
||||
|
||||
@@ -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 仲裁
|
||||
- **批次 4(P5,13 天)**:完整实现 /internal/push + WebSocket 生命周期 + Redis Pub/Sub + Kafka 消费 + /readyz + /metrics + OTel + 多阶段 Dockerfile + slog + shared-go 接入 + 集成测试
|
||||
- **批次 5(P6 硬化,5 天)**:Reconnect 协议 + Redis Stream 持久化 + 测试覆盖率 ≥ 80% + ADR/非功能性需求/失败模式章节补全
|
||||
|
||||
**关键路径依赖**:
|
||||
- 批次 0.14:shared-go 包骨架(coord)—— tracer/logger/jwks/env 4 模块
|
||||
- 批次 1:iam P2.1(JWT RS256 + JWKS 端点)—— WebSocket 鉴权前置
|
||||
- 批次 4:msg 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)
|
||||
> **总工期**:批次 0(2 天)+ 批次 4(13 天)+ 批次 5(5 天)= **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 批次 4(P5):完整实现(13 天)
|
||||
|
||||
#### 任务 4.1:Origin 校验 + CheckOrigin 白名单(P0,1 天)
|
||||
|
||||
- **负责人**: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-Token(P0,1 天)
|
||||
|
||||
- **负责人**: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 控制帧 + SetReadDeadline(P0,1 天)
|
||||
|
||||
- **负责人**: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=5(P0,1 天)
|
||||
|
||||
- **负责人**: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.5:Send 通道满时指标 + 日志(P0,1 天)
|
||||
|
||||
- **负责人**: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.6:Dockerfile 重构(P0,1 天)
|
||||
|
||||
- **负责人**: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 替换标准 log(P0,1 天)
|
||||
|
||||
- **负责人**: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-go(P0,1 天)
|
||||
|
||||
- **负责人**:ai02
|
||||
- **依赖**:任务 4.7 + 批次 0.14(shared-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.9:Redis Pub/Sub 跨实例广播(P1,2 天)
|
||||
|
||||
- **负责人**: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.10:Redis SET 在线状态 + 启动重建(P1,1 天)
|
||||
|
||||
- **负责人**: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 软失败(P1,1 天)
|
||||
|
||||
- **负责人**: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 连接(P1,1 天)
|
||||
|
||||
- **负责人**: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.13:Kafka 消费 edu.notification.requested(P2,2 天)
|
||||
|
||||
- **负责人**: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.14:JWT RS256 升级 + JWKS fetcher(P2,1 天)
|
||||
|
||||
- **负责人**:ai02
|
||||
- **依赖**:任务 4.8(shared-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:设计决策记录章节回写(P2,1 天)
|
||||
|
||||
- **负责人**:ai02
|
||||
- **依赖**:任务 4.13
|
||||
- **交付物**:02-architecture-design.md 新增 §5.4(ISSUE-056)
|
||||
- 标题:"设计决策记录:gRPC vs HTTP 协议选型(coord 已采纳 P1)"
|
||||
- 正文标注"coord 已采纳,见 coord-final-decisions P1/P5/P6"
|
||||
- 记录决策背景、方案对比、采纳理由
|
||||
- **验收标准**:章节存在且标注正确
|
||||
|
||||
#### 任务 4.16:与 msg 联调 /internal/push 双通道(P2,1 天)
|
||||
|
||||
- **负责人**: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:集成测试 + 端到端验证(P2,1 天)
|
||||
|
||||
- **负责人**:ai02
|
||||
- **依赖**:任务 4.16
|
||||
- **交付物**:集成测试用例 + 端到端验证报告
|
||||
- 单实例 1000 连接压测
|
||||
- 双实例跨实例推送验证
|
||||
- Redis 故障降级验证
|
||||
- Kafka 消费积压验证
|
||||
- **验收标准**:
|
||||
- [workline.md](../workline.md) §8 push-gateway 行更新为 ✅
|
||||
- [matrix.md](../matrix.md) §8 push-gateway 就绪信号 ✅
|
||||
|
||||
### 3.3 批次 5(P6):硬化(5 天)
|
||||
|
||||
#### 任务 5.1:Reconnect 协议(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.2:Redis Stream 替代 Pub/Sub(2 天)
|
||||
|
||||
- **负责人**: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.4:ADR + 非功能性需求 + 失败模式章节补全(2 天,可与 5.1/5.2 并行)
|
||||
|
||||
- **负责人**:ai02
|
||||
- **依赖**:批次 4 完成 + ISSUE-007 仲裁
|
||||
- **交付物**:02-architecture-design.md 新增章节
|
||||
- §14 ADR(Architecture Decision Records)
|
||||
- ADR-001:gorilla/websocket 选型(vs nhooyr/websocket)
|
||||
- ADR-002:Redis Pub/Sub vs Stream(P5 Pub/Sub → P6 Stream 演进)
|
||||
- ADR-003:心跳间隔 30s/60s 选型依据
|
||||
- ADR-004:10w 容量依据(goroutine-per-connection 内存估算)
|
||||
- ADR-005:HTTP + 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 基础设施 | coord(P1) | Redis 7.x 可访问 | ✅ P1 已就绪 | 任务 4.9/4.10 |
|
||||
| Kafka 基础设施 | coord(P1) | 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 mock:msg 未就绪前,push-gateway 内置定时器每 30s 生成 mock 事件推所有在线客户端
|
||||
- JWT 验签 mock:iam 未就绪前使用本地固定 RS256 公钥(或 DevMode dev-token)
|
||||
- Kafka 订阅 mock:msg 未就绪前不启动 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 任务,接口冻结后立即对接 |
|
||||
|
||||
@@ -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)
|
||||
- **端口**:3009(HTTP 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 起直接 GraphQL(Yoga + 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-bff,3 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 排期。
|
||||
> **关键路径**(crit):schema 起草 → 文档回写 → 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 核心 Query:studentDashboard / myHomework / myGrades / myExams / myClasses / currentUser
|
||||
- P3 核心 Mutation:submitHomework
|
||||
- 提交 coord 仲裁(president §2.2:coord 在批次 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 client(iam + 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 interceptor:traceId 透传 + 错误归一化
|
||||
- **验收标准**:可调用 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`:myHomework(core-edu)
|
||||
- `src/student/resolvers/grades.resolver.ts`:myGrades(core-edu,B4 强制 userId 比对)
|
||||
- `src/student/resolvers/exams.resolver.ts`:myExams(core-edu)
|
||||
- `src/student/resolvers/classes.resolver.ts`:myClasses(core-edu)
|
||||
- `src/student/resolvers/auth.resolver.ts`:currentUser(聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports)
|
||||
- 并行编排:Promise.allSettled + 部分降级(president §2.6 方案 B:data 内 degraded 字段)
|
||||
- **验收标准**:5 个核心 Query 可执行,返回 ActionState 信封
|
||||
- **状态**:⏳ 待办
|
||||
|
||||
#### P3.4 Mutation(submitHomework)+ AuthorizationGuard(B4)
|
||||
|
||||
- **负责人**: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-30s,B6)
|
||||
- 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`:/healthz(liveness)+ /readyz(readiness)
|
||||
- P3 /readyz 探针:iam gRPC 50052 + core-edu gRPC 50053(2 项,president §2.4)
|
||||
- 必需依赖失败返回 503;可选依赖软失败返回 200 + degraded
|
||||
- **验收标准**:/readyz 返回 2 项探针状态
|
||||
- **状态**:⏳ 待办
|
||||
|
||||
#### P3.8 横切关注点
|
||||
|
||||
- **负责人**:ai04
|
||||
- **依赖**:P3.1
|
||||
- **交付物**:
|
||||
- `src/shared/observability/logger.ts`(pino,service: 'student-bff')
|
||||
- `src/shared/observability/metrics.ts`(prom-client,11 个 student_bff_* 指标)
|
||||
- `src/shared/observability/tracer.ts`(OTel,serviceName: '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 client:TextbookService + ChapterService + QuestionService + KnowledgeGraphService
|
||||
- data-ana gRPC client:AnalyticsService.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 client:NotificationService.ListNotifications + MarkAsRead
|
||||
- ai gRPC client:AiService.Chat + StreamChat(SSE 流式透传)
|
||||
- Query/Mutation 扩展:myNotifications / markAsRead Mutation / aiChat / aiStreamChat
|
||||
- **状态**:⏳ 待办
|
||||
|
||||
#### P5.4-P5.6 Kafka EventSubscriber + push-gateway + 探针扩展
|
||||
|
||||
- **交付物**:
|
||||
- `src/student/events/event-subscriber.ts`:Kafka 消费者组(B7 P5 才订阅)
|
||||
- 订阅 topic:edu.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) | coord(president §2.5) | proto message + RPC 签名定义 | 批次 2 P3 | ⏳ |
|
||||
| buf.gen.yaml gRPC 插件 | coord(批次 0.9) | grpc/node 插件配置 | 批次 2 P3 | ⏳ |
|
||||
| ai03 DownstreamClient 抽象 | ai03(B8 裁决) | teacher-bff P2 产出可复用抽象 | 批次 2 P3 | ⏳ |
|
||||
| iam gRPC 50052 + 12 RPC | ai06(I1 裁决) | HealthService.Check = SERVING | 批次 2 P3 | ⏳ |
|
||||
| core-edu gRPC 50053 + 22 RPC | ai08(C2 裁决) | 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 第一版 | coord(president §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 | ai14(student-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) |
|
||||
|
||||
@@ -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.6(ai14 P3 功能范围)+ §7.14(ai14 工作内容最终清单)+ ARB-002 §2.3(P3 首个 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)):
|
||||
|
||||
- 批次 2(P3):ai14 与 ai07 + ai08 + ai04 + ai03扩展 并行启动(`b2d, after b1d, 8d`)
|
||||
- 批次 3(P4):与 ai09 + ai11 + ai05 + ai15 并行
|
||||
- 批次 4(P5):与 ai10 + ai02 + ai12 + ai03扩展 并行
|
||||
- 批次 5(P6):与 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 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### P2:MF Remote 骨架预埋
|
||||
|
||||
- **负责人**:ai14
|
||||
- **交付物**:⚠️ 由 ai14 自行补充
|
||||
- **依赖**:见 [contracts/student-portal_contract.md](../contracts/student-portal_contract.md)
|
||||
- **验收标准**:⚠️ 由 ai14 自行补充
|
||||
- **裁决依据**:ARB-002 §2.3(P3 首个 Remote,但 P2 可预埋骨架)+ 总裁裁决 §3.6(ai14 P3 起步)+ §2.17(MF 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.tsx,MF 关闭时可独立渲染)
|
||||
- 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/hooks,ai13 批次 0.15 已完成)
|
||||
- teacher-portal MF Shell 配置(ai13 P2,exposes 就绪)
|
||||
- **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.6(ai14 P3 功能范围)+ ARB-001 §1.3(ActionState 信封 + 降级模式方案 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` query(upcomingHomework + 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` hook,5 分钟重新同步,倒计时基于服务器时间)
|
||||
- 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 schema(ai04 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 gRPC(ai08 P3,提供 ExamService/HomeworkService/GradeService/AttendanceService/ClassService)
|
||||
- iam gRPC(ai06 P2,GetUserInfo + GetEffectivePermissions + GetViewports)
|
||||
- data-ana gRPC(ai11 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 gRPC(ai09 P4,TextbookService + ChapterService + KnowledgeGraphService)
|
||||
- data-ana gRPC(ai11 P4,AnalyticsService.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 WebSocket(ai02 P5,`/ws` 端点)
|
||||
- msg gRPC(ai10 P5,NotificationService)
|
||||
- ai 服务 gRPC(ai12 P5,AiService.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 AA:eslint-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.1(P75)
|
||||
- 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 Shell(ai13 P2) | exposes AppShell/GraphQLProvider/useGraphQLClient/useAuth/usePermission + shared singleton | P2 启动 | ⏳ 待 ai13 P2 |
|
||||
| api-gateway HTTP :8080(ai01 P3) | `/api/v1/student/*` 反向代理 student-bff 可用 | P3 启动 | ⏳ 待 ai01 P3 |
|
||||
| student-bff GraphQL(ai04 P3) | `POST /graphql` :3009 + 核心 Query/Mutation 可执行 | P3 启动 | ⏳ 待 ai04 P3 |
|
||||
| student-bff GraphQL schema(ai04 + ISSUE-014-02 仲裁) | `packages/shared-ts/contracts/graphql/student-bff.graphql` 创建 | P3 启动 | ⏳ 待仲裁 |
|
||||
| core-edu gRPC 50053(ai08 P3) | ExamService/HomeworkService/GradeService/AttendanceService/ClassService 全部 RPC | P3 启动 | ⏳ 待 ai08 P3 |
|
||||
| iam gRPC 50052(ai06 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 50054(ai09 P4) | TextbookService + ChapterService + KnowledgeGraphService | P4 启动 | ⏳ 待 ai09 P4 |
|
||||
| data-ana gRPC 50055(ai11 P4) | AnalyticsService.GetStudentWeakness + GetLearningTrend | P4 启动 | ⏳ 待 ai11 P4 |
|
||||
| push-gateway WebSocket :8081/ws(ai02 P5) | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
|
||||
| msg gRPC 50056(ai10 P5) | NotificationService.ListNotifications + MarkAsRead | P5 启动 | ⏳ 待 ai10 P5 |
|
||||
| ai 服务 gRPC 50057(ai12 P5,可选) | AiService.Chat(SSE 流式) | 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-portal(ai13 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/mutation,schema 就绪后切换;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**: ai14(student-portal)
|
||||
**Branch**: feat-review-student-portal-docs-9yN6Av
|
||||
**Coordinator**: coord-ai
|
||||
|
||||
@@ -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 consumer(push-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 排期。
|
||||
> **总工期**:28d(P2 8d + P3 5d + P4 4d + P5 8d + P6 3d),与 workline.md §1 总时间线对齐(批次1 8d + 批次2 5d + 批次4 8d + P4/P6 合并 7d)。
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### P2:GraphQL schema + 5 Query + DataLoader + ActionState
|
||||
### P2:GraphQL 基础 + DownstreamClient + 越权防御(批次 1,8d)
|
||||
|
||||
> 裁决依据:president §3.1 Must Have 13 项(teacher-bff 无 DB,G10/G11 不适用,实际 11 项 + DownstreamClient + admin 预留)
|
||||
|
||||
#### 3.1 GraphQL schema 第一版 + admin 预留(2d,P0 阻塞 ai13)
|
||||
|
||||
- **负责人**:ai03
|
||||
- **依赖**:coord 仲裁 ARB-001(已裁决)
|
||||
- **交付物**:
|
||||
- `packages/shared-ts/contracts/graphql/teacher-bff.graphql` — P2 schema
|
||||
- Yoga endpoint `POST /graphql`
|
||||
- 5 Query:dashboard / viewports / me / classes / class
|
||||
- DataLoader + ActionState 信封 + 降级模式 B
|
||||
- **依赖**:iam gRPC(ai06)+ coord 仲裁 ARB-001
|
||||
- **验收标准**:5 Query 可用 + ActionState 信封 + depth ≤ 7
|
||||
- **完整 P3-P6 任务**:⚠️ 由 ai03 自行补充
|
||||
- `packages/shared-ts/contracts/graphql/teacher-bff.schema.graphql` — P2 schema(5 Query: dashboard/viewports/me/classes/class + admin 命名空间占位)
|
||||
- admin 命名空间预留:schema 中声明 `admin` Query/Mutation 类型骨架(无实际 Resolver),P6 实现
|
||||
- **验收标准**: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 Resolver(3d,P0 阻塞 ai13)
|
||||
|
||||
- **负责人**:ai03
|
||||
- **依赖**:3.1 schema + iam gRPC 50052(ai06 P2.1)
|
||||
- **交付物**:
|
||||
- `POST /graphql` Yoga GraphQL endpoint
|
||||
- 5 Query Resolver:dashboard / viewports / me / classes / class
|
||||
- Dashboard Resolver P2 实现方式(ISSUE-032):P2 仅调 iam gRPC,未启用字段返回 null + `extensions.warning = "field_unavailable_in_p2"`
|
||||
- **验收标准**:5 Query 可执行 + ActionState 信封 + 降级模式 B(success=true + error=null + data 内 degraded 字段)
|
||||
- **裁决引用**:B1 + ARB-001 §1.4 + president §2.6/§2.8
|
||||
|
||||
#### 3.3 DownstreamClient 抽象 + iam gRPC(2d,P0 阻塞 ai04/ai05)
|
||||
|
||||
- **负责人**:ai03
|
||||
- **依赖**:iam gRPC 50052(ai06 P2.1)
|
||||
- **交付物**:
|
||||
- `src/clients/` DownstreamClient 抽象层(B8:BFF 模式 v2 标准抽象,3 个 BFF 统一使用,回写 teacher-bff)
|
||||
- IamClient gRPC 实现:`@grpc/grpc-js` + `@bufbuild/protobuf`,调 iam:50052
|
||||
- gRPC interceptor:注入 trace context(traceparent)+ x-user-id metadata + metrics
|
||||
- **验收标准**:IamClient gRPC 调 iam GetUserInfo/GetViewports/GetEffectiveAccess 成功
|
||||
- **裁决引用**:B2(首次实现即 gRPC)+ B8(DownstreamClient 抽象)
|
||||
|
||||
#### 3.4 AuthorizationGuard 越权防御(1d,P0)
|
||||
|
||||
- **负责人**: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 信封 + 降级模式 B(1d)
|
||||
|
||||
- **负责人**: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 50052(teacher-bff 无 DB,2 项)
|
||||
- **验收标准**:/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 liveness(G3) | ✅ 已具备 | health.controller.ts |
|
||||
| GlobalErrorFilter(G8) | ✅ 已具备 | global-error.filter.ts |
|
||||
| ESM import .js 后缀(G12) | ✅ 已具备 | 源码已用 .js |
|
||||
| import type(G13) | ✅ 已具备 | 源码已用 import type |
|
||||
| OTel tracer(G6) | ⚠️ Should Have | P2 可降级为 logger-only,P5 补全 |
|
||||
| /metrics 业务指标(G5) | ⚠️ Should Have | P2 仅暴露 process metrics |
|
||||
| DataLoader(B1) | ⚠️ Should Have | P2 可先用普通 resolver,P4 全量接入 |
|
||||
| Redis 5-30s 短缓存(B6) | ⚠️ Should Have | P3 接入聚合缓存 |
|
||||
| Zod 全量验证(G7) | ⚠️ Nice to Have | P2 先校验核心 Query,P6 全量 |
|
||||
| 优雅关闭 SIGTERM(G9) | ⚠️ Nice to Have | P2 已有基础,P6 补全关闭顺序 |
|
||||
|
||||
**P2 退出标准**:POST /graphql 可用 + 5 Query Resolver + DownstreamClient + AuthorizationGuard + ActionState 信封 + /readyz 2 项探针 + 错误码 BFF_TEACHER_* + admin 命名空间预留。
|
||||
|
||||
---
|
||||
|
||||
### P3:core-edu gRPC 扩展(批次 2,5d)
|
||||
|
||||
> 裁决依据:§2.3 跨阶段扩展例外(新增下游 gRPC 调用 + AuthorizationGuard 内部实现替换 + /readyz 探针扩展)
|
||||
|
||||
#### 3.8 CoreEduClient gRPC(2d,P0)
|
||||
|
||||
- **负责人**:ai03
|
||||
- **依赖**:core-edu gRPC 50053(ai08 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.9(P3 接入 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 项探针。
|
||||
|
||||
---
|
||||
|
||||
### P4:content + data-ana gRPC 扩展(4d)
|
||||
|
||||
> 裁决依据:§2.3 跨阶段扩展例外
|
||||
|
||||
#### 3.12 ContentClient + DataAnaClient gRPC(2d)
|
||||
|
||||
- **依赖**:content gRPC 50054(ai09 P4)+ data-ana gRPC 50055(ai11 P4)
|
||||
- **交付物**:
|
||||
- ContentClient gRPC:KnowledgeGraphService(GetPrerequisites / GetLearningPath)
|
||||
- DataAnaClient gRPC:AnalyticsService(GetClassPerformance / 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-ana(5 项:Redis + iam + core-edu + content + data-ana)
|
||||
|
||||
**P4 退出标准**:content + data-ana gRPC + 5 Query + DataLoader 全量 + /readyz 5 项探针。
|
||||
|
||||
---
|
||||
|
||||
### P5:ai + msg gRPC 扩展 + SSE + Kafka(批次 4,8d)
|
||||
|
||||
> 裁决依据:B7(P5 push-gateway 落地后再订阅 Kafka)
|
||||
|
||||
#### 3.15 AiClient + MsgClient gRPC(2d)
|
||||
|
||||
- **依赖**:ai gRPC 50057(ai12 P5)+ msg gRPC 50056(ai10 P5)
|
||||
- **交付物**:
|
||||
- AiClient gRPC:AiService(Chat / StreamChat streaming / GenerateQuestion / OptimizeExpression)
|
||||
- MsgClient gRPC:NotificationService(ListNotifications / SearchNotifications / MarkAsRead)
|
||||
- GraphQL Query 扩展:notifications + Mutation:generateQuestion / markNotificationAsRead
|
||||
|
||||
#### 3.16 SSE 流式透传(2d)
|
||||
|
||||
- **交付物**:`GET /ai/chat/stream` SSE 端点(ai.StreamChat gRPC stream → BFF → 前端 EventSource)
|
||||
- **验收标准**:SSE 三层透传端到端通 + 背压处理 + 超时取消
|
||||
- **裁决引用**:02 文档 §10
|
||||
|
||||
#### 3.17 Kafka consumer(2d,push-gateway 落地后)
|
||||
|
||||
- **交付物**:
|
||||
- Kafka consumer 订阅 `edu.identity.user.role_changed` / `edu.identity.role.updated`,精确失效 Redis 权限缓存
|
||||
- 幂等性:基于 event_id 去重(Redis SETNX)
|
||||
- **验收标准**:权限变更秒级缓存失效 + 幂等消费
|
||||
- **裁决引用**:B7(P5 push-gateway 落地后再订阅)
|
||||
|
||||
#### 3.18 /readyz + ai + msg 探针 + Should Have 补全(2d)
|
||||
|
||||
- **交付物**:
|
||||
- /readyz 探针扩展 ai + msg(7 项: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 补全。
|
||||
|
||||
---
|
||||
|
||||
### P6:admin 命名空间 + 硬化(3d)
|
||||
|
||||
> 裁决依据:president §5.1(admin-portal 复用 teacher-bff)+ P6 硬化
|
||||
|
||||
#### 3.19 admin 命名空间实现(1d)
|
||||
|
||||
- **依赖**:admin-portal(ai16 P6)
|
||||
- **交付物**:
|
||||
- admin schema 命名空间 Resolver 实现(P2 预留的占位类型填充实际 Resolver)
|
||||
- admin Query/Mutation:用户管理 / 角色权限管理 / 学校设置 / 组织管理 / 审计日志查询
|
||||
- **验收标准**:admin namespace 可内省 + admin-portal 可消费
|
||||
- **裁决引用**:president §5.1(admin-portal 复用 teacher-bff GraphQL endpoint)
|
||||
|
||||
#### 3.20 熔断 / 重试 / 超时(1d)
|
||||
|
||||
- **交付物**:
|
||||
- Circuit Breaker(opossum,per-downstream-service)
|
||||
- Retry(gRPC interceptor,仅幂等 RPC,指数退避)
|
||||
- Timeout(per-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 50052(ai06)+ core-edu gRPC 50053(ai08,P3+)
|
||||
- **我的就绪信号**:POST /graphql 可用 + dashboard Query 返回正确数据
|
||||
### 4.1 我依赖的上游就绪信号
|
||||
|
||||
| 上游 | 就绪信号 | 阶段 | 状态 |
|
||||
| ---- | -------- | ---- | ---- |
|
||||
| iam(ai06) | gRPC 50052 + 8 RPC(GetUserInfo/GetViewports/GetEffectivePermissions/GetEffectiveAccess/Logout/GetPublicKey/BatchGetUsers/GetChildrenByParent) | P2 | ⏳ |
|
||||
| core-edu(ai08) | gRPC 50053 + ExamService/HomeworkService/GradeService | P3 | ⏳ |
|
||||
| content(ai09) | gRPC 50054 + KnowledgeGraphService | P4 | ⏳ |
|
||||
| data-ana(ai11) | gRPC 50055 + AnalyticsService(含 GetTeacherDashboard,ISSUE-027 补全) | P4 | ⏳ |
|
||||
| ai(ai12) | gRPC 50057 + AiService(含 StreamChat) | P5 | ⏳ |
|
||||
| msg(ai10) | gRPC 50056 + NotificationService | P5 | ⏳ |
|
||||
| push-gateway(ai02) | /internal/push 落地(B7 Kafka 订阅前提) | P5 | ⏳ |
|
||||
| admin-portal(ai16) | admin schema 需求确认 | P6 | ⏳ |
|
||||
|
||||
### 4.2 我的就绪信号(供下游消费)
|
||||
|
||||
| 阶段 | 就绪信号 | 消费方 |
|
||||
| ---- | -------- | ------ |
|
||||
| P2 | POST /graphql 可用 + 5 Query + admin 预留 | teacher-portal(ai13) |
|
||||
| P2 | DownstreamClient 抽象(B8 回写) | student-bff(ai04)/ parent-bff(ai05) |
|
||||
| P3 | exams/homework/grades Query + Mutation | teacher-portal(ai13) |
|
||||
| P4 | 学情分析 Query + DataLoader | teacher-portal(ai13) |
|
||||
| P5 | SSE 流式 + notifications Query | teacher-portal(ai13) |
|
||||
| P6 | admin namespace 可用 | admin-portal(ai16) |
|
||||
|
||||
---
|
||||
|
||||
## §5 跨阶段扩展例外验收清单
|
||||
|
||||
> 裁决依据:president §2.3(ISSUE-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 已有探针检查项(仅新增)
|
||||
|
||||
@@ -74,7 +74,7 @@ gantt
|
||||
- 学生列表页(classStudents GraphQL query,iam 数据)
|
||||
- 个人设置页(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 schema(ai03 + coord 仲裁 ISSUE-037) | packages/shared-ts/contracts/graphql/ 第一版 | P2 启动 | ⏳ 待 coord 仲裁 |
|
||||
| api-gateway HTTP :8080(ai01) | /api/teacher/graphql + /api/auth/login 路由可用 | P2 启动 | ⏳ 待 ai01 |
|
||||
| api-gateway HTTP :8080(ai01) | /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/ws(ai02 P5) | WS 连接可建立 + 推送可接收 | P5 启动 | ⏳ 待 ai02 P5 |
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
> 日期:2026-07-09(v1 by ai06)/ 2026-07-10(v2 审核修订 by ai11)
|
||||
> 关联文档:[ai-allocation.md](../../../docs/architecture/ai-allocation.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[pending-features.md](../../../docs/architecture/roadmap/pending-features.md)、[known-issues.md](../../../docs/troubleshooting/known-issues.md)
|
||||
> 审核修订说明:ai-allocation.md §3.2 将 data-ana 重新分配给 ai11(ai 单独分配给 ai12),ai11 接手后对 ai06 v1 进行审核,本版为 v2 修订。
|
||||
> v2.1 审核修订(ai11):核查发现多处 004 章节引用断裂(§4.2/§11.4/§11.5/§15.3 在 004 正文中不存在,coord-cross-review §6 整改清单要求 coord 补充但尚未落实),已改为引用 coord-cross-review.md 对应裁决;修正 topic 命名统一为 `edu.insight.mastery.updated`;补充 events.proto AIUsageEvent 缺失说明。
|
||||
|
||||
---
|
||||
|
||||
@@ -41,11 +42,11 @@
|
||||
- ClickHouse(独占读模型,宽表 `student_dashboard_view` / `student_errors` / `mastery_snapshot` / `ai_usage_log`)
|
||||
- Redis(DataScope 缓存 5min + 预警去重位图 + CDC 幂等去重 SETNX,004 §6.3 缓存策略矩阵)
|
||||
- Kafka(消费 Debezium CDC 事件 + 发布 `edu.insight.mastery.updated` 派生数据事件)
|
||||
- iam gRPC(调 `GetEffectiveDataScope` 解析数据范围,已仲裁 P4 补全,见 004 §15.3 #5)
|
||||
- iam gRPC(调 `GetEffectiveDataScope` 解析数据范围,已仲裁 P4 补全,见 [coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3)
|
||||
- **通信方式**:
|
||||
- 入口:HTTP(`/analytics/*`,保留作 Gateway 直连降级)+ **gRPC** `AnalyticsService`(P4 启用主入口,BFF 调用)
|
||||
- 出口:Kafka 消费(CDC 主通道 + 领域事件订阅备通道);Kafka 发布(`edu.insight.mastery.updated`,**未实现**,属派生数据豁免 Outbox,见 004 §12.2)
|
||||
- **端口**:HTTP=3006(见 [data-ana config.py:7](../src/data_ana/config.py) + [api-gateway config.go:55](../../api-gateway/internal/config/config.go));**gRPC=50055**(004 §1.2 服务清单 + §4.2 gRPC 启用阶段矩阵:P4 启用)
|
||||
- 出口:Kafka 消费(CDC 主通道 + 领域事件订阅备通道);Kafka 发布(`edu.insight.mastery.updated`,**未实现**,属派生数据豁免 Outbox,见 [coord-cross-review.md §3.3](../../docs/architecture/coord-cross-review.md) + 004 §12.2)
|
||||
- **端口**:HTTP=3006(见 [data-ana config.py:7](../src/data_ana/config.py) + [api-gateway config.go:55](../../api-gateway/internal/config/config.go));**gRPC=50055**([coord-cross-review.md §4.3](../../docs/architecture/coord-cross-review.md) 全局端口矩阵 + [matrix.md §2](../../docs/architecture/issues/matrix.md):P4 启用)
|
||||
|
||||
## 2. 我的限界上下文
|
||||
|
||||
@@ -79,7 +80,7 @@
|
||||
| events.proto | `HomeworkEvent` | 备通道(未消费) | 未来双消费:作业提交/批改事件 → 更新学情 |
|
||||
| events.proto | `ClassEvent` | 备通道(未消费) | 未来双消费:班级变更事件 → 同步班级维度 |
|
||||
| analytics.proto | `GetClassPerformanceRequest` 等 | 自身暴露契约(待实现 gRPC server) | P4 启用 gRPC=50055 后暴露 `AnalyticsService` |
|
||||
| iam.proto | `GetEffectiveDataScopeRequest`(**待 coord 在 iam.proto 新增**,004 §15.3 #5 已裁决 P4 补全) | 调用契约 | gRPC 调 iam 解析 DataScope,结果 Redis 缓存 5min |
|
||||
| iam.proto | `GetEffectiveDataScopeRequest`(**待 coord 在 iam.proto 新增**,[coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3 已裁决 P4 补全;当前 iam.proto 仅有 4 RPC,无此 RPC,**P4 阻塞项**) | 调用契约 | gRPC 调 iam 解析 DataScope,结果 Redis 缓存 5min |
|
||||
|
||||
### 暴露的 API / 事件
|
||||
|
||||
@@ -94,7 +95,7 @@
|
||||
| GET | `/analytics/student/{student_id}/weakness` | 学生薄弱知识点(mastery < 0.6) |
|
||||
| GET | `/analytics/student/{student_id}/errorbook` | 学生错题本 |
|
||||
|
||||
> **响应信封约束**:以上 HTTP 端点当前实现为 `{success, data, degraded}` 结构,违反 004 §11.5 统一响应信封 ActionState(Python 服务必须改 ActionState,degraded 作为 `details.degraded` 子字段)。阶段 2 设计已对齐(见 02-architecture-design.md §4.3)。
|
||||
> **响应信封约束**:以上 HTTP 端点当前实现为 `{success, data, degraded}` 结构,违反统一响应信封 ActionState([coord-cross-review.md §5.3](../../docs/architecture/coord-cross-review.md) 已裁决 Python 服务必须改 ActionState,degraded 作为 `details.degraded` 子字段)。阶段 2 设计已对齐(见 02-architecture-design.md §4.3)。
|
||||
|
||||
**gRPC 契约**(analytics.proto,P4 启用 server,端口 50055):
|
||||
|
||||
@@ -113,11 +114,13 @@
|
||||
|
||||
| 事件 | Topic(004 §7.2) | 触发时机 | 消费者(004 §7.3) | Outbox 合规性 |
|
||||
| ---------------- | ----------------------------- | -------------- | --------------------------------------- | ----------------------------------------------------------- |
|
||||
| `MasteryUpdated` | `edu.insight.mastery.updated` | 掌握度计算完成 | core-edu(推荐个性化练习)、msg(预警) | **豁免 Outbox**(派生数据,见 004 §12.2 + §15.3 #6 已仲裁) |
|
||||
| `MasteryUpdated` | `edu.insight.mastery.updated` | 掌握度计算完成 | core-edu(推荐个性化练习)、msg(预警) | **豁免 Outbox**(派生数据,见 [coord-cross-review.md §3.3](../../docs/architecture/coord-cross-review.md) + 004 §12.2) |
|
||||
|
||||
> **当前未实现发布**:data-ana 当前只消费不发布。掌握度计算完成后通过 `aiokafka.AIOKafkaProducer` 直接发布(已豁免 Outbox,004 §15.3 #6 仲裁结论),下游 core-edu / msg 消费。失败重试 3 次仍失败落 `mastery_publish_failed` 本地表。
|
||||
> **当前未实现发布**:data-ana 当前只消费不发布。掌握度计算完成后通过 `aiokafka.AIOKafkaProducer` 直接发布(已豁免 Outbox,coord-cross-review.md §3.3 仲裁结论),下游 core-edu / msg 消费。失败重试 3 次仍失败落 `mastery_publish_failed` 本地表。
|
||||
|
||||
- **错误码前缀**:`DATA_ANA_*`(004 §11.4 错误码前缀矩阵已登记,清单见阶段 2 §6.2)
|
||||
> **Topic 命名一致性**:本模块统一使用 `edu.insight.mastery.updated`(与 004 §7.2 + matrix.md §4 对齐)。contract.md 早期版本写 `edu.data_ana.mastery.events`,已在 v2.1 修正。
|
||||
|
||||
- **错误码前缀**:`DATA_ANA_*`([matrix.md §6](../../docs/architecture/issues/matrix.md) 错误码前缀矩阵已登记,清单见阶段 2 §6.2)
|
||||
- **缓存**:Redis(DataScope 缓存 5min 事件驱动失效 / CDC event_id 幂等去重 SETNX TTL 7d / 预警去重位图);学情宽表走 ClickHouse 实时,CDC 同步延迟 < 5s
|
||||
|
||||
## 4. 我的技术栈
|
||||
@@ -145,7 +148,7 @@
|
||||
- **交付物**(pending-features P4):DataAna 学情诊断宽表 5s 内返回 + CDC 链路延迟 < 5s + **双轨读策略落地(实时查主库 + 聚合查宽表)** + CDC 模式回写黄金模板 README + 打 tag `v0.4.0-p4`
|
||||
- **依赖上游**:
|
||||
- P1 地基:api-gateway 路由 + arch.db 扫描器 Python 支持
|
||||
- P2 身份:iam `GetEffectiveDataScope` gRPC(已仲裁 P4 补全,见 004 §15.3 #5)
|
||||
- P2 身份:iam `GetEffectiveDataScope` gRPC(已仲裁 P4 补全,见 [coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3;**当前 iam.proto 未实现,P4 阻塞项**)
|
||||
- P3 核心教学:core-edu 写成绩到 MySQL(Debezium 监听 binlog)+ Outbox 领域事件(备通道)
|
||||
- P4 同期:content 服务(提供知识点 ID 供掌握度计算,content → data-ana 事件流见 004 §4 服务依赖图)
|
||||
- **下游依赖我**:
|
||||
@@ -160,8 +163,8 @@
|
||||
> Python 服务无 NestJS 装饰器体系,权限校验等通过等价方式实现。
|
||||
|
||||
- [ ] 权限装饰器等价物:**当前 HTTP 端点全部裸露,无权限校验**。Gateway 层做 JWT 校验,但 data-ana 本身未校验 `x-user-id` / DataScope。**阶段 2 需设计 FastAPI Depends 权限依赖 + DataScope 过滤注入**
|
||||
- [ ] 错误码前缀统一:**当前无错误码体系**,降级时返回 `degraded: true` 标记但无业务错误码。**阶段 2 需定义 `DATA_ANA_*` 错误码清单**(004 §11.4 已登记前缀)
|
||||
- [ ] **响应信封对齐 ActionState**(004 §11.5 已仲裁 P0 整改):当前 `{success, data, degraded}` 偏离结构,须改为 `{success: true, data: T}` / `{success: false, error: {code, message, details?, traceId?}}`,degraded 作为 `details.degraded` 子字段
|
||||
- [ ] 错误码前缀统一:**当前无错误码体系**,降级时返回 `degraded: true` 标记但无业务错误码。**阶段 2 需定义 `DATA_ANA_*` 错误码清单**([matrix.md §6](../../docs/architecture/issues/matrix.md) 已登记前缀)
|
||||
- [ ] **响应信封对齐 ActionState**([coord-cross-review.md §5.3](../../docs/architecture/coord-cross-review.md) 已裁决 P0 整改):当前 `{success, data, degraded}` 偏离结构,须改为 `{success: true, data: T}` / `{success: false, error: {code, message, details?, traceId?}}`,degraded 作为 `details.degraded` 子字段
|
||||
- [x] logger / metrics / tracer 三支柱(已具备,见 main.py + clickhouse_client.py;生产改 JSONRenderer)
|
||||
- [x] `/healthz` + `/readyz` 健康检查(已具备,readyz 含 ClickHouse ping + CDC 状态;**待补 Redis + iam gRPC 连通性检查**)
|
||||
- [ ] 优雅关闭 SIGTERM:当前 lifespan 仅关闭 CDC task + ClickHouse client,**未注册 SIGTERM 信号处理器**显式 drain。阶段 2 设计顺序:HTTP stop → gRPC graceful stop 30s → CDC commit offset → Kafka producer flush → ClickHouse close → iam channel close
|
||||
@@ -210,17 +213,17 @@
|
||||
|
||||
## coord 交叉审查结论对齐(v2 修订,原 v1 标题"待 coord 交叉审查的跨模块契约对齐项")
|
||||
|
||||
> v1 提请的 5 项跨模块契约对齐项,coord 已在 [coord-cross-review.md](../../../docs/architecture/coord-cross-review.md) 完成仲裁,裁决结论中涉及架构设计意图的部分已沉淀到 004 对应章节(004 §15.3 共性问题 #1-#7)。本节 v2 改为"对齐状态"。
|
||||
> v1 提请的 5 项跨模块契约对齐项,coord 已在 [coord-cross-review.md](../../../docs/architecture/coord-cross-review.md) 完成仲裁。本节 v2.1 改为"对齐状态",并标注落实情况。
|
||||
|
||||
| # | 议题(v1 提请) | coord 裁决结论(004 §15.3) | data-ana 文档对齐状态(v2) |
|
||||
| # | 议题(v1 提请) | coord 裁决结论(coord-cross-review.md) | data-ana 文档对齐状态(v2.1) |
|
||||
| --- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| 1 | **data-ana 是否发布 `edu.insight.mastery.updated` 事件** | §15.3 #6:派生数据事件豁免 Outbox,允许直接 Kafka producer | ✅ v2 已对齐:§3 标注"豁免 Outbox",阶段 2 §5.2 设计直接 producer 链路 |
|
||||
| 2 | **data-ana / ai 是否需要实现 gRPC server** | §4.2 gRPC 启用阶段矩阵:P4 content + data-ana 启用 | ✅ v2 已对齐:§1 标注"gRPC=50055 P4 启用",阶段 2 §1 分层图含 grpc.aio Server |
|
||||
| 3 | **CDC 直连 vs Outbox 领域事件双通道** | §15.3 #6 + ADR-008:维持 CDC 为主通道;events.proto 作为业务语义补充,待 P4 后期评估是否双消费 | ✅ v2 已对齐:§3 双通道说明 + 标注"领域事件为备通道" |
|
||||
| 4 | **data-ana DataScope 过滤实现位置** | §15.3 #5:iam 新增 `GetEffectiveDataScope` gRPC RPC(P4 补全);data-ana 在 ClickHouse 查询 SQL 拼接时注入 WHERE | ✅ v2 已对齐:§3 列出 iam.proto 调用契约;阶段 2 §6.1 设计 `inject_data_scope` Depends |
|
||||
| 5 | **data-ana ClickHouse DDL 管理位置** | §15.3 #7:采纳 `infra/clickhouse/ddl/`(coord 建立),data-ana 提供 DDL 内容 | ✅ v2 已对齐:阶段 2 §3 标注"DDL 文件由 coord 统一管理在 `infra/clickhouse/ddl/`" |
|
||||
| 6 | **新增 `edu.insight.ai.usage` topic**(v1 阶段 2 §8.3 #3 提请) | §15.3 #4:补登,见 004 §7.2 | ✅ v2 已对齐:阶段 2 §5.1 列出消费此 topic |
|
||||
| 7 | **iam GetEffectiveDataScope proto 新增**(v1 阶段 2 §8.3 #2 提请) | §15.3 #5:P4 补全 | ✅ v2 已对齐:§3 列出 iam.proto 调用契约 |
|
||||
| 1 | **data-ana 是否发布 `edu.insight.mastery.updated` 事件** | §3.3:派生数据事件豁免 Outbox,允许直接 Kafka producer | ✅ v2 已对齐:§3 标注"豁免 Outbox",阶段 2 §5.2 设计直接 producer 链路 |
|
||||
| 2 | **data-ana / ai 是否需要实现 gRPC server** | §2.1:P4 content + data-ana 启用 gRPC | ✅ v2 已对齐:§1 标注"gRPC=50055 P4 启用",阶段 2 §1 分层图含 grpc.aio Server |
|
||||
| 3 | **CDC 直连 vs Outbox 领域事件双通道** | §3.3 + ADR-008:维持 CDC 为主通道;events.proto 作为业务语义补充,待 P4 后期评估是否双消费 | ✅ v2 已对齐:§3 双通道说明 + 标注"领域事件为备通道" |
|
||||
| 4 | **data-ana DataScope 过滤实现位置** | §2 #3:iam 新增 `GetEffectiveDataScope` gRPC RPC(P4 补全);data-ana 在 ClickHouse 查询 SQL 拼接时注入 WHERE | ✅ v2 已对齐:§3 列出 iam.proto 调用契约;阶段 2 §6.1 设计 `inject_data_scope` Depends |
|
||||
| 5 | **data-ana ClickHouse DDL 管理位置** | §4:采纳 `infra/clickhouse/ddl/`(coord 建立),data-ana 提供 DDL 内容 | ✅ v2 已对齐:阶段 2 §3 标注"DDL 文件由 coord 统一管理在 `infra/clickhouse/ddl/`" |
|
||||
| 6 | **新增 `edu.insight.ai.usage` topic**(v1 阶段 2 §8.3 #3 提请) | §3.2:补登,见 004 §7.2;**events.proto AIUsageEvent message 待 coord 补充** | ✅ v2 已对齐:阶段 2 §5.1 列出消费此 topic;⚠️ events.proto 缺 AIUsageEvent 定义 |
|
||||
| 7 | **iam GetEffectiveDataScope proto 新增**(v1 阶段 2 §8.3 #2 提请) | §2 #3:P4 补全;**当前 iam.proto 仅 4 RPC,未实现,P4 阻塞项** | ✅ v2 已对齐:§3 列出 iam.proto 调用契约;⚠️ iam.proto 未实现 |
|
||||
|
||||
> v1 §8.3 "未决设计决策"3 项全部已被 coord 仲裁,v2 不再列为"未决"。文档后续修订如发现新冲突,按 ai-allocation.md §9.4 proto 变更流程提请 coord。
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
1. **契约先行**:proto 已定义(analytics.proto / events.proto / iam.proto),实现前不修改 proto,如需修改走 coord 流程(ai-allocation.md §9.4)
|
||||
2. **CQRS 读写分离**:data-ana 是纯读模型服务(无 MySQL 写),ClickHouse 宽表由 CDC 投影构建
|
||||
3. **事件驱动**:data-ana 消费 CDC(主通道)+ 领域事件(备通道,待 P4 后期评估);发布 `edu.insight.mastery.updated` 派生数据事件(豁免 Outbox,004 §12.2 + §15.3 #6 已仲裁)
|
||||
4. **gRPC 优先**:004 §4.1 + §4.2 明确 BFF → 业务服务走 gRPC,**P4 启用 data-ana gRPC server 端口 50055**(HTTP 保留作 Gateway 直连降级)
|
||||
5. **DataScope 过滤**:004 §5.3 DataScope 6 级在查询层注入 WHERE;iam `GetEffectiveDataScope` gRPC(004 §15.3 #5 已仲裁 P4 补全)
|
||||
3. **事件驱动**:data-ana 消费 CDC(主通道)+ 领域事件(备通道,待 P4 后期评估);发布 `edu.insight.mastery.updated` 派生数据事件(豁免 Outbox,[coord-cross-review.md §3.3](../../docs/architecture/coord-cross-review.md) + 004 §12.2 已仲裁)
|
||||
4. **gRPC 优先**:004 §4.1 + [coord-cross-review.md §2.1](../../docs/architecture/coord-cross-review.md) 明确 BFF → 业务服务走 gRPC,**P4 启用 data-ana gRPC server 端口 50055**(HTTP 保留作 Gateway 直连降级)
|
||||
5. **DataScope 过滤**:004 §5.3 DataScope 6 级在查询层注入 WHERE;iam `GetEffectiveDataScope` gRPC([coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3 已仲裁 P4 补全)
|
||||
6. **三支柱可观测**:structlog + prometheus-client + OpenTelemetry(已具备,需补业务指标 + gRPC server interceptor)
|
||||
7. **统一响应信封 ActionState**(004 §11.5 已仲裁 P0 整改):成功 `{success: true, data: T}` / 失败 `{success: false, error: {code, message, details?, traceId?}}` / 降级 `degraded` 作为 `details.degraded` 子字段
|
||||
7. **统一响应信封 ActionState**([coord-cross-review.md §5.3](../../docs/architecture/coord-cross-review.md) 已裁决 P0 整改):成功 `{success: true, data: T}` / 失败 `{success: false, error: {code, message, details?, traceId?}}` / 降级 `degraded` 作为顶层 `details.degraded` 子字段(非 `error.details`)
|
||||
8. **降级模式**:外部依赖(ClickHouse / Kafka / iam gRPC / Redis)不可用时返回骨架数据 + `details.degraded: true`
|
||||
9. **Python 规范**:pydantic-settings 配置 / Pydantic 模型校验 / async 优先 / 类型注解强制 / ruff 零错误
|
||||
10. **长远架构演进**:为 P5(ai 用量消费 / gRPC stream)、P6(CDC 水平扩展 / 容量规划 / 数据治理 / Service Mesh)做好铺垫,见 §14 / §19
|
||||
@@ -295,13 +295,15 @@ ORDER BY (student_id, class_id, attendance_date);
|
||||
| `GetAdminDashboard` | `GetAdminDashboardRequest{user_id, scope, scope_id?}` | `AdminDashboard` | `ANALYTICS_ADMIN_DASHBOARD` |
|
||||
| `GetWarnings` | `GetWarningsRequest{class_id?, severity?, since?}` | `WarningList` | `ANALYTICS_WARNING_READ` |
|
||||
| `GetMasteryDistribution` | `GetMasteryDistributionRequest{class_id, subject_id, knowledge_point_id?}` | `MasteryDistribution` | `ANALYTICS_CLASS_READ` |
|
||||
| `GetStudentMastery` | `GetStudentMasteryRequest{student_id, subject_id?}` | `StudentMastery` | `ANALYTICS_STUDENT_READ` |
|
||||
| `TriggerWarning` | `TriggerWarningRequest{target_id, warning_type, severity}` | `TriggerWarningResponse` | `ANALYTICS_WARNING_READ` |
|
||||
| `SubscribeMasteryUpdate` | `SubscribeMasteryUpdateRequest{student_id?, class_id?}` | `stream MasteryUpdateEvent` | `ANALYTICS_STUDENT_READ` |
|
||||
|
||||
> **proto 扩展提案**(待 coord 审议):上述新增 RPC 的 message 定义需在 `packages/shared-proto/proto/analytics.proto` 补充。`SubscribeMasteryUpdate` 为 server-streaming RPC,为 P5+ AI 个性化推荐预留实时推送通道。
|
||||
> **proto 扩展提案**(待 coord 审议):上述新增 RPC 的 message 定义需在 `packages/shared-proto/proto/analytics.proto` 补充,共 12 RPC(3 现有 + 9 扩展)。`SubscribeMasteryUpdate` 为 server-streaming RPC,为 P5+ AI 个性化推荐预留实时推送通道。
|
||||
|
||||
**权限校验**:gRPC server interceptor 从 metadata 提取 `x-user-id` / `x-user-roles` / `x-data-scope`,调用 `AuthDepends` 等价逻辑。
|
||||
|
||||
### 4.3 ActionState 统一响应信封(004 §11.5 P0 整改)
|
||||
### 4.3 ActionState 统一响应信封(coord-cross-review.md §5.3 P0 整改)
|
||||
|
||||
所有 HTTP/gRPC 响应必须遵循 ActionState 信封:
|
||||
|
||||
@@ -318,23 +320,22 @@ class ActionStateError(BaseModel):
|
||||
trace_id: str | None = None
|
||||
|
||||
class ActionState(BaseModel, Generic[T]):
|
||||
"""统一响应信封(004 §11.5).
|
||||
"""统一响应信封(coord-cross-review.md §5.3 裁决).
|
||||
|
||||
成功:{success: true, data: T}
|
||||
失败:{success: false, error: {code, message, details?, trace_id?}}
|
||||
降级:success=true 但 error.details.degraded=true(保留功能但数据可能不完整)
|
||||
降级:{success: true, data: T, details: {degraded: true}}(保留功能但数据可能不完整)
|
||||
"""
|
||||
success: bool
|
||||
data: T | None = None
|
||||
error: ActionStateError | None = None
|
||||
details: dict[str, Any] | None = None # 降级标记放此字段,不放 error
|
||||
|
||||
@classmethod
|
||||
def ok(cls, data: T, *, degraded: bool = False) -> "ActionState[T]":
|
||||
# 降级不是错误:success=True,degraded 标记放 details 子字段(非 error.details)
|
||||
details = {"degraded": True} if degraded else None
|
||||
# 简化:degraded 作为 data 的元信息附加,复杂场景用 details
|
||||
return cls(success=True, data=data, error=None if not degraded else
|
||||
ActionStateError(code="DATA_ANA_DEGRADED", message="degraded mode",
|
||||
details=details))
|
||||
return cls(success=True, data=data, error=None, details=details)
|
||||
|
||||
@classmethod
|
||||
def fail(cls, code: str, message: str, *, trace_id: str | None = None,
|
||||
@@ -361,7 +362,7 @@ class StudentScore(BaseModel):
|
||||
# 端点返回类型:ActionState[ClassPerformanceData]
|
||||
```
|
||||
|
||||
> **P0 整改要点**:当前 main.py 返回 `{success, data, degraded}` 三字段平铺,违反 004 §11.5。实现阶段需重构为 `ActionState[T]` 泛型,`degraded` 移到 `error.details.degraded`。
|
||||
> **P0 整改要点**:当前 main.py 返回 `{success, data, degraded}` 三字段平铺,违反 coord-cross-review.md §5.3 裁决。实现阶段需重构为 `ActionState[T]` 泛型,`degraded` 移到顶层 `details.degraded`(非 `error.details`,降级不是错误)。
|
||||
|
||||
## 5. 事件设计
|
||||
|
||||
@@ -547,13 +548,13 @@ async def readyz() -> dict:
|
||||
| 被调用 | api-gateway | HTTP | `/analytics/*` | Gateway 代理(降级通道) |
|
||||
| 被调用 | teacher-bff / student-bff / parent-bff | gRPC | `AnalyticsService.*` | BFF 聚合查询(4 端 Dashboard) |
|
||||
| 被调用 | ai(P5+) | gRPC | `AnalyticsService.GetStudentWeakness / GetLearningTrend / SubscribeMasteryUpdate` | AI 个性化出题上下文 + 实时掌握度推送 |
|
||||
| 调用 | iam | gRPC | `IamService.GetEffectiveDataScope`(004 §15.3 #5 已仲裁 P4 补全) | DataScope 解析 |
|
||||
| 调用 | iam | gRPC | `IamService.GetEffectiveDataScope`([coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3 已仲裁 P4 补全;**当前 iam.proto 未实现**) | DataScope 解析 |
|
||||
| 消费 | core-edu(CDC) | Kafka | `edu-cdc.next_edu_cloud.core_edu_grades/exams/homework_submissions/attendance` | 学情 + 考勤数据投递 |
|
||||
| 消费 | core-edu(CDC) | Kafka | `edu-cdc.next_edu_cloud.classes` | 班级维度同步 |
|
||||
| 消费 | iam(CDC) | Kafka | `edu-cdc.next_edu_cloud.iam_users` | 用户 dataScope 同步 |
|
||||
| 消费 | content(CDC) | Kafka | `edu-cdc.next_edu_cloud.content_knowledge_points` | 知识点元数据同步(v2 新增) |
|
||||
| 消费 | ai(P5+) | Kafka | `edu.insight.ai.usage`(004 §15.3 #6 已仲裁,004 §7.2 已登记) | AI 用量落库 |
|
||||
| 发布 | core-edu / msg | Kafka | `edu.insight.mastery.updated`(004 §12.2 + §15.3 #6 已仲裁:派生数据豁免 Outbox) | 掌握度更新通知 |
|
||||
| 消费 | ai(P5+) | Kafka | `edu.insight.ai.usage`([coord-cross-review.md §3.2](../../docs/architecture/coord-cross-review.md) 已仲裁;004 §7.2 已登记) | AI 用量落库 |
|
||||
| 发布 | core-edu / msg | Kafka | `edu.insight.mastery.updated`([coord-cross-review.md §3.3](../../docs/architecture/coord-cross-review.md) + 004 §12.2 已仲裁:派生数据豁免 Outbox) | 掌握度更新通知 |
|
||||
| 发布 | msg / core-edu | Kafka | `edu.insight.warning.triggered`(v2 新增,需 coord 在 004 §7.2 登记) | 预警触发通知 |
|
||||
|
||||
> **004 §4.1 服务间通信矩阵对齐**:content → data-ana 已声明"教学内容变更通知",本次落实为 CDC 订阅 `content_knowledge_points` 表。
|
||||
@@ -562,10 +563,10 @@ async def readyz() -> dict:
|
||||
|
||||
### 8.1 假设
|
||||
|
||||
- **假设 1**:iam 在 P4 阶段提供 `GetEffectiveDataScope(userId) → DataScope` gRPC API(004 §15.3 #5 已仲裁)。若 iam 未及时提供,fallback 为:从 `x-user-roles` 头推导(admin=ALL, teacher=CLASS_TAUGHT, student=SELF),但无法支持细粒度年级/学校范围
|
||||
- **假设 1**:iam 在 P4 阶段提供 `GetEffectiveDataScope(userId) → DataScope` gRPC API([coord-cross-review.md §2](../../docs/architecture/coord-cross-review.md) #3 已仲裁)。**当前 iam.proto 仅 4 RPC 未实现此 RPC,P4 阻塞项**。若 iam 未及时提供,fallback 为:从 `x-user-roles` 头推导(admin=ALL, teacher=CLASS_TAUGHT, student=SELF),但无法支持细粒度年级/学校范围
|
||||
- **假设 2**:core-edu 的 `core_edu_homework_submissions` / `core_edu_attendance` 表存在 binlog。若不存在,需 core-edu 补表或走 Outbox 事件(领域事件备通道)
|
||||
- **假设 3**:ClickHouse `ReplacingMergeTree` 在查询时需 `FINAL` 关键字确保去重生效。**v2 修复要求**:所有查询加 `FINAL` 或使用 `argMax` 聚合(当前实现未加,是 P0 整改项)
|
||||
- **假设 4**:coord 已在 004 §7.2 登记新增 `edu.insight.ai.usage` topic(004 §15.3 #6 已仲裁)
|
||||
- **假设 4**:coord 已在 004 §7.2 登记新增 `edu.insight.ai.usage` topic([coord-cross-review.md §3.2](../../docs/architecture/coord-cross-review.md) 已仲裁);**events.proto AIUsageEvent message 待 coord 补充**
|
||||
|
||||
### 8.2 技术风险
|
||||
|
||||
@@ -582,22 +583,22 @@ async def readyz() -> dict:
|
||||
| 大数据量 Dashboard 聚合超时 | 管理员 Dashboard 全校聚合慢 | 物化视图预聚合 + 异步刷新 + 缓存 5min |
|
||||
| 知识点元数据与成绩关联失败 | mastery_snapshot 缺知识点标题 | content CDC 同步 + 缺失时显示 `knowledge_point_id` |
|
||||
|
||||
### 8.3 coord 交叉审查结论对齐(v2:原"未决设计决策"已全部仲裁)
|
||||
### 8.3 coord 交叉审查结论对齐(v2.1:原"未决设计决策"已全部仲裁)
|
||||
|
||||
| # | 议题 | coord 仲裁结论 | 涉及文档 |
|
||||
| --- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ----------------------- |
|
||||
| 1 | data-ana 发布 `edu.insight.mastery.updated` 用直接 producer(非 Outbox)是否合规 | ✅ 已仲裁:派生数据事件豁免 Outbox(004 §12.2 + §15.3 #6) | 004 §12.2 |
|
||||
| 2 | 新增 `edu.insight.ai.usage` topic + `AIUsageEvent` proto message | ✅ 已仲裁:coord 在 004 §7.2 登记 + events.proto 补 message | 004 §7.2 + events.proto |
|
||||
| 3 | iam 新增 `GetEffectiveDataScope` gRPC RPC | ✅ 已仲裁:P4 补全(004 §15.3 #5) | iam.proto |
|
||||
| 4 | data-ana 实现 gRPC server 决策 | ✅ 已仲裁:P4 启用(004 §4.2 gRPC 启用阶段矩阵) | 004 §4.2 |
|
||||
| 1 | data-ana 发布 `edu.insight.mastery.updated` 用直接 producer(非 Outbox)是否合规 | ✅ 已仲裁:派生数据事件豁免 Outbox([coord-cross-review §3.3](../../docs/architecture/coord-cross-review.md) + 004 §12.2) | 004 §12.2 |
|
||||
| 2 | 新增 `edu.insight.ai.usage` topic + `AIUsageEvent` proto message | ✅ 已仲裁:coord 在 004 §7.2 登记 + events.proto 补 message;**⚠️ events.proto 当前缺 AIUsageEvent,待 coord 落实** | 004 §7.2 + events.proto |
|
||||
| 3 | iam 新增 `GetEffectiveDataScope` gRPC RPC | ✅ 已仲裁:P4 补全([coord-cross-review §2](../../docs/architecture/coord-cross-review.md) #3);**⚠️ iam.proto 当前仅 4 RPC,未实现** | iam.proto |
|
||||
| 4 | data-ana 实现 gRPC server 决策 | ✅ 已仲裁:P4 启用([coord-cross-review §2.1](../../docs/architecture/coord-cross-review.md) gRPC 启用阶段) | 004 §4.1 |
|
||||
| 5 | ClickHouse DDL 管理位置(建议 `infra/clickhouse/ddl/`) | ⏳ 待 coord 建立 `infra/clickhouse/ddl/` 目录 + data-ana 提供内容 | infra/ |
|
||||
| 6 | 端口冲突检查:data-ana HTTP=3006 / gRPC=50055 | ✅ 无冲突(004 §1.2 端口分配) | 004 §1.2 |
|
||||
| 7 | 错误码前缀检查:`DATA_ANA_*` | ✅ 无冲突(004 §11.4 错误码前缀矩阵) | 004 §11.4 |
|
||||
| 8 | 黄金模板对齐:Python 服务无 NestJS 装饰器,权限校验用 FastAPI Depends 等价物是否认可 | ✅ 已仲裁:认可(004 §15.3) | — |
|
||||
| 6 | 端口冲突检查:data-ana HTTP=3006 / gRPC=50055 | ✅ 无冲突([coord-cross-review §4.3](../../docs/architecture/coord-cross-review.md) 全局端口矩阵) | port-allocation |
|
||||
| 7 | 错误码前缀检查:`DATA_ANA_*` | ✅ 无冲突([matrix.md §6](../../docs/architecture/issues/matrix.md) 错误码前缀矩阵) | matrix.md §6 |
|
||||
| 8 | 黄金模板对齐:Python 服务无 NestJS 装饰器,权限校验用 FastAPI Depends 等价物是否认可 | ✅ 已仲裁:认可([coord-cross-review §5.3](../../docs/architecture/coord-cross-review.md)) | — |
|
||||
| 9 | `edu.insight.warning.triggered` topic 新增 | ⏳ 待 coord 在 004 §7.2 登记(v2 新提案) | 004 §7.2 |
|
||||
| 10 | analytics.proto 扩展(4 端 Dashboard / Warning / MasteryDistribution / Stream RPC) | ⏳ 待 coord 审议(v2 新提案,§4.2) | analytics.proto |
|
||||
|
||||
> 所有"未决设计决策"已消除,进入实现阶段无阻塞。剩余 3 项 ⏳ 为 v2 新提案,待 coord 审议但不阻塞 P4 主体实现(可先用现有 RPC + HTTP 端点兜底)。
|
||||
> 所有"未决设计决策"已消除。**P4 阻塞项**:#2 events.proto 缺 AIUsageEvent、#3 iam.proto 缺 GetEffectiveDataScope,均待 coord 落实。剩余 3 项 ⏳ 为 v2 新提案,待 coord 审议但不阻塞 P4 主体实现(可先用现有 RPC + HTTP 端点 + mock 兜底)。
|
||||
|
||||
---
|
||||
|
||||
@@ -783,7 +784,7 @@ ENV PATH="/app/.venv/bin:$PATH"
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
EXPOSE 3006 50055
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD curl -f http://localhost:3006/healthz || exit 1
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:3006/healthz')" || exit 1
|
||||
CMD ["python", "-m", "uvicorn", "src.data_ana.main:app", \
|
||||
"--host", "0.0.0.0", "--port", "3006"]
|
||||
```
|
||||
@@ -945,12 +946,26 @@ CDC → ClickHouse 原始表 → MaterializedView → 宽表(自动聚合)
|
||||
- **AI 驱动的学情诊断**:data-ana 提供数据,ai 服务提供模型,组合成"智能诊断报告"
|
||||
- **多租户**:当前 school_id 隐含在 class_id 中,远期可显式多租户隔离
|
||||
|
||||
## 19. 服务审计表 v2(黄金模板对齐)
|
||||
## 19. 非功能性需求(NFR,v2.1 新增)
|
||||
|
||||
| 维度 | 指标 | 目标值 | 验证方式 |
|
||||
| ---------- | ----------------------------- | --------------- | ------------------------------- |
|
||||
| 性能 | ClickHouse 宽表查询 P99 延迟 | < 5s | P4 退出标准 + Prometheus 监控 |
|
||||
| 性能 | CDC 链路延迟 | < 5s | Debezium ts_ms → CH 写入时间差 |
|
||||
| 性能 | gRPC RPC P99 延迟 | < 500ms | OTel trace |
|
||||
| 可用性 | P4 单实例 | 99% | 降级模式保证 |
|
||||
| 可用性 | P6 多实例 + ClickHouse 集群 | 99.9% | HPA + 副本 |
|
||||
| 安全 | DataScope 越权防护 | 0 越权 | 权限测试 + 渗透测试 |
|
||||
| 安全 | PII 日志脱敏 | 100% student_id hash | structlog processor 单测 |
|
||||
| 可扩展性 | CDC 消费者水平扩展 | N 实例无重复 | ReplacingMergeTree + Redis SETNX |
|
||||
| 数据保留 | ClickHouse TTL | 见 §11.1 | TTL 策略自动执行 |
|
||||
|
||||
## 20. 服务审计表 v2(黄金模板对齐)
|
||||
|
||||
| 维度 | 状态 | 说明 |
|
||||
| ------------- | ---- | ----------------------------------------------------------------------- |
|
||||
| 契约(proto) | ✅ | analytics.proto 已定义 3 RPC,v2 提案扩展 7 RPC |
|
||||
| 路由 | ✅ | HTTP 13 端点 + gRPC 10 RPC |
|
||||
| 契约(proto) | ✅ | analytics.proto 已定义 3 RPC,v2 提案扩展至 12 RPC(含 GetStudentMastery / TriggerWarning) |
|
||||
| 路由 | ✅ | HTTP 14 端点(3 基础 + 11 业务)+ gRPC 12 RPC |
|
||||
| 数据访问 | ✅ | ClickHouse 5 宽表 + Redis 缓存 + iam gRPC |
|
||||
| 鉴权 | ✅ | FastAPI Depends 等价物 + 7 权限点 |
|
||||
| 错误处理 | ✅ | 13 错误码 `DATA_ANA_*` + ActionState 信封 |
|
||||
@@ -960,6 +975,7 @@ CDC → ClickHouse 原始表 → MaterializedView → 宽表(自动聚合)
|
||||
| 健康检查 | ✅ | /healthz + /readyz(v2 补 redis/iam_grpc) |
|
||||
| 配置管理 | ✅ | pydantic-settings 完整配置项(§15) |
|
||||
| 降级模式 | ✅ | 4 降级场景(CH/Kafka/Redis/iam) |
|
||||
| NFR | ✅ | v2.1 新增 §19,性能/可用性/安全/可扩展性目标已定义 |
|
||||
| 响应信封 | ⚠️ | v2 设计对齐 ActionState,实现阶段需重构(P0 整改) |
|
||||
| Redis | ⚠️ | v2 设计就绪,实现阶段需新增 redis-py 依赖 + RedisClient |
|
||||
| gRPC server | ⚠️ | v2 设计就绪,实现阶段需新增 grpc.aio + betterproto + server interceptor |
|
||||
@@ -975,5 +991,6 @@ CDC → ClickHouse 原始表 → MaterializedView → 宽表(自动聚合)
|
||||
**AI Agent**: ai11 (data-ana)
|
||||
**Branch**: 单仓库并行模式(直接提交 main)
|
||||
**Coordinator**: coord-ai
|
||||
**v2 修订依据**: ai-allocation.md §3.2 重新分配 + 004 §1.2/§4.2/§7.2/§11.5/§12.2/§15.3 + ai-allocation §5 设计重点 + pending-features P4 退出标准
|
||||
**v2 审核结论**: 16 项遗漏已全部补强(3 项 P0 + 5 项 P1 + 8 项 P2),文档进入实现阶段无阻塞
|
||||
**v2 修订依据**: ai-allocation.md §3.2 重新分配 + coord-cross-review.md §2/§3.3/§5.3 + 004 §4.1/§7.2/§12.2 + ai-allocation §5 设计重点 + pending-features P4 退出标准
|
||||
**v2.1 审核修订**: 修正 004 章节引用断裂(§4.2/§11.4/§11.5/§15.3 不存在→改引 coord-cross-review);修正 ActionState 实现矛盾(degraded 从 error.details 移至顶层 details);补 RPC 至 12 个(GetStudentMastery/TriggerWarning);修正 HTTP 端点计数 14;Dockerfile HEALTHCHECK 改用 urllib 避免 curl 依赖;新增 §19 NFR 章节
|
||||
**v2 审核结论**: 16 项遗漏已全部补强(3 项 P0 + 5 项 P1 + 8 项 P2),文档进入实现阶段无阻塞(P4 阻塞项:iam.proto GetEffectiveDataScope + events.proto AIUsageEvent 待 coord 落实,可用 fallback/mock 兜底)
|
||||
|
||||
Reference in New Issue
Block a user