# push-gateway 下一步工作与上下游依赖

> 模块：push-gateway（WebSocket 实时推送网关，L3 基础设施）
> 负责人：ai09
> 更新日期：2026-07-13
> 关联文档：[02-architecture-design.md](./02-architecture-design.md) · [push-gateway_workline.md](../../../docs/architecture/issues/worklines/push-gateway_workline.md)

---

## 1. 模块当前状态

push-gateway 的 P1–P6 全部批次已完成并经本地 Docker 验证通过（非 mock 数据）。

| 能力 | 状态 | 验证方式 |
| --- | --- | --- |
| WebSocket `/ws` 端点（JWT RS256 + DevMode dev-token） | ✅ 完成 | Docker 容器本地测试 |
| `/internal/push` HTTP API（X-Internal-Token 鉴权） | ✅ 完成 | curl + WS 客户端联调 |
| `/internal/broadcast` 广播 API | ✅ 完成 | 代码实现 + 单元测试 |
| `/internal/online/:userID` 在线查询 | ✅ 完成 | curl 验证 |
| Kafka 消费 `edu.notification.requested` | ✅ 完成 | Kafka producer → WS 客户端联调 |
| Redis Pub/Sub 跨实例消息扇出 | ✅ 完成 | 容器连接 edu-redis 验证 |
| Redis 在线状态 SET + 启动重建（ISSUE-058） | ✅ 完成 | /readyz 返回 redis ok |
| P6 Reconnect 协议（session_id + last_seq + ring buffer 100 条/用户） | ✅ 完成 | 代码实现 + 单元测试 |
| 幂等性（event_id Redis SETNX 24h 去重） | ✅ 完成 | Kafka 重复消息测试 |
| /healthz + /readyz（软失败探活） | ✅ 完成 | curl 验证 |
| /metrics Prometheus 指标 | ✅ 完成 | curl 验证 |
| Docker 多阶段构建（golang:1.25-alpine → alpine:3.20） | ✅ 完成 | docker build 成功 |
| 事件透传（event_type 字段无需硬编码） | ✅ 完成 | 3 类考试事件 Kafka→WS 验证 |

### 1.1 本地 Docker 验证结果（2026-07-13）

测试环境：本地 Docker（edu-redis + edu-kafka + edu-push-gateway-test 容器，均接入 `edu-full_default` 网络）

```
镜像：edu/push-gateway:test（63.2MB）
容器：edu-push-gateway-test（DEV_MODE=true）

测试 1：健康检查
  GET /healthz  → {"service":"push-gateway","status":"ok"} ✅
  GET /readyz   → {"status":"ok","degraded":false,"dependencies":{"kafka":{"ok":true},"redis":{"ok":true}}} ✅

测试 2：HTTP /internal/push → WebSocket 投递
  WS 客户端连接 ws://localhost:8081/ws?token=dev-token → 连接成功 ✅
  POST /internal/push × 3（ExamExtended / ExamForceSubmitted / ExamQuestionReordered）
    → 三个事件均 delivered=true，WS 客户端全部收到 ✅

测试 3：Kafka → WebSocket 投递
  Kafka producer → topic edu.notification.requested × 3 条消息
    → push-gateway 消费 → WS 客户端全部收到 ✅
  指标：push_gateway_kafka_consumed_total = 3 ✅

测试 4：在线状态查询
  GET /internal/online/dev-user（无连接） → {"online":false} ✅
```

---

## 2. 上游依赖（push-gateway 依赖谁）

push-gateway 作为 L3 基础设施网关，运行时依赖以下服务/组件：

### 2.1 iam（ai01）— JWT RS256 公钥

| 项 | 内容 |
| --- | --- |
| 依赖内容 | iam `/.well-known/jwks.json` 端点提供 RS256 公钥 |
| 端点 | `GET http://iam:3002/v1/iam/.well-known/jwks.json` |
| 用途 | WebSocket `/ws` 连接时校验客户端 JWT 签名（RS256） |
| 当前状态 | DevMode 下使用 `dev-token` 旁路，生产环境需要 iam 就绪 |
| 缓存策略 | shared-go/jwks Fetcher 每 5 分钟刷新 JWKS 缓存 |
| 环境变量 | `JWKS_URL=http://iam:3002/v1/iam/.well-known/jwks.json` |

### 2.2 Redis（基础设施）— 在线状态 + 跨实例消息扇出

| 项 | 内容 |
| --- | --- |
| 依赖内容 | Redis SET（在线用户→实例映射）+ Pub/Sub（跨实例消息扇出） |
| 端点 | `redis://edu-redis:6379` |
| 用途 | 1) 多实例在线状态同步 2) 跨实例 WebSocket 消息投递 3) event_id 幂等去重（SETNX 24h TTL） |
| 当前状态 | ✅ 本地 Docker edu-redis 已就绪并验证通过 |
| 环境变量 | `REDIS_URL=redis://edu-redis:6379` |
| 降级策略 | Redis 不可用时 /readyz 标记 degraded 但不返回 503（软失败，ARB-015 §17.4） |

### 2.3 Kafka（基础设施）— 通知事件消费

| 项 | 内容 |
| --- | --- |
| 依赖内容 | Kafka topic `edu.notification.requested` 消费 |
| 端点 | `kafka:29092`（容器内 INSIDE listener） |
| 用途 | 消费 msg 服务发出的 NotificationRequested 事件，投递到在线用户的 WebSocket |
| 当前状态 | ✅ 本地 Docker edu-kafka 已就绪并验证通过 |
| 环境变量 | `KAFKA_BROKERS=kafka:29092`、`KAFKA_NOTIFICATION_TOPIC=edu.notification.requested`、`KAFKA_CONSUMER_GROUP=push-gateway` |
| 消费策略 | Consumer Group `push-gateway`，StartOffset=LastOffset（首次启动跳过历史积压） |
| 幂等性 | event_id Redis SETNX 去重，24h TTL |
| DLQ | 失败 3 次后投递到 `edu.notification.requested.dlq` |
| 降级策略 | Kafka 不可用时 /readyz 标记 degraded，HTTP /internal/push 仍可用 |

### 2.4 OTel Collector（基础设施）— 可观测性

| 项 | 内容 |
| --- | --- |
| 依赖内容 | OpenTelemetry trace 上报 |
| 端点 | `http://otel-collector:4318`（本地使用 edu-jaeger:4318） |
| 当前状态 | 可选依赖，初始化失败时 tracing 自动禁用（不影响业务） |
| 环境变量 | `OTEL_EXPORTER_OTLP_ENDPOINT` |

---

## 3. 下游依赖（谁依赖 push-gateway）

以下 4 个前端 portal 模块和 1 个后端服务依赖 push-gateway：

### 3.1 teacher-portal（ai13）— WebSocket 通知通道

| 项 | 内容 |
| --- | --- |
| 依赖内容 | WebSocket `/ws` 端点，用于 `/notifications` 页面实时推送 |
| 端点 | `ws://push-gateway:8081/ws?token=<JWT>` |
| 事件类型 | 通知类事件（由 msg 服务通过 Kafka 发出，push-gateway 透传） |
| 当前状态 | ✅ push-gateway 已就绪，等待 teacher-portal 切换 MSW mock → 真实 WS |
| portal 需求来源 | `apps/teacher-portal/nextstep.md` §2.3 |

### 3.2 student-portal（ai14）— 考试实时事件

| 项 | 内容 |
| --- | --- |
| 依赖内容 | WebSocket `/ws` 端点，接收考试实时事件 |
| 端点 | `ws://push-gateway:8081/ws?token=<JWT>` |
| 需要的事件类型 | `ExamExtended`（教师延长考试）、`ExamForceSubmitted`（教师强制收卷）、`ExamQuestionReordered`（教师调整题目顺序） |
| 当前状态 | ✅ 已验证：3 类考试事件经 Kafka → push-gateway → WebSocket 全链路投递成功 |
| 事件来源 | msg 服务（ai10）通过 Outbox → Kafka `edu.notification.requested` 发出 |
| 透传机制 | push-gateway 从 Kafka 消息的 `event_type` 字段读取事件类型，作为 WebSocket 消息的 `event` 字段透传，**无需在 push-gateway 中硬编码事件类型** |
| portal 需求来源 | `apps/student-portal/docs/nextstep.md` §3 |

### 3.3 parent-portal（ai15）— WebSocket 通知

| 项 | 内容 |
| --- | --- |
| 依赖内容 | WebSocket `/ws` 端点 |
| 当前状态 | ✅ push-gateway /ws 已标记就绪（parent-portal nextstep.md §6 确认） |
| portal 需求来源 | `apps/parent-portal/docs/nextstep.md` §6 |

### 3.4 admin-portal（ai16）— WebSocket 实时通知联调

| 项 | 内容 |
| --- | --- |
| 依赖内容 | WebSocket `/ws` 端点，管理端实时通知联调 |
| 当前状态 | ✅ push-gateway 已就绪，等待 admin-portal 联调 |
| portal 需求来源 | `apps/admin-portal/docs/nextstep.md` §3.5 |

### 3.5 msg 服务（ai10）— /internal/push HTTP API

| 项 | 内容 |
| --- | --- |
| 依赖内容 | POST `/internal/push`（定向推送）、POST `/internal/broadcast`（广播）、GET `/internal/online/:userID`（在线查询） |
| 端点 | `http://push-gateway:8081/internal/*` |
| 鉴权 | `X-Internal-Token` 头（生产环境），DevMode 跳过 |
| 当前状态 | ✅ 全部 API 已实现并验证 |
| 环境变量（msg 侧） | `PUSH_GATEWAY_URL=http://push-gateway:8081` |

---

## 4. 事件投递架构（portal 关注）

```
┌──────────────┐     gRPC/HTTP      ┌──────────┐   Outbox→Kafka   ┌─────────────┐
│ core-edu     │ ──────────────────→ │   msg    │ ──────────────→ │   Kafka     │
│ (考试业务)   │  ExamExtended 等    │ (ai10)   │  edu.notification│ (edu-kafka) │
└──────────────┘                     └──────────┘ .requested      └──────┬──────┘
                                                                        │ consume
                                                                        ▼
┌──────────────┐  WS /ws?token=JWT   ┌──────────────────┐   Redis    ┌─────────────┐
│ 4× portal    │ ←────────────────── │  push-gateway    │ ←────────→ │  edu-redis  │
│ (前端)       │  事件透传(event字段) │  (ai09, 本模块)  │  Pub/Sub   │  (在线状态) │
└──────────────┘                     └──────────────────┘            └─────────────┘
```

**关键设计**：push-gateway 不硬编码事件类型。Kafka 消息中的 `event_type` 字段被原样作为 WebSocket 消息的 `event` 字段透传给前端。前端根据 `event` 字段路由处理。这意味着：
- 新增事件类型无需修改 push-gateway
- msg 服务只需在 Kafka 消息中设置正确的 `event_type`
- 前端在 WebSocket 消息回调中按 `event` 字段分发

---

## 5. 环境变量清单（Docker 部署）

| 变量 | 必填 | 示例值 | 说明 |
| --- | --- | --- | --- |
| `PUSH_GATEWAY_PORT` | 是 | `8081` | HTTP 监听端口 |
| `JWT_SECRET` | 生产必填 | — | DevMode 自动使用 dev 默认值 |
| `DEV_MODE` | 否 | `false` | true 时跳过 JWT/InternalToken 校验 |
| `REDIS_URL` | 是 | `redis://edu-redis:6379` | Redis 连接地址 |
| `KAFKA_BROKERS` | 是 | `kafka:29092` | Kafka broker 地址（逗号分隔） |
| `KAFKA_NOTIFICATION_TOPIC` | 是 | `edu.notification.requested` | 消费的 Kafka topic |
| `KAFKA_CONSUMER_GROUP` | 是 | `push-gateway` | Consumer Group ID |
| `INTERNAL_API_TOKEN` | 生产必填 | — | /internal/* API 鉴权 token |
| `JWKS_URL` | 是 | `http://iam:3002/v1/iam/.well-known/jwks.json` | iam JWKS 端点 |
| `WS_ALLOWED_ORIGINS` | 是 | `http://localhost:3000,...` | WebSocket 允许的 Origin 白名单 |
| `MAX_CONNS_PER_USER` | 否 | `5` | 每用户最大连接数 |
| `HEARTBEAT_INTERVAL_SECONDS` | 否 | `30` | 心跳间隔 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | 否 | `http://otel-collector:4318` | OTLP 上报端点 |

---

## 6. 剩余工作

push-gateway 模块自身功能已全部完成，无剩余开发任务。以下为联调阶段事项：

| 任务 | 负责方 | 依赖 | 状态 |
| --- | --- | --- | --- |
| teacher-portal 切换 MSW mock → 真实 WS | teacher-portal（ai13） | push-gateway 就绪 ✅ | 待 portal 执行 |
| student-portal 接入考试事件 WS | student-portal（ai14） | push-gateway 就绪 ✅ + msg 发出考试事件 | 待 msg + portal 联调 |
| parent-portal 接入 WS | parent-portal（ai15） | push-gateway 就绪 ✅ | 待 portal 执行 |
| admin-portal WS 实时通知联调 | admin-portal（ai16） | push-gateway 就绪 ✅ | 待 portal 执行 |
| msg 服务接入 /internal/push | msg（ai10） | push-gateway 就绪 ✅ | 待 msg 执行 |
| iam JWKS 端点就绪 | iam（ai01） | — | 生产环境必需，DevMode 可旁路 |
| Redis 生产部署 | SRE | — | P6.1 限流迁移依赖 |
