Files
Edu/docs/architecture/issues/contracts/push-gateway_contract.md
SpecialX c179af64a6 docs(docs): coord 完成 15 模块 issue 仲裁与基础设施同步
coord.md 新增 ARB-019/020/021 三章仲裁章节,修正 ARB-001。

- coord.md: 新增 ARB-019/020/021(student/parent/admin-portal 24 项)
- coord.md: 修正 ARB-001(admin P2 预留/schema 文件名/classes 数据源)
- 004 §4: 依赖图加 PBFF→DataAna+Msg
- 004 §7.2: push-gateway→Redis 软失败标注
- 004 §11.4: 错误码前缀矩阵(11 服务+i18n key)
- 004 §11.5: ActionState 信封规范(降级模式方案 B)
- matrix §1: 依赖矩阵加 PBFF 边
- matrix §2: 移除 api-gateway 为 iam gRPC 消费方
- matrix §4: admin-portal→teacher-bff
- matrix §5: 移除 /sse+鉴权头统一
- matrix §6: 错误码表补 i18n key 列
- 15 个 issue.md: 仲裁结论回写
- push-gateway_contract: 移除 /sse+鉴权头改 X-Internal-Token
- packages/contracts: 新建包 ADMIN_* 权限点常量

AI: coord
2026-07-10 16:30:51 +08:00

15 KiB
Raw Permalink Blame History

push-gateway 对接契约

负责人ai02 关联:matrix.mdmsg.protoevents.proto02-architecture-design.md 版本v22026-07-10对齐总裁裁决 ISSUE-053/055/056/058 + 02 文档 + 现码) 变更摘要:① topic 改 edu.notification.requestedISSUE-053② /internal/send → /internal/push对齐代码 + 总裁 §4.2);③ 鉴权 mTLS → X-Internal-Token对齐总裁 §7.2);④ 移除 /sse待 ISSUE-001 仲裁);⑤ 新增 /internal/online 端点


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

1.1 gRPC 接口(如有)

无对外 gRPC。push-gateway 是 WebSocket 推送入口,仅通过 HTTP /internal/* 接收 msg 服务调用。

协议选型决策coord 已采纳 P102 文档 §5.4HTTP /internal/* + Kafka 双通道,不走 gRPC。理由推送结果需同步返回delivered/onlineHTTP 同步响应更直接;广播走 Kafka 解耦。

1.2 HTTP 端点

Method Path 用途 认证 请求体 响应
GET /ws WebSocket 升级端点(实时推送通知) JWT RS256query ?token=Authorization 升级为 WebSocket 长连接
POST /internal/push 内部单推接口msg 服务触发) X-Internal-Token {user_id, event, data, ttl?} {success, delivered, online}
POST /internal/broadcast 内部广播接口msg 服务触发) X-Internal-Token {event, data, filter?} {success, reached}
GET /internal/online/<userID> 查询用户在线状态 X-Internal-Token {online: bool, instances: []}
GET /healthz 健康检查liveness 公开 {status:"ok", service, version, connections}
GET /readyz 就绪检查readiness 公开 {status, degraded?}Redis/Kafka 软失败时 degraded:true
GET /metrics Prometheus 指标端点 公开(内网) Prometheus 文本格式

SSE 端点裁决ARB-015 §17.3ISSUE-001 已裁决):

  • 不支持 GET /sse(仅 WebSocket /ws删除 contract v1 的 /sse 行02 文档 §10 正确)
  • 理由:简化实现 + 统一推送通道 + WebSocket 双向通信能力更强

鉴权头裁决ARB-015 §17.3ISSUE-002 已裁决):

  • 统一采用 X-Internal-Token 头(对齐总裁裁决 §7.2
  • 现码 X-Internal-Key 需 ai02 统一为 X-Internal-Token

X-Internal-Token 校验机制

  • 启动时从 INTERNAL_API_TOKEN 环境变量加载
  • 与 msg 服务共享同一密钥K8s Secret 注入)
  • DevModeDEV_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 不发布任何 Kafka 事件,仅消费事件触发推送。推送结果通过 HTTP /internal/push 同步响应返 msg 服务。

1.5 错误码前缀

PUSH_(见 matrix.md §6 错误码前缀矩阵)

错误码清单(对齐 02 文档 §6.2

错误码 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 应用层消息协议

服务端 → 客户端

{
  "type": "message",
  "event": "notification.created",
  "data": { ... },
  "seq": 12345,
  "timestamp": "2026-07-09T..."
}

客户端 → 服务端

  • WebSocket Ping 控制帧心跳30s 间隔,非文本消息)
  • 重连时:GET /ws?token=&session_id=<id>&last_seq=<n>P6 实现)

心跳规则RFC 6455 控制帧):

  • 客户端每 30s 发送 Ping
  • 服务端自动回 Ponggorilla/websocket 默认)
  • 服务端 SetReadDeadline(60s)60s 无消息则关闭连接

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

2.1 gRPC 调用(同步)

无主动 gRPC 调用上游

JWT 公钥通过 HTTP GET iam/.well-known/jwks.json 拉取(非 gRPCshared-go/auth/jwks 实现5 分钟缓存刷新。

2.2 Kafka 事件订阅(异步)

Topic Event 发布方 mock 策略
edu.notification.requested NotificationRequested msg (ai10) msg 就绪前不订阅,使用本地定时器每 30 秒推送 1 条 mock 通知到所有在线客户端

topic 命名对齐 ISSUE-053 裁决president-final-rulings.md §1.5):禁止抽象名 edu.*.events,统一 edu.<domain>.<aggregate>.<action> 格式。 原 contract v1 写的 edu.msg.notification.events 已废弃。

消费语义

  • Consumer Grouppush-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 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 GET /internal/online/ msg 查在线状态 msg 决定走在线推送还是离线推送SMS/邮件)

§3 就绪信号

3.1 我依赖的上游就绪标志

  • shared-go 包骨架coord 批次 0.14packages/shared-go 含 tracer/logger/jwks/env 4 模块
  • iam JWT RS256 + JWKS 端点ai06 批次 1iam gRPC 50052 + /.well-known/jwks.json 可访问
  • msg gRPC + Kafka topicai10 批次 4msg gRPC 50056 + edu.notification.requested topic 有事件发布
  • Redis 基础设施coord P1Redis 7.x 可访问 已就绪
  • Kafka 基础设施coord P1Kafka 可访问 已就绪
  • ISSUE-001~007 仲裁coordcoord.md 追加 ARB-003+ 仲裁章节

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

  • push-gateway HTTP :8081 启用(GET /healthz 返 200
  • /readyz 返 200含 Redis/Kafka 软失败检查,degraded 字段)
  • WebSocket /ws 端点可升级连接JWT RS256 鉴权后建立长连接)
  • /internal/push + /internal/broadcast 接收 msg 推送并下发到在线客户端
  • /internal/online/ 查在线状态可调用
  • Kafka consumer edu.notification.requested 订阅成功Consumer Group push-gateway lag=0
  • /metrics 暴露 8+ 自定义指标(push_gateway_* 系列)

§4 Mock 策略

4.1 我提供的 mock

在 push-gateway 真实就绪前,为下游(各前端 portal提供以下 mock

  • WebSocket mock:前端开发期使用 mock-socket 库模拟 WS 连接
    • 连接成功后每 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 mockmsg 就绪前push-gateway 内置定时器每 30 秒生成 1 条 mock NotificationRequested 事件,推送到所有在线客户端
  • JWT 验签 mockiam 就绪前使用本地固定 mock RS256 公钥验签 WebSocket 连接的 token或 DevMode dev-token 跳过)
  • Kafka 订阅 mockmsg 就绪前不启动 Kafka consumer使用本地定时器替代
  • JWKS fetcher mockiam 就绪前 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