ai_trade_assistance/docs/superpowers/specs/2026-07-22-baidu-quote-ws-design.md
dongzp 1c1f370d59 fix(ws): proxy Baidu WebSocket via Vite to bypass Origin 403
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 10:01:23 +08:00

6.0 KiB
Raw Blame History

百度财经 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()      // ws(s)://{host}/finance-ws/ → Vite/Nginx 代理到百度
      → 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. 协议细节

端点: 浏览器连同域 /finance-ws/;由 Vite(开发)或 Nginx(生产)代理到 wss://finance-ws.pae.baidu.com/,并改写 Origin/Referer 为 https://gushitong.baidu.com(否则 localhost Origin 会 403)。

订阅(open 后立即发送两条):

{"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(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
  • 跨域 / 鉴权:浏览器不能直连(Origin=localhost → 403)。开发经 Vite 代理 /finance-ws,并把 Origin/Referer 改写为 https://gushitong.baidu.com;生产需同等反向代理。