Merge worktree branch merge-15-modules-to-main-5ug5xJ
This commit is contained in:
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user