docs: add Baidu quote WebSocket real-time design spec

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dongzp 2026-07-22 09:51:44 +08:00
parent 7366bf66fa
commit d55877e8c7

View File

@ -0,0 +1,146 @@
# 百度财经 WebSocket 行情实时刷新 — 设计
**日期:** 2026-07-22
**状态:** 已口头确认,待书面复核
---
## 1. 背景与目标
当前行情通过 HTTP `getStockQuotation` 在页面打开时拉取一次全量(分时、盘口、成交明细)。`quotation.ts` 已预留「实时更新后续对接 WebSocket」。
本阶段目标:接入百度财经 WebSocket,在 HTTP 全量底之上做增量刷新,使报价、盘口、分时图、分时成交与大单分析实时更新。
---
## 2. 已确认决策
| 项 | 选择 |
|---|---|
| 初始全量 | 现有 HTTP `getStockQuotation`,成功后再连 WS |
| 数据映射 | `tick` → 成交明细(TradeTape)+ 大单分析;`snapshot` → 报价 / 盘口 / 分时点 |
| 架构 | 独立 `BaiduQuoteWs` 客户端 + mapper;状态仍落 `quota` store |
| 合约 | 仍读 `contractConfig` 单合约,不做多合约订阅 |
| WS 库 | 原生 `WebSocket`,不引入第三方 |
| 连接 UI | 本期不做连接指示灯 / 手动重连按钮 |
---
## 3. 架构与数据流
```
页面挂载
→ quota.fetchQuote() // HTTP 全量
→ BaiduQuoteWs.connect() // wss://finance-ws.pae.baidu.com/
→ subscribe tick + snapshot
→ 每 6s ping
→ 每 60s patch snapshot
→ onMessage
→ product=tick → 合并 trades(最新在前)
→ product=snapshot → 更新报价 / 盘口 / upsert intraday
页面卸载 / 主动 refresh
→ BaiduQuoteWs.disconnect()(refresh 成功后再 connect)
```
### 模块划分
| 文件 | 职责 |
|------|------|
| `src/api/baidu/ws.ts` | 连接、心跳、订阅、重连、回调 |
| `src/api/baidu/wsTypes.ts` | tick / snapshot 消息 TypeScript 类型 |
| `src/api/baidu/mapWsQuote.ts` | 增量消息映射并合并进 `QuoteData` |
| `src/stores/quota.ts` | HTTP 成功后启 WS;应用增量;卸载断开 |
| `src/composables/useQuote.ts` | `onMounted` 拉行情;`onUnmounted` 断开 WS |
大单分析已基于 `quote.trades` 计算,只需更新 `trades`,不改组件。
---
## 4. 协议细节
**端点:** `wss://finance-ws.pae.baidu.com/`
**订阅(open 后立即发送两条):**
```json
{"method":"subscribe","source":"pc-web","product":"tick","items":[{"code":"FG609","name":"玻璃2609","market":"ab","financeType":"futures"}]}
{"method":"subscribe","source":"pc-web","product":"snapshot","items":[{"code":"FG609","name":"玻璃2609","market":"ab","financeType":"futures"}]}
```
`code` / `name` 来自 `contractConfig`;`market` 固定 `ab`;`financeType` 固定 `futures`。
**保活:**
- 每 6s:`{"method":"ping","source":"pc-web"}`
- 每 60s:snapshot 的 `patch`(items 同订阅)
**重连:** 断线后指数退避(1s → 2s → 4s … 上限 30s);重连成功后重新 subscribe;不自动重拉 HTTP。
**disconnect:** 清 ping/patch 定时器、关闭 socket、停止重连调度。
---
## 5. 合并规则
### 5.1 tick → trades
- 解析 `data.detailinfos` → `TradeTick[]`(字段映射与现有 HTTP `parseTrades` 一致:`formatTime` / `price` / `volume` / `bsFlag`;最新在前)
- 前置合并到 `quote.trades`
- 去重键:`time + price + volume + side`
- 上限:最近 **200** 条
### 5.2 snapshot → 报价 / 盘口 / 分时
- `cur`:更新 `last` / `change` / `changePercent` / `avg`;状态可从 `cur.status` 或 `update` 取
- `pankouinfos`(数组,按 `ename`):更新 open / high / low / volume / amount / openInterest / amplitude / settlement / prevSettlement / outerVol / innerVol 等
- `askinfos` / `buyinfos`:刷新五档;过滤无效价;重算 `buyRatio` / `sellRatio`
- `update`:刷新 `updatedAt` / `status`
- `point`:按分钟时间(从 `point.time` 取 `HH:mm`)upsert 到 `intraday`
- 覆盖 / 写入:`price` ← `point.price`,`avg` ← `point.avgPrice`
- `volume`:WS 只给 `totalVolume`(全日累计)。分钟成交量 = `max(0, totalVolume - 此前各分钟 volume 之和)`;同分钟再次推送时用同一公式重算并覆盖
- 新分钟:append;同分钟:覆盖该点
### 5.3 错误处理
- `resultCode !== "0"`:打日志,忽略本条
- JSON / 字段解析失败:打日志,不中断连接
- 组件层无感;不暴露 WS 连接状态到 UI
---
## 6. 生命周期
1. `useQuote` `onMounted` → `store.fetchQuote()`
2. `fetchQuote` 成功 → `store.connectWs()`(若已连接则先 disconnect 再 connect)
3. `useQuote` `onUnmounted` → `store.disconnectWs()`
4. `refresh()` / `fetchForAnalysis()`:仍走 HTTP 全量;成功后重连 WS,用最新全量覆盖后再接增量
5. K 线逻辑不变;WS 不修改 `candles`
---
## 7. 明确不做(本期)
- 连接状态 UI / 手动重连按钮
- 多合约同时订阅
- 修改 TradeTape / LargeOrderAnalysis / ChartPanel(仅消费响应式 `quote`)
- 引入第三方 WS 库
- WS 失败时回退 HTTP 轮询
---
## 8. 验证要点
- HTTP 成功后会发送 tick + snapshot 两条 subscribe
- 约每 6s 有 ping,约每 60s 有 snapshot patch
- tick 合并 trades:去重生效、长度 ≤ 200;TradeTape / 大单分析随之刷新
- snapshot.point:同分钟覆盖、新分钟追加;分时图更新
- snapshot 盘口 / 现价字段正确刷新
- 页面卸载后无残留 timer / 未关闭的 socket
- 手动 refresh 后 WS 会断开并重新订阅
---
## 9. 风险与注意
- WS 与 HTTP 的 snapshot 字段结构不完全一致(HTTP 用 `origin_pankou`,WS 用 `pankouinfos` 数组);mapper 需独立,勿强行复用 HTTP mapper 全文
- 去重键不含服务端唯一 id(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
- 跨域 / 鉴权:浏览器直连百度 WS;若环境拦截再评估代理,本期先直连