push-gateway 模块上下游依赖与工作清单(Next Steps v2)
版本:v2
日期:2026-07-14
负责人:ai09
关联:
1. 概述
push-gateway 是 Edu 平台的实时 WebSocket 推送基础设施(L3 网关层,端口 8081),负责维护用户在线连接、接收 msg 服务通过 HTTP /internal/* 和 Kafka 推送的消息、并通过 WebSocket 实时投递给在线客户端。
v2 核心目标:对齐 ARB-013 Kafka topic 命名规范和 msg 服务的 API 契约,消除 v1 中存在的 4 个不一致点。
2. v2 完成的工作(API 契约对齐)
2.1 Kafka topic 对齐(ARB-013)
| 项 |
v1 |
v2 |
状态 |
| 消费 topic |
edu.notification.requested |
edu.notify.notification.sent |
✅ |
| 命名规范 |
旧命名(违反 ARB-013) |
edu.<domain>.<aggregate>.<action> |
✅ |
| DLQ topic |
edu.notification.requested.dlq |
edu.notify.notification.sent.dlq(自动派生) |
✅ |
修改文件:
internal/config/config.go:KAFKA_NOTIFICATION_TOPIC 默认值改为 edu.notify.notification.sent
internal/kafkaconsumer/consumer.go:包注释更新,metric label 使用 event.EventType(而非硬编码 "notification.requested")
internal/config/config_test.go:断言更新为新 topic 名
2.2 鉴权头对齐
| 项 |
v1 |
v2 |
状态 |
| 规范头名 |
X-Internal-Token |
X-Internal-Key(ARB-013 对齐 msg) |
✅ |
| 向后兼容 |
— |
同时接受 X-Internal-Token(legacy alias) |
✅ |
| 环境变量 |
INTERNAL_API_TOKEN |
PUSH_INTERNAL_TOKEN(canonical)+ INTERNAL_API_TOKEN(fallback) |
✅ |
修改文件:
internal/ws/handler.go:
internalTokenHeader 常量改为 X-Internal-Key
- 新增
legacyInternalTokenHeader = "X-Internal-Token" 兼容别名
checkInternalToken() 依次尝试两个头
internal/config/config.go:优先读取 PUSH_INTERNAL_TOKEN,为空时回退到 INTERNAL_API_TOKEN
2.3 请求体字段命名对齐
| 项 |
v1 |
v2 |
状态 |
| 用户 ID 字段 |
user_id(snake_case) |
userId(camelCase,msg canonical)+ user_id(legacy) |
✅ |
| 其他字段 |
event / data / ttl |
保持不变 |
✅ |
修改文件:
internal/ws/handler.go PushHandler():
- 结构体同时定义
UserID json:"userId" 和 UserIDLeg json:"user_id"
- 绑定后若
UserID 为空则回退到 UserIDLeg
2.4 环境变量对齐
| v1 环境变量 |
v2 规范环境变量 |
兼容性 |
INTERNAL_API_TOKEN |
PUSH_INTERNAL_TOKEN |
v1 变量作为 fallback 别名保留 |
KAFKA_NOTIFICATION_TOPIC(默认 edu.notification.requested) |
KAFKA_NOTIFICATION_TOPIC(默认 edu.notify.notification.sent) |
环境变量名不变,默认值更新 |
修改文件:
infra/docker-compose.deploy.yml:push-gateway 段使用 PUSH_INTERNAL_TOKEN(支持 ${PUSH_INTERNAL_TOKEN:-${INTERNAL_API_TOKEN:-edu-internal-token}} 回退链)
infra/deploy.env.example:PUSH_INTERNAL_TOKEN 为规范变量,INTERNAL_API_TOKEN 标注为兼容别名
2.5 部署配置更新
| 文件 |
修改内容 |
infra/docker-compose.deploy.yml |
KAFKA_NOTIFICATION_TOPIC: edu.notify.notification.sent;PUSH_INTERNAL_TOKEN 带回退链 |
infra/deploy.env.example |
新增 PUSH_INTERNAL_TOKEN,保留 INTERNAL_API_TOKEN 作为兼容别名(留空) |
3. 本地 Docker 验证结果(2026-07-14 v2)
测试环境:本地 Docker Desktop,edu-full_default 网络,接入真实 edu-kafka / edu-redis / edu-iam-test。
| 验证项 |
结果 |
说明 |
go vet ./... |
✅ |
零警告 |
go build ./... |
✅ |
零错误 |
go test ./... |
✅ |
config/hub/ws 全部通过(含新增 TestLoadProdModeLegacyToken) |
| Docker 镜像构建 |
✅ |
edu/push-gateway:test 构建成功 |
/healthz |
✅ |
{"service":"push-gateway","status":"ok"} |
/readyz |
✅ |
degraded:false,kafka+redis 均 ok |
| 容器日志确认 topic |
✅ |
kafka_topic=edu.notify.notification.sent |
| v2 契约:X-Internal-Key + userId |
✅ |
POST /internal/push 返回 {"success":true} |
| v1 兼容:X-Internal-Token + user_id |
✅ |
旧调用方式仍正常工作 |
| Kafka 全链路:新 topic → WS |
✅ |
发布到 edu.notify.notification.sent → WebSocket 客户端收到消息 |
| 考试事件透传(3 类) |
✅ |
ExamExtended / ExamForceSubmitted / ExamQuestionReordered 全部经 Kafka → WS 投递成功 |
| Prometheus metrics |
✅ |
push_gateway_kafka_consumed_total{topic="edu.notify.notification.sent"} 计数正确;push_gateway_messages_pushed_total{event="ExamExtended",result="delivered"} 等标签正确 |
3.1 测试脚本
| 脚本 |
用途 |
services/push-gateway/scripts/test-kafka-ws.mjs |
验证单条 Kafka 消息经 edu.notify.notification.sent topic 到达 WebSocket 客户端 |
services/push-gateway/scripts/test-exam-events.mjs |
验证 3 类考试事件(ExamExtended/ExamForceSubmitted/ExamQuestionReordered)的 event_type 透传 |
运行方式(需在仓库根目录):
4. 上游依赖(调用 push-gateway 的模块)
4.1 4 个前端 Portal(teacher/student/parent/admin)— P0
| Portal |
WebSocket 接入状态 |
v2 新增工作 |
状态 |
| teacher-portal |
✅ 已接入(useNotificationsWebSocket hook) |
无 |
✅ |
| student-portal |
✅ 已接入(考试事件监听) |
无 |
✅ |
| parent-portal |
✅ 已接入 |
无 |
✅ |
| admin-portal |
✅ 已接入 |
无 |
✅ |
说明:4 个 portal 在 v1 阶段已完成 WebSocket 接入,使用 ?token=<jwt> query param 鉴权。v2 的 API 契约变更(Kafka topic / 鉴权头 / 请求体字段)对 portal 透明,无需 portal 侧改动。
4.2 3 个 BFF(teacher-bff / student-bff / parent-bff)— P1
| BFF |
依赖 push-gateway 的方式 |
v2 新增工作 |
状态 |
| teacher-bff |
无直接依赖(BFF 不调用 push-gateway) |
无 |
✅ |
| student-bff |
无直接依赖(BFF 不调用 push-gateway) |
无 |
✅ |
| parent-bff |
无直接依赖(BFF 不调用 push-gateway) |
无 |
✅ |
说明:BFF 层不直接调用 push-gateway。推送由 msg 服务通过 HTTP /internal/push 或 Kafka 发起,push-gateway 负责投递到 portal 的 WebSocket。
5. 下游依赖(push-gateway 调用的模块)
5.1 iam 服务(ai06 负责)— P0
| 依赖项 |
用途 |
状态 |
GET /v1/iam/.well-known/jwks.json |
RS256 JWKS 公钥(WebSocket JWT 验签) |
✅ 已就绪 |
环境变量:JWKS_URL=http://iam:3002/v1/iam/.well-known/jwks.json
说明:shared-go/jwks Fetcher 每 5 分钟刷新 JWKS 缓存。DevMode 接受 dev-token 旁路 JWT 验签。
5.2 msg 服务(ai10 负责)— P0
| 依赖项 |
用途 |
状态 |
HTTP POST /internal/push |
定向推送(msg → push-gateway) |
✅ 已对齐 |
Kafka topic edu.notify.notification.sent |
事件驱动推送(msg Outbox → Kafka → push-gateway) |
✅ 已对齐 |
鉴权头 X-Internal-Key: PUSH_INTERNAL_TOKEN |
/internal/* API 鉴权 |
✅ 已对齐 |
请求体 { userId, event, data } |
camelCase 字段命名 |
✅ 已对齐 |
环境变量:
- push-gateway 侧:
PUSH_INTERNAL_TOKEN(canonical)/ INTERNAL_API_TOKEN(fallback)
- msg 侧:
PUSH_INTERNAL_TOKEN(已对齐)
v2 对齐结果:4 个不一致点(Kafka topic / 鉴权头 / 请求体字段 / 环境变量)全部修复,push-gateway 同时保持对 v1 调用方式的向后兼容。
5.3 Redis(基础设施)— P0
| 依赖项 |
用途 |
状态 |
redis://edu-redis:6379 |
在线状态 SET + 跨实例 Pub/Sub 扇出 + 事件去重 SETNX |
✅ 已就绪 |
环境变量:REDIS_URL=redis://edu-redis:6379/0
5.4 Kafka(基础设施)— P0
| 依赖项 |
用途 |
状态 |
edu-kafka:29092 |
消费 edu.notify.notification.sent topic |
✅ 已就绪 |
环境变量:
KAFKA_BROKERS=edu-kafka:29092
KAFKA_NOTIFICATION_TOPIC=edu.notify.notification.sent(ARB-013)
KAFKA_CONSUMER_GROUP=push-gateway
5.5 OTel Collector(基础设施)— P1
| 依赖项 |
用途 |
状态 |
http://otel-collector:4318 |
OTLP trace 上报 |
⏳ 待部署 |
环境变量:OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
说明:未配置时 push-gateway 跳过 trace 上报,不影响功能。
6. 待协调事项(需上下游配合)
6.1 core-edu 考试事件发布(下游阻塞 student-portal)
| 项 |
详情 |
| 阻塞方 |
core-edu(ai07) |
| 影响方 |
student-portal / student-bff |
| 问题 |
student-portal 期望接收 ExamExtended / ExamForceSubmitted / ExamQuestionReordered 三类考试事件,但这些事件在 core-edu 源码中未找到发布逻辑 |
| 期望流程 |
core-edu Outbox publisher → Kafka edu.notify.notification.sent → push-gateway → student-portal WebSocket |
| push-gateway 侧状态 |
✅ 已验证可消费这三类事件并透传到 WebSocket(见 §3 测试结果) |
| 协调动作 |
请 core-edu 在考试相关业务逻辑中发布这三类事件到 edu.notify.notification.sent topic |
6.2 msg 服务 Outbox 发布 topic 对齐(待确认)
| 项 |
详情 |
| 阻塞方 |
msg(ai10) |
| 问题 |
需确认 msg 的 Outbox publisher 已将发布 topic 从 edu.notification.requested 改为 edu.notify.notification.sent(ARB-013) |
| push-gateway 侧状态 |
✅ 已消费 edu.notify.notification.sent |
| 协调动作 |
请 msg 确认 Outbox TOPIC_MAP 中 NotificationSent 事件映射到 edu.notify.notification.sent;如仍发往旧 topic,需更新 |
6.3 生产环境环境变量迁移(部署时)
| 项 |
详情 |
| 阻塞方 |
SRE AI / 人类决策者 |
| 问题 |
生产环境需将 INTERNAL_API_TOKEN 迁移为 PUSH_INTERNAL_TOKEN(或在 deploy.env 中同时设置两者) |
| push-gateway 侧状态 |
✅ 支持双环境变量(PUSH_INTERNAL_TOKEN 优先,INTERNAL_API_TOKEN 回退) |
| 协调动作 |
部署时在 infra/security/secrets.example.env 或 K8s Secret 中设置 PUSH_INTERNAL_TOKEN;旧变量可保留作为兼容 |
7. v2 文件变更清单
7.1 源码修改
| 文件 |
修改内容 |
services/push-gateway/internal/config/config.go |
Kafka topic 默认值 → edu.notify.notification.sent;支持 PUSH_INTERNAL_TOKEN 环境变量(带 INTERNAL_API_TOKEN 回退) |
services/push-gateway/internal/ws/handler.go |
鉴权头 X-Internal-Key(canonical)+ X-Internal-Token(legacy);请求体 userId(canonical)+ user_id(legacy) |
services/push-gateway/internal/kafkaconsumer/consumer.go |
包注释更新;metric label 使用 event.EventType 而非硬编码 |
services/push-gateway/internal/config/config_test.go |
断言新 topic 名;新增 TestLoadProdModeLegacyToken 验证 fallback |
services/push-gateway/main.go |
启动流程注释更新(topic 名 + 鉴权头名) |
7.2 部署配置修改
| 文件 |
修改内容 |
infra/docker-compose.deploy.yml |
push-gateway 段:KAFKA_NOTIFICATION_TOPIC: edu.notify.notification.sent;PUSH_INTERNAL_TOKEN 带回退链 |
infra/deploy.env.example |
PUSH_INTERNAL_TOKEN 为规范变量;INTERNAL_API_TOKEN 标注为兼容别名 |
7.3 测试脚本新增
| 文件 |
用途 |
services/push-gateway/scripts/test-kafka-ws.mjs |
Kafka → WebSocket 全链路验证(单条消息) |
services/push-gateway/scripts/test-exam-events.mjs |
3 类考试事件透传验证 |
7.4 根 package.json
| 文件 |
修改内容 |
package.json |
devDependencies 新增 kafkajs + ws(仅用于测试脚本) |
8. 环境变量清单(v2)
| 环境变量 |
必填 |
默认值 |
用途 |
PUSH_GATEWAY_PORT |
否 |
8081 |
HTTP 端口 |
JWT_SECRET |
生产必填 |
— |
JWT HS256 签名密钥(DevMode 有默认值) |
DEV_MODE |
否 |
false |
开发模式(旁路 JWT + InternalToken 校验) |
REDIS_URL |
是 |
redis://localhost:6379/0 |
Redis 连接地址 |
KAFKA_BROKERS |
是 |
localhost:9092 |
Kafka broker 列表(逗号分隔) |
KAFKA_NOTIFICATION_TOPIC |
否 |
edu.notify.notification.sent |
消费的 Kafka topic(ARB-013) |
KAFKA_CONSUMER_GROUP |
否 |
push-gateway |
Kafka 消费者组 |
PUSH_INTERNAL_TOKEN |
生产必填 |
— |
/internal/* API 鉴权 token(ARB-013 canonical) |
INTERNAL_API_TOKEN |
兼容别名 |
— |
/internal/* API 鉴权 token(fallback,v1 兼容) |
JWKS_URL |
生产必填 |
— |
iam JWKS 端点(RS256 公钥) |
WS_ALLOWED_ORIGINS |
生产必填 |
— |
WebSocket 允许的 Origin 白名单(逗号分隔) |
MAX_CONNS_PER_USER |
否 |
5 |
单用户最大连接数 |
HEARTBEAT_INTERVAL_SECONDS |
否 |
30 |
心跳间隔(秒) |
OTEL_EXPORTER_OTLP_ENDPOINT |
否 |
localhost:4318 |
OTLP collector 端点 |
INSTANCE_ID |
否 |
hostname |
实例标识(Redis SET 成员) |
9. API 契约(v2)
9.1 WebSocket 端点
- 鉴权:JWT RS256(via iam JWKS)或 DevMode
dev-token
- 消息格式:
{ "type": "message", "event": "<event_type>", "data": {...}, "timestamp": "<RFC3339>" }
- 心跳:RFC 6455 Ping/Pong(30s 间隔,60s 空闲超时)
9.2 内部 HTTP API
| 方法 |
路径 |
鉴权头 |
请求体 |
响应 |
| POST |
/internal/push |
X-Internal-Key: <token> |
{ userId, event, data, ttl? } |
{ success, delivered?, online?, reached?, error? } |
| POST |
/internal/broadcast |
X-Internal-Key: <token> |
{ event, data, filter? } |
{ success, reached? } |
| GET |
/internal/online/<userID> |
X-Internal-Key: <token> |
— |
{ online: bool, instances: [] } |
向后兼容:
- 鉴权头同时接受
X-Internal-Token(v1 legacy)
/internal/push 请求体同时接受 user_id(snake_case,v1 legacy)
9.3 Kafka 消费
| Topic |
消息格式 |
来源 |
edu.notify.notification.sent |
NotificationRequested JSON |
msg Outbox publisher |
edu.notify.notification.sent.dlq |
原始消息(失败 3 次后投递) |
push-gateway 自动派生 |
消息字段:event_id / user_id / event_type / channel / title / content / data / broadcast / occurred_at
event_type 透传:push-gateway 从 Kafka 消息的 event_type 字段读取事件类型,作为 WebSocket 消息的 event 字段透传,不硬编码事件类型。
10. 后续工作(v3 展望)
| 工作项 |
优先级 |
说明 |
| P6 Reconnect 协议 |
P1 |
session_id + last_seq + ring buffer(100 条/用户)支持断线重连后补发 |
| P6 离线消息推送 |
P2 |
与 msg 服务集成,离线用户消息落库后通过 SMS/邮件/应用内通知补发 |
| Prometheus 告警规则 |
P2 |
配置 push_gateway_kafka_consumed_total 停止增长告警 |
| Grafana 面板 |
P2 |
WebSocket 连接数、Kafka 消费延迟、推送成功率面板 |
| 多副本部署验证 |
P2 |
验证 Redis Pub/Sub 跨实例扇出在多副本场景下的正确性 |