Add realtime gateway and simulator bridge
This commit is contained in:
287
doc/cloudflare-tunnel-dev-guide.md
Normal file
287
doc/cloudflare-tunnel-dev-guide.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Realtime Gateway + Cloudflare Tunnel 本机联调说明
|
||||
|
||||
本文档说明如何在**不正式部署到线上服务器**的前提下,把本机的 `realtime-gateway` 暴露给外部设备或远程联调方。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 真机调试
|
||||
- 外网联调
|
||||
- 家长端 / 场控端远程验证
|
||||
- 演示环境
|
||||
|
||||
不适用场景:
|
||||
|
||||
- 正式生产实时链路
|
||||
- 严格 SLA 场景
|
||||
- 高稳定长时间压测
|
||||
|
||||
---
|
||||
|
||||
## 1. 推荐结论
|
||||
|
||||
当前阶段建议:
|
||||
|
||||
1. 网关继续运行在本机
|
||||
2. 本机使用 Cloudflare Tunnel 暴露新网关
|
||||
3. 旧模拟器继续本地运行
|
||||
4. 旧模拟器通过桥接把数据发给本机新网关
|
||||
5. 外部客户端只访问 Cloudflare Tunnel 暴露出来的 `wss` 地址
|
||||
|
||||
这样做的好处:
|
||||
|
||||
- 不需要先买独立服务器
|
||||
- 不需要先做正式公网部署
|
||||
- 不改变当前本机开发结构
|
||||
- 新旧链路可以并行使用
|
||||
|
||||
---
|
||||
|
||||
## 2. 本机网关配置
|
||||
|
||||
推荐先使用:
|
||||
|
||||
[tunnel-dev.json](D:/dev/cmr-mini/realtime-gateway/config/tunnel-dev.json)
|
||||
|
||||
这个配置相比开发默认配置更适合 Tunnel 联调:
|
||||
|
||||
- 端口改为 `18080`
|
||||
- 关闭匿名 consumer
|
||||
- token 不再使用默认开发值
|
||||
|
||||
建议先把其中的 token 改成你自己的值。
|
||||
|
||||
启动方式:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go build -o .\bin\gateway.exe .\cmd\gateway
|
||||
.\bin\gateway.exe -config .\config\tunnel-dev.json
|
||||
```
|
||||
|
||||
本机地址:
|
||||
|
||||
- HTTP: `http://127.0.0.1:18080`
|
||||
- WebSocket: `ws://127.0.0.1:18080/ws`
|
||||
|
||||
---
|
||||
|
||||
## 3. Quick Tunnel 方案
|
||||
|
||||
Quick Tunnel 最适合当前阶段。
|
||||
|
||||
Cloudflare 官方文档说明:
|
||||
|
||||
- Quick Tunnel 用于测试和开发,不建议生产使用
|
||||
- 可以直接把本地 `http://localhost:8080` 之类的地址暴露出去
|
||||
- 命令形式是 `cloudflared tunnel --url http://localhost:8080`
|
||||
|
||||
来源:
|
||||
|
||||
- [Quick Tunnels](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)
|
||||
- [Set up Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/setup/)
|
||||
|
||||
### 3.1 启动 Quick Tunnel
|
||||
|
||||
你的网关如果跑在 `18080`:
|
||||
|
||||
```powershell
|
||||
cloudflared tunnel --url http://localhost:18080
|
||||
```
|
||||
|
||||
启动后,`cloudflared` 会输出一个随机 `trycloudflare.com` 域名。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
https://random-name.trycloudflare.com
|
||||
```
|
||||
|
||||
对应的 WebSocket 地址就是:
|
||||
|
||||
```text
|
||||
wss://random-name.trycloudflare.com/ws
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 对外使用时,WebSocket 必须写成 `wss://`
|
||||
- 本地 origin 仍然是 `http://localhost:18080`
|
||||
|
||||
### 3.2 Quick Tunnel 限制
|
||||
|
||||
Cloudflare 官方说明里,Quick Tunnel 当前有这些限制:
|
||||
|
||||
- 仅适合测试和开发
|
||||
- 有并发请求上限
|
||||
- 不支持 SSE
|
||||
|
||||
因此它适合:
|
||||
|
||||
- 临时分享联调地址
|
||||
- 验证 WebSocket 接入
|
||||
- 短期演示
|
||||
|
||||
不适合拿来当正式生产入口。
|
||||
|
||||
另外,官方文档提到:
|
||||
|
||||
- 如果本机 `.cloudflared` 目录里已有 `config.yaml`,Quick Tunnel 可能不能直接使用
|
||||
|
||||
来源:
|
||||
|
||||
- [Quick Tunnels](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)
|
||||
|
||||
---
|
||||
|
||||
## 4. Named Tunnel 方案
|
||||
|
||||
如果你后面不想每次拿随机域名,可以改用 Named Tunnel。
|
||||
|
||||
这时推荐的本地 `cloudflared` 配置示例在:
|
||||
|
||||
[config.example.yml](D:/dev/cmr-mini/realtime-gateway/deploy/cloudflared/config.example.yml)
|
||||
|
||||
示例内容:
|
||||
|
||||
```yaml
|
||||
tunnel: YOUR_TUNNEL_ID
|
||||
credentials-file: C:\Users\YOUR_USER\.cloudflared\YOUR_TUNNEL_ID.json
|
||||
|
||||
ingress:
|
||||
- hostname: gateway-dev.example.com
|
||||
service: http://localhost:18080
|
||||
- service: http_status:404
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
- Tunnel 把 `gateway-dev.example.com` 映射到本机 `http://localhost:18080`
|
||||
- 最后一条 `http_status:404` 是 catch-all
|
||||
|
||||
Cloudflare 官方文档对 published application 和 ingress 的说明见:
|
||||
|
||||
- [Set up Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/setup/)
|
||||
- [Protocols for published applications](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/routing-to-tunnel/protocols/)
|
||||
|
||||
启动后,对外 WebSocket 地址就是:
|
||||
|
||||
```text
|
||||
wss://gateway-dev.example.com/ws
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4.1 外部 consumer 小工具
|
||||
|
||||
如果你要在另一台机器上用仓库里的 `mock-consumer` 做联调,推荐复制:
|
||||
|
||||
[consumer-tunnel.example.json](D:/dev/cmr-mini/realtime-gateway/config/consumer-tunnel.example.json)
|
||||
[consumer-gps-heart.example.json](D:/dev/cmr-mini/realtime-gateway/config/consumer-gps-heart.example.json)
|
||||
|
||||
填好实际的公网地址和 token,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "wss://gateway-dev.example.com/ws",
|
||||
"token": "your-consumer-token",
|
||||
"deviceId": "child-001",
|
||||
"topics": [
|
||||
"telemetry.location",
|
||||
"telemetry.heart_rate"
|
||||
],
|
||||
"snapshot": true
|
||||
}
|
||||
```
|
||||
|
||||
然后运行:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-consumer -config .\config\consumer-tunnel.example.json
|
||||
```
|
||||
|
||||
命令行参数会覆盖配置文件中的同名项,所以临时换 `deviceId` 时可以直接追加:
|
||||
|
||||
```powershell
|
||||
go run .\cmd\mock-consumer -config .\config\consumer-tunnel.example.json -device-id child-002
|
||||
```
|
||||
|
||||
如果只想临时直接从命令行看 GPS 和心率:
|
||||
|
||||
```powershell
|
||||
go run .\cmd\mock-consumer -url wss://gateway-dev.example.com/ws -token your-consumer-token -device-id child-001 -topics telemetry.location,telemetry.heart_rate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 旧模拟器如何配合
|
||||
|
||||
旧模拟器仍然本地运行:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini
|
||||
npm run mock-gps-sim
|
||||
```
|
||||
|
||||
然后在旧模拟器页面的“新网关桥接”区域里填:
|
||||
|
||||
- 网关地址:`ws://127.0.0.1:18080/ws`
|
||||
- Producer Token:与你 `tunnel-dev.json` 里配置一致
|
||||
- 目标 Device ID:按你的联调对象填写
|
||||
- Source ID:例如 `mock-gps-sim-a`
|
||||
|
||||
这里要注意:
|
||||
|
||||
- 旧模拟器桥接到的是**本机网关地址**
|
||||
- 外部消费者连接的是**Cloudflare Tunnel 暴露的公网地址**
|
||||
|
||||
不要把旧模拟器桥接目标直接写成公网 `wss` 地址。
|
||||
旧模拟器和网关都在本机,直接走本地回环最稳。
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐联调拓扑
|
||||
|
||||
```text
|
||||
旧模拟器页面
|
||||
-> 本机 mock-gps-sim
|
||||
-> 本机 realtime-gateway (ws://127.0.0.1:18080/ws)
|
||||
-> Cloudflare Tunnel
|
||||
-> 外部 consumer / 真机 / 调试端 (wss://<public-host>/ws)
|
||||
```
|
||||
|
||||
这样职责最清晰:
|
||||
|
||||
- 旧模拟器只负责发模拟数据
|
||||
- 新网关负责实时中转
|
||||
- Cloudflare Tunnel 只负责把本机网关暴露到外部
|
||||
|
||||
---
|
||||
|
||||
## 7. 当前阶段的安全建议
|
||||
|
||||
即使只是联调,也建议至少做到:
|
||||
|
||||
- `allowAnonymousConsumers = false`
|
||||
- Producer / Consumer / Controller token 全部替换
|
||||
- 不把默认 token 长期暴露在公网链接中
|
||||
- Tunnel 只暴露新网关,不一定要暴露旧模拟器 UI
|
||||
|
||||
如果只是你自己本机调试:
|
||||
|
||||
- 旧模拟器 UI 保持本地访问即可
|
||||
- 只把 `realtime-gateway` 暴露出去
|
||||
|
||||
---
|
||||
|
||||
## 8. 结论
|
||||
|
||||
当前阶段最推荐的方案是:
|
||||
|
||||
1. 本机用 [tunnel-dev.json](D:/dev/cmr-mini/realtime-gateway/config/tunnel-dev.json) 启动新网关
|
||||
2. 本机旧模拟器桥接到 `ws://127.0.0.1:18080/ws`
|
||||
3. 用 `cloudflared tunnel --url http://localhost:18080` 先跑 Quick Tunnel
|
||||
4. 外部客户端使用 `wss://<quick-tunnel-domain>/ws`
|
||||
5. 等你需要固定域名或更稳定的入口时,再切换 Named Tunnel
|
||||
|
||||
这条路径最轻、最稳,也最符合你现在“先不正式上线”的目标。
|
||||
123
doc/gateway-mvp-task-breakdown.md
Normal file
123
doc/gateway-mvp-task-breakdown.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 实时网关 MVP 拆分
|
||||
|
||||
本文档用于把 `realtime-gateway` 第一阶段工作拆成可执行任务。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
第一阶段只解决以下问题:
|
||||
|
||||
- 建立一个独立于现有模拟器的新 Go 网关工程
|
||||
- 能接收 Producer 上报
|
||||
- 能让 Consumer 按设备订阅
|
||||
- 能转发位置和基础 telemetry
|
||||
- 能保存 latest state
|
||||
- 能提供最基础的健康检查和运行入口
|
||||
|
||||
---
|
||||
|
||||
## 2. 任务拆分
|
||||
|
||||
### 2.1 工程骨架
|
||||
|
||||
- 新建 `realtime-gateway/`
|
||||
- 初始化 `go.mod`
|
||||
- 建立 `cmd/` 和 `internal/` 结构
|
||||
- 提供最小运行 README
|
||||
- 提供开发配置文件
|
||||
|
||||
### 2.2 网络入口
|
||||
|
||||
- HTTP server
|
||||
- `/healthz`
|
||||
- `/metrics`
|
||||
- `/ws`
|
||||
- 优雅关闭
|
||||
|
||||
### 2.3 会话管理
|
||||
|
||||
- 生成 `sessionId`
|
||||
- 维护连接生命周期
|
||||
- 记录角色和订阅
|
||||
- 断线清理
|
||||
|
||||
### 2.4 协议处理
|
||||
|
||||
- `authenticate`
|
||||
- `subscribe`
|
||||
- `publish`
|
||||
- `snapshot`
|
||||
- 错误返回格式统一
|
||||
|
||||
### 2.5 路由与 latest state
|
||||
|
||||
- 按 `deviceId` 路由
|
||||
- 支持 `groupId` 和 `topic` 过滤
|
||||
- latest state 内存缓存
|
||||
- `snapshot` 获取最新值
|
||||
|
||||
### 2.6 轻鉴权
|
||||
|
||||
- Producer token
|
||||
- Controller token
|
||||
- Consumer token 或匿名策略
|
||||
|
||||
### 2.7 基础观测
|
||||
|
||||
- JSON 日志
|
||||
- 连接日志
|
||||
- 发布日志
|
||||
- 基础 metrics 占位接口
|
||||
|
||||
---
|
||||
|
||||
## 3. 暂不进入 MVP
|
||||
|
||||
- 规则引擎正式实现
|
||||
- 通知分发正式实现
|
||||
- Recorder 正式实现
|
||||
- Replayer 正式实现
|
||||
- Redis
|
||||
- 集群
|
||||
- 数据库存档
|
||||
- 高级限流
|
||||
- 复杂 ACL
|
||||
|
||||
---
|
||||
|
||||
## 4. 建议开发顺序
|
||||
|
||||
1. 骨架与配置
|
||||
2. HTTP 和 WebSocket 入口
|
||||
3. 会话管理
|
||||
4. 发布与订阅
|
||||
5. latest state
|
||||
6. 鉴权
|
||||
7. 观察与日志
|
||||
8. 插件总线占位
|
||||
|
||||
---
|
||||
|
||||
## 5. 完成标准
|
||||
|
||||
满足以下条件可视为 MVP 跑通:
|
||||
|
||||
- 可以启动一个独立 Go 服务
|
||||
- 模拟 Producer 能发布 `telemetry.location`
|
||||
- Consumer 能订阅某个 `deviceId`
|
||||
- Consumer 能收到实时转发
|
||||
- Consumer 可读取某个设备的 latest state
|
||||
- Producer、Controller、Consumer 的角色边界基本可用
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一阶段
|
||||
|
||||
MVP 跑通后优先做:
|
||||
|
||||
1. 插件总线正式化
|
||||
2. Recorder 插件
|
||||
3. 模拟器对接新协议
|
||||
4. 简单规则插件
|
||||
5. Dispatcher 插件
|
||||
344
doc/gateway-protocol-spec.md
Normal file
344
doc/gateway-protocol-spec.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# 实时网关协议草案
|
||||
|
||||
本文档描述 `realtime-gateway` 第一版协议草案,范围只覆盖 MVP。
|
||||
|
||||
---
|
||||
|
||||
## 1. 连接方式
|
||||
|
||||
- 协议:WebSocket
|
||||
- 地址:`/ws`
|
||||
- 编码:JSON
|
||||
- 通信模式:客户端主动发消息,服务端返回状态或事件
|
||||
|
||||
---
|
||||
|
||||
## 2. 消息类型
|
||||
|
||||
客户端上行 `type`:
|
||||
|
||||
- `authenticate`
|
||||
- `join_channel`
|
||||
- `subscribe`
|
||||
- `publish`
|
||||
- `snapshot`
|
||||
|
||||
服务端下行 `type`:
|
||||
|
||||
- `welcome`
|
||||
- `authenticated`
|
||||
- `subscribed`
|
||||
- `published`
|
||||
- `snapshot`
|
||||
- `event`
|
||||
- `error`
|
||||
|
||||
---
|
||||
|
||||
## 3. 鉴权
|
||||
|
||||
### 3.1 authenticate
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "authenticate",
|
||||
"role": "producer",
|
||||
"token": "dev-producer-token"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `role` 可选值:`producer`、`consumer`、`controller`
|
||||
- 第一版 `consumer` 可允许匿名,是否启用由配置控制
|
||||
|
||||
### 3.2 join_channel
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "join_channel",
|
||||
"role": "producer",
|
||||
"channelId": "ch-xxxx",
|
||||
"token": "producer-token"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `channelId` 必填
|
||||
- `token` 必须和 `channelId` 对应
|
||||
- `role` 不同,使用的 token 也不同
|
||||
- `producerToken`
|
||||
- `consumerToken`
|
||||
- `controllerToken`
|
||||
|
||||
---
|
||||
|
||||
## 4. 订阅
|
||||
|
||||
### 4.1 subscribe
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"subscriptions": [
|
||||
{
|
||||
"deviceId": "child-001",
|
||||
"topic": "telemetry.location"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
支持字段:
|
||||
|
||||
- `channelId`
|
||||
- `deviceId`
|
||||
- `groupId`
|
||||
- `topic`
|
||||
|
||||
第一版匹配规则:
|
||||
|
||||
- 非空字段必须全部匹配
|
||||
- 空字段视为不过滤
|
||||
|
||||
---
|
||||
|
||||
## 5. 发布
|
||||
|
||||
### 5.1 publish
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "publish",
|
||||
"envelope": {
|
||||
"schemaVersion": 1,
|
||||
"messageId": "msg-001",
|
||||
"timestamp": 1711267200000,
|
||||
"topic": "telemetry.location",
|
||||
"source": {
|
||||
"kind": "producer",
|
||||
"id": "watch-001",
|
||||
"mode": "real"
|
||||
},
|
||||
"target": {
|
||||
"channelId": "ch-xxxx",
|
||||
"deviceId": "child-001",
|
||||
"groupId": "class-a"
|
||||
},
|
||||
"payload": {
|
||||
"lat": 31.2304,
|
||||
"lng": 121.4737,
|
||||
"speed": 1.2,
|
||||
"bearing": 90,
|
||||
"accuracy": 6,
|
||||
"coordSystem": "GCJ02"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 只有 `producer` 和 `controller` 能发布
|
||||
- 第一版不做复杂 schema 校验
|
||||
- 第一版缓存键为 `channelId + deviceId`
|
||||
- 如果连接已经通过 `join_channel` 加入通道,服务端会自动补全 `target.channelId`
|
||||
|
||||
---
|
||||
|
||||
## 6. 快照
|
||||
|
||||
### 6.1 snapshot
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "snapshot",
|
||||
"subscriptions": [
|
||||
{
|
||||
"channelId": "ch-xxxx",
|
||||
"deviceId": "child-001"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
服务端返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "snapshot",
|
||||
"sessionId": "sess-2",
|
||||
"state": {
|
||||
"schemaVersion": 1,
|
||||
"timestamp": 1711267200000,
|
||||
"topic": "telemetry.location",
|
||||
"source": {
|
||||
"kind": "producer",
|
||||
"id": "watch-001",
|
||||
"mode": "real"
|
||||
},
|
||||
"target": {
|
||||
"deviceId": "child-001"
|
||||
},
|
||||
"payload": {
|
||||
"lat": 31.2304,
|
||||
"lng": 121.4737
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 服务端消息
|
||||
|
||||
### 7.1 welcome
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "welcome",
|
||||
"sessionId": "sess-1"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 authenticated
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "authenticated",
|
||||
"sessionId": "sess-1"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 subscribed
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subscribed",
|
||||
"sessionId": "sess-1"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 joined_channel
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "joined_channel",
|
||||
"sessionId": "sess-1",
|
||||
"state": {
|
||||
"channelId": "ch-xxxx",
|
||||
"deliveryMode": "cache_latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.5 published
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "published",
|
||||
"sessionId": "sess-1"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.6 event
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "event",
|
||||
"envelope": {
|
||||
"schemaVersion": 1,
|
||||
"timestamp": 1711267200000,
|
||||
"topic": "telemetry.location",
|
||||
"source": {
|
||||
"kind": "producer",
|
||||
"id": "watch-001",
|
||||
"mode": "real"
|
||||
},
|
||||
"target": {
|
||||
"channelId": "ch-xxxx",
|
||||
"deviceId": "child-001"
|
||||
},
|
||||
"payload": {
|
||||
"lat": 31.2304,
|
||||
"lng": 121.4737
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.7 error
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"error": "authentication failed"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 第一版约束
|
||||
|
||||
- 不做历史回放协议
|
||||
- 不做压缩和二进制编码
|
||||
- 不做批量发布
|
||||
- 不做通配符 topic
|
||||
- 不做持久会话
|
||||
- 不做 ACK 队列
|
||||
|
||||
---
|
||||
|
||||
## 9. 管理接口
|
||||
|
||||
### 9.1 创建 channel
|
||||
|
||||
`POST /api/channel/create`
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"label": "debug-a",
|
||||
"deliveryMode": "cache_latest",
|
||||
"ttlSeconds": 28800
|
||||
}
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"snapshot": {
|
||||
"id": "ch-xxxx",
|
||||
"label": "debug-a",
|
||||
"deliveryMode": "cache_latest"
|
||||
},
|
||||
"producerToken": "....",
|
||||
"consumerToken": "....",
|
||||
"controllerToken": "...."
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 管理台读取接口
|
||||
|
||||
- `GET /api/admin/overview`
|
||||
- `GET /api/admin/sessions`
|
||||
- `GET /api/admin/latest`
|
||||
- `GET /api/admin/channels`
|
||||
- `GET /api/admin/traffic`
|
||||
- `GET /api/admin/live`
|
||||
|
||||
---
|
||||
|
||||
## 10. 第二阶段预留
|
||||
|
||||
后续协议可以增加:
|
||||
|
||||
- `command`
|
||||
- `batch_publish`
|
||||
- `rule_event`
|
||||
- `plugin_status`
|
||||
- `replay_control`
|
||||
- `auth_refresh`
|
||||
877
doc/realtime-device-gateway-architecture.md
Normal file
877
doc/realtime-device-gateway-architecture.md
Normal file
@@ -0,0 +1,877 @@
|
||||
# 实时设备数据网关最终方案
|
||||
|
||||
本文档用于收敛当前关于 GPS 模拟、中转、监控、规则判定、回放、通知分发等讨论,给出一版可直接进入实现设计的最终方案。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与定位
|
||||
|
||||
本系统不再定义为“GPS 模拟器”,而定义为:
|
||||
|
||||
- 一个独立部署的实时设备数据网关
|
||||
- 负责实时接入、标准化、路由、订阅、最新状态同步
|
||||
- 默认不依赖数据库,不承担历史存储职责
|
||||
- 通过插件扩展规则判定、通知分发、回放、归档等能力
|
||||
|
||||
一句话定义:
|
||||
|
||||
> 一个以实时中转为核心、以插件扩展业务能力的轻量遥测网关。
|
||||
|
||||
---
|
||||
|
||||
## 2. 设计原则
|
||||
|
||||
### 2.1 核心优先级
|
||||
|
||||
核心目标按优先级排序如下:
|
||||
|
||||
1. 实时性能
|
||||
2. 稳定性
|
||||
3. 低耦合
|
||||
4. 易扩展
|
||||
5. 易观测
|
||||
|
||||
### 2.2 核心边界
|
||||
|
||||
核心服务只负责:
|
||||
|
||||
- 长连接接入
|
||||
- 消息标准化
|
||||
- 路由转发
|
||||
- 订阅管理
|
||||
- latest state 缓存
|
||||
- 连接管理
|
||||
- 心跳和断线清理
|
||||
- 基础鉴权
|
||||
- 限流与基本防护
|
||||
|
||||
核心服务不负责:
|
||||
|
||||
- 历史存储
|
||||
- 业务报表
|
||||
- 复杂规则引擎
|
||||
- 第三方通知发送
|
||||
- 业务数据库查询
|
||||
- 面向家长端或场控端的专属业务逻辑
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
### 3.1 拓扑关系
|
||||
|
||||
```text
|
||||
Producer
|
||||
├─ 手机客户端
|
||||
├─ 外部模拟器
|
||||
├─ 回放器
|
||||
└─ 设备接入器
|
||||
|
|
||||
v
|
||||
Realtime Device Gateway
|
||||
├─ Connection Manager
|
||||
├─ Protocol Layer
|
||||
├─ Session Manager
|
||||
├─ Router / Fanout
|
||||
├─ Latest State Cache
|
||||
└─ Plugin Bus
|
||||
|
|
||||
+--> Consumer
|
||||
| ├─ 小程序 / App
|
||||
| ├─ 家长端
|
||||
| ├─ 场控端
|
||||
| └─ 调试端 / 大屏
|
||||
|
|
||||
+--> Plugins
|
||||
├─ Rule Engine
|
||||
├─ Dispatcher
|
||||
├─ Recorder
|
||||
├─ Replayer Adapter
|
||||
└─ Webhook / Bridge
|
||||
|
||||
Business Server
|
||||
├─ 用户与设备关系
|
||||
├─ 配置管理
|
||||
├─ 历史归档
|
||||
└─ 业务接口
|
||||
```
|
||||
|
||||
### 3.2 在整个系统中的定位
|
||||
|
||||
实时网关在整个系统中,不是主业务服务器的替代品,而是一层独立的实时中枢。
|
||||
|
||||
职责分工建议固定为:
|
||||
|
||||
- 设备、模拟器、回放器
|
||||
- 负责产出实时数据
|
||||
- 实时网关
|
||||
- 负责接入、路由、订阅、latest state、实时分发、运行态观察
|
||||
- 业务服务器
|
||||
- 负责用户、设备归属、配置、历史存档、业务查询
|
||||
- 插件
|
||||
- 负责规则、通知、归档、回放等异步能力
|
||||
|
||||
一句话理解:
|
||||
|
||||
> 主业务服务器负责“谁是谁”,实时网关负责“现在发生了什么,发给谁看”。
|
||||
|
||||
### 3.3 服务角色
|
||||
|
||||
- `Producer`
|
||||
- 实时数据生产者
|
||||
- 包括真实设备、模拟器、回放器
|
||||
|
||||
- `Consumer`
|
||||
- 实时数据消费者
|
||||
- 包括家长端、场控端、调试端、大屏
|
||||
|
||||
- `Controller`
|
||||
- 可向 Producer 或 Gateway 下发控制命令
|
||||
- 包括调试控制台、场控后台、回放控制器
|
||||
|
||||
### 3.4 角色使用流程
|
||||
|
||||
#### 开发模拟
|
||||
|
||||
1. Controller 在管理台创建 `channel`
|
||||
2. 网关返回 `channelId / producerToken / consumerToken`
|
||||
3. 老模拟器作为 Producer 加入 channel 并开始发 `telemetry.location / telemetry.heart_rate`
|
||||
4. 管理台和调试 Consumer 实时观察数据
|
||||
5. 业务服务器此时可以不参与,或仅做低频归档
|
||||
|
||||
#### 家长端监控
|
||||
|
||||
1. 真机设备作为 Producer 向网关持续上报位置和状态
|
||||
2. 家长端作为 Consumer 订阅某个 `deviceId` 或 `groupId`
|
||||
3. 网关负责实时分发
|
||||
4. 业务服务器负责账号关系、历史数据和业务页面
|
||||
|
||||
#### 场控
|
||||
|
||||
1. 场控端作为 Consumer 订阅多个设备或一个分组
|
||||
2. 网关持续推送实时位置、状态和事件
|
||||
3. 如果有控制需求,场控端再以 Controller 身份下发命令
|
||||
4. 规则插件可基于同一条实时流做违规判定
|
||||
|
||||
#### 归档
|
||||
|
||||
1. Producer 只向网关上报实时数据
|
||||
2. Recorder 插件异步消费网关流
|
||||
3. Recorder 再按 `10-30s` 批量写入业务服务器
|
||||
|
||||
这样可以保证:
|
||||
|
||||
- 实时链路和归档链路解耦
|
||||
- 客户端可以逐步从双写演进到单写网关
|
||||
- 实时数据和历史数据口径更一致
|
||||
|
||||
---
|
||||
|
||||
## 4. 部署原则
|
||||
|
||||
### 4.1 独立部署
|
||||
|
||||
实时网关必须独立于主业务服务器部署。
|
||||
|
||||
这样做的原因:
|
||||
|
||||
- 不占用主业务服务器的实时长连接资源
|
||||
- 不让实时 fanout 干扰主业务接口
|
||||
- 实时服务可独立扩容
|
||||
- 便于分离故障和性能问题
|
||||
|
||||
### 4.2 与主业务服务的关系
|
||||
|
||||
主业务服务只提供控制面能力:
|
||||
|
||||
- 用户与设备归属关系
|
||||
- 权限配置
|
||||
- 策略配置
|
||||
- 历史存档入口
|
||||
|
||||
实时网关处理数据面:
|
||||
|
||||
- 高频 telemetry
|
||||
- 最新状态同步
|
||||
- 实时下行通知和控制
|
||||
|
||||
原则上,不允许每条实时上报都回主业务服务查库。
|
||||
|
||||
---
|
||||
|
||||
## 5. 0 数据库方案
|
||||
|
||||
### 5.1 可行性
|
||||
|
||||
第一版核心网关可以做到 0 数据库依赖。
|
||||
|
||||
这里的含义是:
|
||||
|
||||
- 不接 MySQL
|
||||
- 不接 PostgreSQL
|
||||
- 不接业务持久化存储
|
||||
- 不存历史轨迹
|
||||
- 不做持久化 session
|
||||
|
||||
### 5.2 保留的内存态
|
||||
|
||||
即使 0 数据库,网关仍然必须保留以下运行时内存数据:
|
||||
|
||||
- 连接表
|
||||
- 订阅关系
|
||||
- 设备 latest state
|
||||
- 会话元数据
|
||||
- 限流计数
|
||||
- 心跳状态
|
||||
|
||||
### 5.3 重启语义
|
||||
|
||||
网关重启后允许丢失:
|
||||
|
||||
- 当前连接
|
||||
- 当前订阅
|
||||
- latest state
|
||||
- 临时限流计数
|
||||
|
||||
系统恢复方式:
|
||||
|
||||
- 客户端自动重连
|
||||
- 重新鉴权
|
||||
- 重新订阅
|
||||
- Producer 继续上报最新点
|
||||
|
||||
这对实时网关是可接受的。
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能目标
|
||||
|
||||
### 6.1 第一版容量目标
|
||||
|
||||
第一版至少满足:
|
||||
|
||||
- `1000-2000` 设备同时在线上报
|
||||
- 默认 `1 Hz` 实时上报能力
|
||||
- 支持部分调试设备升频到 `2-5 Hz`
|
||||
- 端到端实时链路延迟目标 `< 500 ms`
|
||||
- 插件故障不阻塞核心中转
|
||||
|
||||
### 6.2 带宽认知
|
||||
|
||||
实时中转一定消耗网关所在服务器的带宽,但不会占用主业务服务器带宽。
|
||||
|
||||
不做历史存储,只能减少:
|
||||
|
||||
- 磁盘 IO
|
||||
- 存储成本
|
||||
- 数据库压力
|
||||
|
||||
不能减少:
|
||||
|
||||
- 长连接压力
|
||||
- 实时 fanout 压力
|
||||
- 网关入口和出口带宽
|
||||
|
||||
### 6.3 核心优化原则
|
||||
|
||||
- 不允许全量广播
|
||||
- 必须按设备或组定向订阅
|
||||
- payload 尽量小
|
||||
- latest state 只保留当前值
|
||||
- 插件必须异步消费
|
||||
- 高频调试源必须可限流
|
||||
|
||||
---
|
||||
|
||||
## 7. 消息模型
|
||||
|
||||
### 7.1 统一信封
|
||||
|
||||
所有进入网关的数据统一使用一个标准信封:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"messageId": "uuid",
|
||||
"timestamp": 1711267200000,
|
||||
"topic": "telemetry.location",
|
||||
"source": {
|
||||
"kind": "producer",
|
||||
"id": "watch-001",
|
||||
"mode": "real"
|
||||
},
|
||||
"target": {
|
||||
"deviceId": "child-001",
|
||||
"groupId": "class-a"
|
||||
},
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 topic 分类
|
||||
|
||||
建议只保留四类顶级 topic:
|
||||
|
||||
- `telemetry.*`
|
||||
- `state.*`
|
||||
- `event.*`
|
||||
- `command.*`
|
||||
|
||||
示例:
|
||||
|
||||
- `telemetry.location`
|
||||
- `telemetry.heart_rate`
|
||||
- `telemetry.motion`
|
||||
- `state.device`
|
||||
- `event.rule_violation`
|
||||
- `event.sos`
|
||||
- `command.replay.start`
|
||||
- `command.simulation.stop`
|
||||
|
||||
### 7.3 source mode
|
||||
|
||||
建议显式区分数据来源模式:
|
||||
|
||||
- `real`
|
||||
- `mock`
|
||||
- `replay`
|
||||
- `control`
|
||||
|
||||
这样业务侧可以明确识别是真实数据还是模拟/回放数据。
|
||||
|
||||
---
|
||||
|
||||
## 8. 主要数据载荷
|
||||
|
||||
### 8.1 位置 telemetry
|
||||
|
||||
```json
|
||||
{
|
||||
"lat": 31.2304,
|
||||
"lng": 121.4737,
|
||||
"altitude": 12.3,
|
||||
"speed": 1.5,
|
||||
"bearing": 90,
|
||||
"accuracy": 8,
|
||||
"coordSystem": "GCJ02"
|
||||
}
|
||||
```
|
||||
|
||||
最少保留字段:
|
||||
|
||||
- `timestamp`
|
||||
- `lat`
|
||||
- `lng`
|
||||
- `speed`
|
||||
- `bearing`
|
||||
- `accuracy`
|
||||
- `coordSystem`
|
||||
|
||||
### 8.2 心率 telemetry
|
||||
|
||||
```json
|
||||
{
|
||||
"bpm": 142,
|
||||
"confidence": 0.92
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 运动 telemetry
|
||||
|
||||
```json
|
||||
{
|
||||
"cadence": 168,
|
||||
"stepCount": 3201,
|
||||
"calories": 248.5,
|
||||
"movementState": "run"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 设备状态
|
||||
|
||||
```json
|
||||
{
|
||||
"online": true,
|
||||
"battery": 76,
|
||||
"network": "4g",
|
||||
"charging": false
|
||||
}
|
||||
```
|
||||
|
||||
### 8.5 事件
|
||||
|
||||
```json
|
||||
{
|
||||
"eventType": "rule_violation",
|
||||
"ruleId": "geo-fence-01",
|
||||
"severity": "high",
|
||||
"text": "leave allowed area"
|
||||
}
|
||||
```
|
||||
|
||||
### 8.6 控制命令
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "simulation.start",
|
||||
"args": {
|
||||
"sessionId": "sim-001",
|
||||
"speedRate": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 订阅与路由模型
|
||||
|
||||
### 9.1 路由单位
|
||||
|
||||
实时网关按以下维度路由:
|
||||
|
||||
- `deviceId`
|
||||
- `groupId`
|
||||
- `topic`
|
||||
|
||||
### 9.2 推荐订阅粒度
|
||||
|
||||
- 订阅某个设备全部实时消息
|
||||
- 订阅某个设备某类消息
|
||||
- 订阅某个组某类消息
|
||||
|
||||
示例:
|
||||
|
||||
- `device:child-001`
|
||||
- `device:child-001/topic:telemetry.location`
|
||||
- `group:class-a/topic:event.*`
|
||||
|
||||
### 9.3 latest state
|
||||
|
||||
每个设备保留一个最新状态快照,不存历史。
|
||||
|
||||
用途:
|
||||
|
||||
- 新订阅者刚连上时立即获得当前状态
|
||||
- 页面重连后快速恢复
|
||||
- 插件可从最新态快速计算
|
||||
|
||||
---
|
||||
|
||||
## 10. 鉴权策略
|
||||
|
||||
### 10.1 不建议完全不鉴权
|
||||
|
||||
如果完全不鉴权,会带来两个高风险问题:
|
||||
|
||||
- 任意客户端都能伪造位置或心率
|
||||
- 任意客户端都可能下发控制命令
|
||||
|
||||
### 10.2 推荐分级鉴权
|
||||
|
||||
- `Producer`
|
||||
- 必须强鉴权
|
||||
- 防止伪造上报
|
||||
|
||||
- `Controller`
|
||||
- 必须强鉴权
|
||||
- 防止误操作和恶意控制
|
||||
|
||||
- `Consumer`
|
||||
- 可轻鉴权
|
||||
- 可按内网、白名单、临时 token、短期票据放宽
|
||||
|
||||
### 10.3 第一版推荐方式
|
||||
|
||||
第一版建议:
|
||||
|
||||
- Producer 使用签名 token 或短期接入 token
|
||||
- Controller 使用管理 token
|
||||
- Consumer 使用轻量订阅 token
|
||||
|
||||
网关本身不查数据库,只做:
|
||||
|
||||
- token 基本校验
|
||||
- 角色识别
|
||||
- 权限范围校验
|
||||
|
||||
复杂鉴权可以由独立鉴权服务承担。
|
||||
|
||||
---
|
||||
|
||||
## 11. 插件体系
|
||||
|
||||
### 11.1 原则
|
||||
|
||||
事件处理不是核心同步主链路的一部分,而是插件。
|
||||
|
||||
同步主链路只做:
|
||||
|
||||
`ingest -> normalize -> route -> fanout -> update latest state`
|
||||
|
||||
然后异步投递到插件总线:
|
||||
|
||||
`publish -> plugin bus -> plugin async consume`
|
||||
|
||||
### 11.2 第一批插件类型
|
||||
|
||||
- `Rule Engine`
|
||||
- 实时规则判定
|
||||
- `Dispatcher`
|
||||
- 通知和下行分发
|
||||
- `Recorder`
|
||||
- 归档与历史写入
|
||||
- `Replayer`
|
||||
- 文件或历史流回放
|
||||
- `Webhook`
|
||||
- 向外部系统桥接
|
||||
|
||||
### 11.3 插件要求
|
||||
|
||||
- 不能阻塞主链路
|
||||
- 失败必须隔离
|
||||
- 可独立启停
|
||||
- 最好支持单独部署
|
||||
|
||||
---
|
||||
|
||||
## 12. 规则判定与通知分发
|
||||
|
||||
### 12.1 规则不进核心
|
||||
|
||||
规则判定必须在插件层执行,不进入中转核心。
|
||||
|
||||
原因:
|
||||
|
||||
- 规则计算不稳定
|
||||
- 容易引入复杂状态
|
||||
- 容易放大 CPU 和 IO 消耗
|
||||
- 不应影响实时转发
|
||||
|
||||
### 12.2 推荐规则类型
|
||||
|
||||
- 阈值规则
|
||||
- 心率过高
|
||||
- 速度异常
|
||||
- 电量过低
|
||||
|
||||
- 时空规则
|
||||
- 离开围栏
|
||||
- 进入禁区
|
||||
- 停留超时
|
||||
- 偏离路线
|
||||
|
||||
- 行为规则
|
||||
- 长时间静止
|
||||
- 轨迹跳变
|
||||
- 设备离线
|
||||
|
||||
### 12.3 动作分发
|
||||
|
||||
规则命中后由 Dispatcher 插件执行动作:
|
||||
|
||||
- 下发终端警告
|
||||
- 推送给家长端
|
||||
- 推送给场控端
|
||||
- 上报业务服务
|
||||
- 触发 Webhook
|
||||
|
||||
### 12.4 规则系统必须处理的问题
|
||||
|
||||
- 去重
|
||||
- 冷却时间
|
||||
- 恢复事件
|
||||
- 幂等
|
||||
|
||||
---
|
||||
|
||||
## 13. 客户端上报策略
|
||||
|
||||
### 13.1 当前推荐的第一阶段方案
|
||||
|
||||
客户端保留两条链路:
|
||||
|
||||
- 实时链路
|
||||
- 有需要时实时上报到网关
|
||||
- 归档链路
|
||||
- 每 `10-30s` 打包一次发给业务服务器存档
|
||||
|
||||
这个方案的优点:
|
||||
|
||||
- 改造快
|
||||
- 实时与归档解耦
|
||||
- 主业务服务器不承受高频位置流
|
||||
|
||||
### 13.2 双写风险
|
||||
|
||||
客户端双写存在天然风险:
|
||||
|
||||
- 两边成功率不同
|
||||
- 数据口径可能不一致
|
||||
- 客户端逻辑更复杂
|
||||
- 重试和去重更难
|
||||
|
||||
### 13.3 推荐演进方向
|
||||
|
||||
推荐第二阶段改成:
|
||||
|
||||
- 客户端只向网关实时上报
|
||||
- `Recorder` 插件按 `10-30s` 批量归档到业务服务器
|
||||
|
||||
这样可以保证:
|
||||
|
||||
- 实时和归档数据同源
|
||||
- 客户端逻辑更轻
|
||||
- 后续回放、规则、告警直接复用同一条实时流
|
||||
|
||||
### 13.4 客户端升频策略
|
||||
|
||||
建议客户端支持策略切换:
|
||||
|
||||
- `normal`
|
||||
- 低频或关闭实时上报
|
||||
- `monitor`
|
||||
- `1-5s` 实时上报
|
||||
- `debug`
|
||||
- `1 Hz`
|
||||
- `alert`
|
||||
- 临时升频
|
||||
|
||||
---
|
||||
|
||||
## 14. 模拟与回放
|
||||
|
||||
### 14.1 模拟器定位
|
||||
|
||||
模拟器不再是一个独立特殊系统,而是一个标准 Producer。
|
||||
|
||||
它可以上报:
|
||||
|
||||
- `telemetry.location`
|
||||
- `telemetry.heart_rate`
|
||||
- `telemetry.motion`
|
||||
- `state.device`
|
||||
|
||||
### 14.2 回放器定位
|
||||
|
||||
回放器也是 Producer。
|
||||
|
||||
区别只是:
|
||||
|
||||
- source mode 为 `replay`
|
||||
- 输入来源是文件或历史流
|
||||
- 支持播放、暂停、倍速、循环
|
||||
|
||||
### 14.3 地图模拟器要求
|
||||
|
||||
地图拖点时不应直接瞬移发点,至少要支持:
|
||||
|
||||
- 时间戳
|
||||
- 插值
|
||||
- 平滑
|
||||
- 速度和朝向生成
|
||||
|
||||
否则业务侧会看到不真实的轨迹。
|
||||
|
||||
---
|
||||
|
||||
## 15. 技术选型建议
|
||||
|
||||
### 15.1 技术选型原则
|
||||
|
||||
本系统技术选型必须遵守以下原则:
|
||||
|
||||
- 轻量
|
||||
- 健壮
|
||||
- 性能优先
|
||||
- 少依赖
|
||||
- 易部署
|
||||
- 易排障
|
||||
|
||||
目标形态应更接近:
|
||||
|
||||
- 软路由插件
|
||||
- 轻量代理
|
||||
- 边缘网关
|
||||
- 单二进制网络服务
|
||||
|
||||
而不是典型的重业务后台服务。
|
||||
|
||||
### 15.2 第一版推荐
|
||||
|
||||
核心网关建议明确采用以下技术栈:
|
||||
|
||||
- 服务端语言:`Go`
|
||||
- 实时协议:`WebSocket`
|
||||
- 管理接口:`HTTP`
|
||||
- 消息格式:`JSON`
|
||||
- 运行态存储:纯内存
|
||||
- 配置方式:本地配置文件加环境变量
|
||||
- 部署形态:单二进制
|
||||
- 日志:结构化日志
|
||||
|
||||
推荐原因:
|
||||
|
||||
- 更适合长连接、低延迟、fanout 场景
|
||||
- 内存和 CPU 占用更可控
|
||||
- 二进制部署简单
|
||||
- 依赖少,更接近基础设施服务风格
|
||||
- 后续扩到更高在线数更稳
|
||||
|
||||
如果目标是先稳定支撑 `1000-2000` 在线连接,推荐优先考虑:
|
||||
|
||||
- 服务端:`Go`
|
||||
- 实时协议:`WebSocket`
|
||||
- 管理接口:`HTTP`
|
||||
- 配置来源:本地配置文件或环境变量
|
||||
- latest state:进程内内存
|
||||
|
||||
原因:
|
||||
|
||||
- 长连接和 fanout 场景更贴近 Go 的强项
|
||||
- 资源占用更稳
|
||||
- 后续扩到更高并发成本更低
|
||||
|
||||
### 15.3 网关内部建议模块
|
||||
|
||||
为了保持“轻量但可扩展”,建议核心网关内部拆为以下模块:
|
||||
|
||||
- `listener`
|
||||
- 管理 WebSocket 和 HTTP 入口
|
||||
- `session manager`
|
||||
- 管理连接、身份、心跳、会话元数据
|
||||
- `router`
|
||||
- 负责 topic、device、group 维度路由
|
||||
- `fanout hub`
|
||||
- 负责多订阅者分发
|
||||
- `state cache`
|
||||
- 保存设备 latest state
|
||||
- `auth verifier`
|
||||
- 校验 Producer、Consumer、Controller 的 token
|
||||
- `rate limiter`
|
||||
- 限制异常高频上报
|
||||
- `plugin bus`
|
||||
- 异步发布给规则、通知、归档等插件
|
||||
|
||||
这些模块都应保持进程内实现,避免第一版引入外部组件。
|
||||
|
||||
### 15.4 第一版不建议引入的依赖
|
||||
|
||||
第一版不建议引入:
|
||||
|
||||
- 数据库
|
||||
- 消息队列
|
||||
- 服务注册中心
|
||||
- 复杂配置中心
|
||||
- 脚本型规则执行器
|
||||
- 重型 RPC 框架
|
||||
|
||||
这些能力都可以在后续容量和业务复杂度真正需要时再补。
|
||||
|
||||
### 15.5 可接受的替代方案
|
||||
|
||||
如果团队更熟悉 TypeScript,也可以先用:
|
||||
|
||||
- `Node.js + TypeScript + WebSocket`
|
||||
|
||||
但要注意:
|
||||
|
||||
- 网关层应保持非常薄
|
||||
- 不要过早塞业务逻辑
|
||||
- 一开始就准备好以后拆分插件
|
||||
|
||||
但如果核心目标明确是“像软路由一样稳定跑实时中转”,仍然优先推荐 Go。
|
||||
|
||||
---
|
||||
|
||||
## 16. MVP 范围
|
||||
|
||||
第一版只做以下内容:
|
||||
|
||||
### 16.1 核心网关
|
||||
|
||||
- WebSocket 接入
|
||||
- Producer / Consumer / Controller 三类角色
|
||||
- 统一消息信封
|
||||
- 按设备订阅
|
||||
- latest state 缓存
|
||||
- 心跳和断线处理
|
||||
- 基础限流
|
||||
- 基础鉴权
|
||||
|
||||
### 16.2 模拟器接入
|
||||
|
||||
- 地图拖点
|
||||
- 轨迹文件导入
|
||||
- 播放 / 暂停 / 倍速 / 循环
|
||||
- 输出位置和心率等标准 telemetry
|
||||
|
||||
### 16.3 观察端
|
||||
|
||||
- 订阅设备位置和状态
|
||||
- 查看最新点
|
||||
- 接收实时事件
|
||||
|
||||
### 16.4 插件最小集
|
||||
|
||||
- Recorder
|
||||
- 可选启用
|
||||
- Rule Engine
|
||||
- 先做最简单几条规则
|
||||
- Dispatcher
|
||||
- 先支持网关内消息通知
|
||||
|
||||
### 16.5 明确不做
|
||||
|
||||
- 业务数据库耦合
|
||||
- 复杂多租户
|
||||
- 大报表系统
|
||||
- 历史查询中心
|
||||
- 集群调度系统
|
||||
|
||||
---
|
||||
|
||||
## 17. 第二阶段演进
|
||||
|
||||
第二阶段建议逐步增加:
|
||||
|
||||
- 归档从客户端双写迁移到 Recorder
|
||||
- 多实例网关
|
||||
- Redis 作为共享状态和实例间消息桥
|
||||
- 更细粒度权限
|
||||
- 完整规则库
|
||||
- 回放服务独立化
|
||||
- 外部通知集成
|
||||
|
||||
第三阶段再考虑:
|
||||
|
||||
- 数据库落盘
|
||||
- 历史检索
|
||||
- 统计分析
|
||||
- 高可用和地域分布
|
||||
|
||||
---
|
||||
|
||||
## 18. 最终结论
|
||||
|
||||
最终方案建议如下:
|
||||
|
||||
- 以“实时设备数据网关”作为系统核心,而不是继续围绕“GPS 模拟器”扩展
|
||||
- 网关独立部署,不占主业务服务器的实时带宽和连接资源
|
||||
- 第一版采用 0 数据库设计,仅保留运行时内存态
|
||||
- 核心只做实时接入、标准化、路由、订阅和 latest state
|
||||
- 规则判定、通知分发、回放、归档全部插件化
|
||||
- 第一版先支撑 `1000-2000` 在线上报
|
||||
- 当前客户端可继续采用“实时发网关 + 10-30 秒批量发业务服”的过渡方案
|
||||
- 中期应演进为“客户端单写网关,归档由 Recorder 插件完成”
|
||||
|
||||
这套方案能同时覆盖:
|
||||
|
||||
- 开发模拟
|
||||
- 家长端监控
|
||||
- 场控
|
||||
- 规则判定
|
||||
- 通知分发
|
||||
- 数据回放
|
||||
- 后续更多传感器接入
|
||||
|
||||
同时又能把实时性能放在系统设计的首位。
|
||||
443
doc/realtime-gateway-runbook.md
Normal file
443
doc/realtime-gateway-runbook.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# Realtime Gateway 运行手册
|
||||
|
||||
本文档用于整理当前 `realtime-gateway` 的构建、运行、联调和排障方式,覆盖今天已经落地的能力。
|
||||
|
||||
## 1. 当前组成
|
||||
|
||||
当前工程由 3 部分组成:
|
||||
|
||||
- 新实时网关
|
||||
- 路径:`D:\dev\cmr-mini\realtime-gateway`
|
||||
- 老模拟器
|
||||
- 路径:`D:\dev\cmr-mini\tools\mock-gps-sim`
|
||||
- 文档目录
|
||||
- 路径:`D:\dev\cmr-mini\doc`
|
||||
|
||||
当前推荐的本地开发方式:
|
||||
|
||||
- 老模拟器继续负责地图拖点、路径回放、心率模拟
|
||||
- 新网关负责实时中转、channel 管理、实时查看、流量统计
|
||||
|
||||
## 1.1 中转服务器在整个系统中的用法
|
||||
|
||||
中转服务器在整个系统里,应当被当成“实时中枢”来使用,而不是业务主服务器的一部分。
|
||||
|
||||
当前建议的角色分工:
|
||||
|
||||
- `Producer`
|
||||
- 老模拟器
|
||||
- 后续真机设备
|
||||
- 后续回放器
|
||||
- `Consumer`
|
||||
- 管理台
|
||||
- CLI 调试端
|
||||
- 后续家长端 / 场控端 / 手机观察端
|
||||
- `Controller`
|
||||
- 管理台
|
||||
- 后续场控后台
|
||||
- 后续回放控制器
|
||||
- `Business Server`
|
||||
- 用户、设备关系、配置、历史存档
|
||||
|
||||
系统里的基本流向应当是:
|
||||
|
||||
`Producer -> Gateway -> Consumer / Plugin -> Business Server`
|
||||
|
||||
也就是说:
|
||||
|
||||
- 实时数据先进入网关
|
||||
- 网关负责实时观察和分发
|
||||
- 业务服务器负责低频业务数据和历史
|
||||
|
||||
## 2. 端口约定
|
||||
|
||||
本地开发建议统一使用以下端口:
|
||||
|
||||
- 老模拟器 HTTP / WS:`17865`
|
||||
- 新网关 HTTP / WS:`18080`
|
||||
|
||||
对应地址:
|
||||
|
||||
- 老模拟器页面:`http://127.0.0.1:17865/`
|
||||
- 老模拟器本地 mock WS:`ws://127.0.0.1:17865/mock-gps`
|
||||
- 新网关管理台:`http://127.0.0.1:18080/`
|
||||
- 新网关 WebSocket:`ws://127.0.0.1:18080/ws`
|
||||
|
||||
## 3. 构建命令
|
||||
|
||||
### 3.1 构建网关
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go build -o .\bin\gateway.exe .\cmd\gateway
|
||||
```
|
||||
|
||||
### 3.2 构建调试 CLI
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go build -o .\bin\mock-producer.exe .\cmd\mock-producer
|
||||
go build -o .\bin\mock-consumer.exe .\cmd\mock-consumer
|
||||
```
|
||||
|
||||
### 3.3 一次性编译检查
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go build ./...
|
||||
```
|
||||
|
||||
## 4. 运行命令
|
||||
|
||||
### 4.1 启动新网关
|
||||
|
||||
开发期建议直接使用 Tunnel 开发配置:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\gateway -config .\config\tunnel-dev.json
|
||||
```
|
||||
|
||||
如果只想本机用默认开发配置:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\gateway -config .\config\dev.json
|
||||
```
|
||||
|
||||
### 4.2 启动老模拟器
|
||||
|
||||
在仓库根目录:
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini
|
||||
npm run mock-gps-sim
|
||||
```
|
||||
|
||||
### 4.3 打开页面
|
||||
|
||||
- 新网关管理台:`http://127.0.0.1:18080/`
|
||||
- 老模拟器页面:`http://127.0.0.1:17865/`
|
||||
|
||||
如果页面和实际代码不一致,先强刷一次:
|
||||
|
||||
```text
|
||||
Ctrl + F5
|
||||
```
|
||||
|
||||
## 5. 当前管理台能力
|
||||
|
||||
新网关管理台已经包含:
|
||||
|
||||
- 会话列表
|
||||
- channel 创建与查看
|
||||
- latest state 查看
|
||||
- 实时数据窗口
|
||||
- GPS / 心率专用格式化显示
|
||||
- 轨迹预览
|
||||
- 流量统计
|
||||
- Published
|
||||
- Dropped
|
||||
- Fanout
|
||||
- Topic 统计
|
||||
- Channel 统计
|
||||
|
||||
相关接口:
|
||||
|
||||
- `/api/admin/overview`
|
||||
- `/api/admin/sessions`
|
||||
- `/api/admin/latest`
|
||||
- `/api/admin/channels`
|
||||
- `/api/admin/traffic`
|
||||
- `/api/admin/live`
|
||||
|
||||
## 6. 本地联调方式
|
||||
|
||||
### 6.1 方式 A:直接用 CLI
|
||||
|
||||
1. 启动网关
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\gateway -config .\config\tunnel-dev.json
|
||||
```
|
||||
|
||||
2. 启动 consumer
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-consumer -config .\config\consumer-gps-heart.example.json
|
||||
```
|
||||
|
||||
3. 发 GPS
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-producer -device-id child-001 -topic telemetry.location -count 5
|
||||
```
|
||||
|
||||
4. 发心率
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-producer -device-id child-001 -topic telemetry.heart_rate -bpm 148 -count 5
|
||||
```
|
||||
|
||||
### 6.2 方式 B:老模拟器桥接新网关
|
||||
|
||||
这是当前最推荐的开发联调方式。
|
||||
|
||||
1. 启动网关
|
||||
2. 启动老模拟器
|
||||
3. 在老模拟器页面里打开“新网关桥接”
|
||||
4. 填入:
|
||||
|
||||
- 网关地址:`ws://127.0.0.1:18080/ws`
|
||||
- `Producer Token / Channel Token`
|
||||
- `Channel ID` 可选
|
||||
- `Device ID`
|
||||
- `Group ID`
|
||||
- `Source ID`
|
||||
- `Source Mode`
|
||||
|
||||
5. 点“应用桥接配置”
|
||||
|
||||
这样可以同时保留:
|
||||
|
||||
- 老 mock 小程序链路
|
||||
- 新网关旁路链路
|
||||
|
||||
### 6.3 现在最推荐的使用方式
|
||||
|
||||
今天这个阶段,最推荐这样用:
|
||||
|
||||
1. 老模拟器作为主 Producer
|
||||
2. 新网关作为实时中转和观测面板
|
||||
3. 管理台负责创建 channel、查看会话、观察实时流量
|
||||
4. 业务服务器暂时不接入高频实时链路
|
||||
|
||||
这个组合的好处是:
|
||||
|
||||
- 不影响你现有模拟和调试方式
|
||||
- 新网关可以独立收敛协议和运行态
|
||||
- 出问题时边界清晰,便于排查
|
||||
|
||||
## 7. channel 模式怎么用
|
||||
|
||||
当前网关支持两种生产者接入模式。
|
||||
|
||||
### 7.1 老模式
|
||||
|
||||
不使用 channel,直接走 `authenticate`:
|
||||
|
||||
```json
|
||||
{"type":"authenticate","role":"producer","token":"dev-producer-token"}
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 早期本机调试
|
||||
- 兼容老桥接工具
|
||||
|
||||
### 7.2 新模式
|
||||
|
||||
先创建 channel,再用 `join_channel`:
|
||||
|
||||
```json
|
||||
{"type":"join_channel","role":"producer","channelId":"ch-xxxx","token":"<producerToken>"}
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 多人联调
|
||||
- 多会话隔离
|
||||
- `drop_if_no_consumer` / `cache_latest` 策略
|
||||
|
||||
### 7.3 管理台创建 channel
|
||||
|
||||
在管理台可直接创建 channel,返回:
|
||||
|
||||
- `channelId`
|
||||
- `producerToken`
|
||||
- `consumerToken`
|
||||
- `controllerToken`
|
||||
|
||||
### 7.4 老模拟器接 channel
|
||||
|
||||
老模拟器现在已经支持:
|
||||
|
||||
- 不填 `Channel ID`:走老的 `authenticate`
|
||||
- 填 `Channel ID`:自动走 `join_channel`
|
||||
|
||||
所以如果你在管理台里创建了 channel:
|
||||
|
||||
- `Channel ID` 填 `channelId`
|
||||
- `Producer Token / Channel Token` 填 `producerToken`
|
||||
|
||||
两者必须配套使用。
|
||||
|
||||
## 8. delivery mode 说明
|
||||
|
||||
当前 channel 支持两种分发策略:
|
||||
|
||||
- `cache_latest`
|
||||
- 即使没有消费者,也会保留 latest state
|
||||
- `drop_if_no_consumer`
|
||||
- 没有消费者就直接丢弃,不保留 latest state
|
||||
|
||||
适用建议:
|
||||
|
||||
- 开发调试或临时通道:可用 `drop_if_no_consumer`
|
||||
- 常规联调和状态观察:建议 `cache_latest`
|
||||
|
||||
## 9. 管理台实时窗口说明
|
||||
|
||||
“实时数据窗口”当前支持:
|
||||
|
||||
- 按 `topic` 过滤
|
||||
- 按 `channelId` 过滤
|
||||
- 按 `deviceId` 过滤
|
||||
- 实时事件滚动显示
|
||||
- GPS 专用摘要
|
||||
- 经纬度
|
||||
- 速度
|
||||
- 航向
|
||||
- 精度
|
||||
- 心率专用摘要
|
||||
- bpm
|
||||
- 轨迹预览
|
||||
- 按 `channelId / deviceId` 聚合
|
||||
|
||||
建议使用方式:
|
||||
|
||||
- 如果联调设备较多,先填 `channelId`
|
||||
- 如果只看单个对象,再加 `deviceId`
|
||||
|
||||
## 10. 流量统计说明
|
||||
|
||||
网关已累计以下指标:
|
||||
|
||||
- `Published`
|
||||
- 收到的总发布数
|
||||
- `Dropped`
|
||||
- 因策略被丢弃的发布数
|
||||
- `Fanout`
|
||||
- 实际分发到消费者的总次数
|
||||
|
||||
同时支持:
|
||||
|
||||
- 按 `topic` 统计
|
||||
- 按 `channel` 统计
|
||||
|
||||
这几个指标适合用来观察:
|
||||
|
||||
- 老模拟器是否在稳定发流
|
||||
- 哪个 topic 最热
|
||||
- 哪个 channel 在大量占用实时流量
|
||||
- `drop_if_no_consumer` 是否正在生效
|
||||
|
||||
## 11. 常用命令备忘
|
||||
|
||||
### 11.1 启动网关
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\gateway -config .\config\tunnel-dev.json
|
||||
```
|
||||
|
||||
### 11.2 启动老模拟器
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini
|
||||
npm run mock-gps-sim
|
||||
```
|
||||
|
||||
### 11.3 编译检查
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go build ./...
|
||||
```
|
||||
|
||||
### 11.4 直接发 GPS
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-producer -device-id child-001 -topic telemetry.location -count 5
|
||||
```
|
||||
|
||||
### 11.5 直接发心率
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-producer -device-id child-001 -topic telemetry.heart_rate -bpm 148 -count 5
|
||||
```
|
||||
|
||||
### 11.6 channel 模式下的 producer
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-producer -channel-id ch-xxxx -token <producer-token> -topic telemetry.location -count 5
|
||||
```
|
||||
|
||||
### 11.7 channel 模式下的 consumer
|
||||
|
||||
```powershell
|
||||
cd D:\dev\cmr-mini\realtime-gateway
|
||||
go run .\cmd\mock-consumer -channel-id ch-xxxx -token <consumer-token> -topics telemetry.location,telemetry.heart_rate
|
||||
```
|
||||
|
||||
## 12. 常见问题
|
||||
|
||||
### 12.1 老模拟器显示 `authentication failed`
|
||||
|
||||
通常是这两种情况:
|
||||
|
||||
- 你填了 `producerToken`,但没填 `channelId`
|
||||
- 你填的是 channel 的 token,却还在走老的 `authenticate`
|
||||
|
||||
处理方式:
|
||||
|
||||
- 如果用 channel:
|
||||
- 必须同时填 `Channel ID` 和对应 `producerToken`
|
||||
- 如果不用 channel:
|
||||
- `Channel ID` 留空
|
||||
- token 使用全局 `dev-producer-token`
|
||||
|
||||
### 12.2 模拟器还连到 8080
|
||||
|
||||
当前开发统一使用 `18080`。
|
||||
|
||||
如果页面还显示旧地址:
|
||||
|
||||
- 重启模拟器服务
|
||||
- 浏览器强刷 `Ctrl + F5`
|
||||
|
||||
### 12.3 管理台创建 channel 成功,但页面没显示
|
||||
|
||||
这通常是浏览器缓存旧前端资源。
|
||||
|
||||
处理方式:
|
||||
|
||||
- 重启网关
|
||||
- 打开 `http://127.0.0.1:18080/`
|
||||
- 按一次 `Ctrl + F5`
|
||||
|
||||
### 12.4 管理台看不到实时数据
|
||||
|
||||
先检查:
|
||||
|
||||
- 网关是否启动在 `18080`
|
||||
- 老模拟器桥接是否已认证
|
||||
- 管理台实时窗口是否误填了 `channelId / deviceId` 过滤
|
||||
|
||||
## 13. 当前建议
|
||||
|
||||
今天这个阶段,最稳的开发方式是:
|
||||
|
||||
- 老模拟器继续做主生产者
|
||||
- 新网关继续做中转和观测
|
||||
- 手机端暂时不接正式消费链路
|
||||
- 先把网关本身的运行态、流量、实时查看能力做稳
|
||||
|
||||
这也是当前最省风险的组合。
|
||||
Reference in New Issue
Block a user