Merge worktree branch merge-15-modules-to-main-5ug5xJ

This commit is contained in:
SpecialX
2026-07-10 15:28:20 +08:00
parent 60d7173545
commit df62ffc176
51 changed files with 11559 additions and 1908 deletions

View File

@@ -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)
> 版本v22026-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/onlineHTTP 同步响应更直接;广播走 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 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 文本格式 |
**待 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
- 服务端自动回 Ponggorilla/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 | NotificationEventaction: 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 批次 1iam gRPC 50052 + `/.well-known/jwks.json` 可访问
- [ ] **msg gRPC + Kafka topic**ai10 批次 4msg gRPC 50056 + `edu.notification.requested` topic 有事件发布
- [x] **Redis 基础设施**coord P1Redis 7.x 可访问 ✅ 已就绪
- [x] **Kafka 基础设施**coord P1Kafka 可访问 ✅ 已就绪
- [ ] **ISSUE-001~007 仲裁**coord[coord.md](../coord.md) 追加 ARB-003+ 仲裁章节
### 3.2 我的就绪标志(供下游消费)
- [ ] push-gateway HTTP :8081 启用(/healthz 返 200
- [ ] /readyz 返 200Kafka 连通性检查通过
- [ ] 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 返 200Redis/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 NotificationEventaction=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 |