TREK WebSocket协议设计:消息类型、心跳保活与断线重连策略全解析
2026/9/15 18:55:09 网站建设 项目流程

TREK WebSocket协议设计:消息类型、心跳保活与断线重连策略全解析

【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK

🧭TREK是一款可自托管的旅行行程规划工具,其实时协作能力的核心就是 WebSocket 协议:团队成员对同一行程的修改,通过一条持久化的 WebSocket 长连接在毫秒级同步到彼此屏幕上。本文带你完整拆解 TREK 的 WebSocket 协议设计,包括消息类型定义、30 秒心跳保活机制、指数退避断线重连策略,以及握手阶段的一次性令牌安全方案。

为什么旅行规划工具需要 WebSocket 实时协作

想象一个场景:你和队友正在同一份行程里分工——你在地图上加景点,队友在填预算、改待办。如果每次修改都要刷新页面,协作体验将一塌糊涂。

TREK 的解法是:每位用户登录后建立一条 WebSocket 长连接,加入所打开行程对应的"房间"(Room),服务端在该行程上发生的任何变更都会广播给房间内所有连接。相关实现分布在两个核心文件:

  • 服务端网关:server/src/websocket.ts
  • 客户端单例管理器:client/src/api/websocket.ts

上图:TREK 行程规划器界面,所有成员的实时变更都会通过 WebSocket 同步到这里

连接建立:一次性令牌与多层握手安全

WebSocket 握手发生在 URL 的 query 参数中,但 TREK 并不直接把你的登录 JWT 挂在 URL 上,而是多走一步安全设计(见 server/src/websocket.ts):

  1. 换取一次性令牌:客户端先调用POST /api/auth/ws-token接口,用 Cookie 会话换发一个短时效的ephemeral token(临时令牌),再连接到ws(s)://host/ws?token=...
  2. 令牌只能消费一次:服务端用consumeEphemeralTokenWithMeta(token, 'ws')消费令牌,防止重放
  3. 密码版本校验:令牌绑定签发时的password_version,一旦用户改过密码,旧令牌立即失效——这是防止会话劫持的纵深防御
  4. MFA 门禁:若站点启用了"强制 MFA"策略,未完成双因素认证的用户会被以 4403 关闭码拒绝
  5. Origin 白名单:可通过ALLOWED_ORIGINS环境变量限制允许跨域的源

服务端同时设置了64 KB 单条消息上限maxPayload),从协议层面杜绝超大消息攻击。

握手成功后,服务端立刻向客户端发送第一条消息:

{ "type": "welcome", "socketId": 7 }

socketId是本次连接的唯一编号,后续所有广播都会用它来排除发起变更的本人(客户端对本地操作已有乐观更新,无需回声)。

消息类型一览:客户端指令与服务端事件

整条协议基于统一的 JSON 消息格式:{ "type": "...", ...payload }。方向分为两类:

客户端 → 服务端:房间指令

消息载荷作用
jointripId加入某行程房间,服务端先做访问权限校验canAccessTrip),无权限则回error
leavetripId离开房间,服务端自动清理房间映射

服务端 → 客户端:握手确认与业务广播

消息说明
welcome握手完成,附带socketId
joined/left加入/离开房间的确认
error权限拒绝、限流等错误提示
领域事件place:createdday:updatedbudget:createdpacking:updatedtodo:deletedcollab:note:createdcollab:message:createdreservation:updated

领域事件遵循实体:动作的命名规范(例如place:createdpacking:bag-updated),由 NestJS 各业务控制器在 API 写操作落库后调用 broadcast() 发出。广播时有两个精妙细节:

  • 排除发送者excludeSid参数跳过触发变更的连接自身
  • 私有数据隔离onlyUserId参数可让某些事件(如私有行李物品 #858)只送达属主自己的多个标签页,不泄漏给其他协作者

上图:TREK 协作标签页——聊天、共享笔记与投票全部基于同一套 WebSocket 事件流驱动

心跳保活:30 秒 ping/pong 剔除死连接

WebSocket 连接可能"假死"(NAT 超时、移动端休眠后静默断开),服务端无法感知。TREK 采用经典的应用层心跳方案(见 server/src/websocket.ts):

  1. 30 秒HEARTBEAT_INTERVAL = 30000)服务端向所有连接发送一个ping
  2. 收到pong回包即标记isAlive = true
  3. 下一轮心跳时若某连接isAlive仍为false(即上一轮 ping 石沉大海),立即terminate()强制清理,并同步摘除其所在的所有房间

这套"两拍确认"策略(给客户端一轮回复窗口再判定死亡)既不会误杀弱网用户,又能及时回收僵尸连接,配合每连接10 秒 30 条的消息速率限制(WS_MSG_LIMIT = 30/WS_MSG_WINDOW = 10s),构成了完整的连接健康治理。

断线重连:指数退避 + 状态自动恢复

服务端只是链路的一半,客户端的容错设计才是体验关键。client/src/api/websocket.ts 实现了一个单例管理器,重连策略可以概括为三步:

① 指数退避,避免风暴

连接断开后从1 秒开始等待,每次失败延迟翻倍(reconnectDelay * 2),上限30 秒MAX_RECONNECT_DELAY)。成功重连后延迟重置为 1 秒。如果fetchWsToken返回 401(会话过期),则主动停止重连,交由登录流程处理——这是避免无效重试的关键判断。

② 自动重新加入房间

客户端用activeTrips集合记录当前打开的行程。重连成功(onopen)后,自动对每个活动行程重新发送join消息,用户无需任何手动操作即恢复实时状态。

③ 补发队列 + 全量回读,保证数据不错位

断线期间,本地修改被离线队列暂存。重连时执行一段精心排序的恢复流程:

  1. 先运行preReconnectHook——把断线期间排队的变更刷到服务端(await 等待落库)
  2. 再触发refetchCallback对活动行程全量回读,以服务端为准刷新本地状态

这一"先写后读"的顺序保证了离线期间的编辑不会被随后的全量回读覆盖,是 TREK 离线模式(PWA)能丝滑衔接在线协作的核心保障。

页面级订阅由 useTripWebSocket Hook 管理:进入行程页自动joinTrip,离开页面自动leaveTrip,做到房间订阅与路由生命周期对齐。

实时事件的落地:Zustand 更新 + IndexedDB 写穿

收到广播后,客户端事件流最终汇入 handleRemoteEvent,它做两件事:

  1. Zustand 不可变更新:按typeplace:createdday:updated等领域事件映射为对应状态切片(地点、日程、预算、行李、待办、预订)的增删改
  2. IndexedDB 写穿:同一事件同步写入本地离线库(Dexie),fire-and-forget 不阻塞 UI——这样离线打开 App 时也能看到队友最后一次协作成果

上图:协作投票功能——投票结果通过 collab:poll:voted 事件实时推送给房间内所有成员

小结:TREK WebSocket 协议的设计要点

  • 🔐握手安全:一次性临时令牌 + 密码版本绑定 + MFA 门禁 + Origin 白名单
  • 🏷️消息规范实体:动作命名 +socketId回声排除 + 按用户投递私有事件
  • 💓心跳保活:30 秒 ping/pong 两拍确认,及时剔除僵尸连接
  • 🔁断线重连:1s→30s 指数退避、自动重入房间、"先刷队列后全量回读"的数据一致性恢复
  • 📦离线写穿:每条远程事件同步落地 IndexedDB,无缝衔接 PWA 离线模式

这套协议没有引入任何重型实时框架,仅用 Nodews+ 浏览器原生 WebSocket API,就为自托管旅行规划工具提供了生产级的实时协作体验。如果你想动手研究,建议从 server/src/websocket.ts 与 client/src/api/websocket.ts 两个文件读起,再配合 wiki/Real-Time-Collaboration.md 官方文档对照理解。

【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询