← 全部文章

技术笔记 · 2026-02-26

断线重连后,怎样补消息且不丢草稿

只讨论重连后的两件事:用已确认序号请求补推,避免消息空洞;用快照保存输入草稿,避免会话列表预览把未发送内容覆盖掉。

  • WebSocket
  • 消息补推
  • 草稿
  • 断线重连

关联案例:企业沟通与售后协作平台。文章依据本地 Socket 重连与会话草稿逻辑整理;示例接口字段已泛化,短重连阈值与缓存键名仅作说明。本文不声称消息在任何网络切换下都零丢失。

重连成功不等于消息已对齐

聊天里 Socket 断开再连上很常见:切网、休眠、网关重启。客户端若只把“连接状态”改成在线,界面可能已经缺了一段消息;用户看到的是空洞,而不是“正在补齐”。

需要分开回答两个问题:

  1. 消息空洞:从哪一条之后开始补?
  2. 输入草稿:断线期间用户敲的字,以谁为准?

用已确认序号表达“我收到哪了”

本地做法是为会话维护 last_ack_seq:客户端处理并确认消息后更新;重连成功时带上该序号请求服务端补推后续消息。

在线收消息 ─→ 更新 last_ack_seq ─→ 断开

重连成功 ─→ 对每个已记录会话请求 replay(last_ack_seq)

                         服务端补推后续 ─→ 再更新 ack

两个容易做错的点:

  • 不能只补当前选中会话。后台会话同样可能缺消息,只补激活会话会让列表预览继续错位。
  • 短重连与长断开要分流。很短的断开适合立即 replay;更长的离线往往应走历史分页或全量对齐,而不是无界补推。

本地实现用重连耗时阈值区分:低于阈值才触发按 last_ack_seq 的补推,更长的断开交给上层错误/刷新路径。阈值是产品与协议的折中,不是通用常数。

草稿不能写在“列表预览字符串”上就结束

会话列表常显示 send_content 作为预览。若把草稿直接覆盖进去,会遇到:

  • 清空输入时无法还原真正的上一条消息;
  • 多次编辑后丢失“草稿前的原始内容”;
  • 图片等富文本直接进列表字段,预览语义被破坏。

本地实现把草稿当成带前缀的展示态,并保留一份原始快照:

// 示意:列表字段同时承载“原消息”与“草稿展示”
const DRAFT_PREFIX = '[草稿]'

// 记录草稿时
item.send_old_content = wasDraft ? item.send_old_content : item.send_content
item.send_content = DRAFT_PREFIX + draftText

// 清空输入时
item.send_content = wasDraft ? (item.send_old_content || '') : item.send_content

要点:

  • send_old_content 只在首次进入草稿时写入,避免草稿叠草稿把快照冲掉;
  • 空内容或仅 <br> 视为“清空草稿”,走还原分支;
  • 当前正在编辑的会话不把草稿前缀写回预览,避免输入框与列表互相干扰。

补消息与草稿可以并行,但都要以会话为边界

replay 按会话维度请求;草稿也按会话键缓存。身份切换或强制重连时,应先销毁旧连接,避免不同账号复用同一条 Socket,把 ack 与草稿都写到错误的人身上。

重连成功
  ├─ replay: 对每个 clientInstance 的 last_ack_seq 补推
  └─ drafts: 输入框恢复当前会话缓存;列表只展示草稿态预览

验证建议

至少覆盖:断开 2 秒内重连(应补推)、断开较久(应走历史对齐)、编辑草稿后切会话再切回、清空输入还原原消息、强制换身份重连。断言不只看“Socket 已连接”,还要看消息序号是否连续、草稿前缀是否只出现在非激活会话的预览里。

把“补空洞”和“保草稿”拆成会话级的两条恢复路径,重连才不会变成另一种形式的数据错乱。