前端必须实现操作队列、版本校验与本地暂存,配合服务端OT/CRDT协同,否则多人编辑必丢数据或覆盖错误——WebSocket只传不管对错。

前端不做冲突处理,就一定会丢数据或覆盖错内容——WebSocket只管传,不管对错。
为什么直接用 onmessage 覆盖全文会出问题
很多快速实现方案(比如 Flask + Vue 简单广播)让每个编辑操作都把整个文档内容发过来,前端收到就 editor.setValue(newContent)。这在单人编辑时没问题,但两人同时改:A删了第3行,B在第2行末尾加了字,谁后到谁赢,先到的操作被彻底抹掉。
- 浏览器没有“操作队列”概念,
setValue是原子覆盖,不保留任何上下文 - 网络延迟导致消息乱序,后发的操作可能先到,前端无法判断该不该应用
- 用户本地光标位置、选区状态全丢,强行覆盖后体验断裂
insertText 和 deleteText 必须带位置与版本校验
真正可落地的前端同步,得把“用户做了什么”拆成最小可逆操作,并附带执行前提。不能只传结果,要传动作 + 上下文。
- 服务端推送的消息必须含
op字段,如{"type":"insert","index":42,"text":"OK","version":12}或{"type":"delete","index":42,"length":2,"version":12} - 前端收到后,先比对本地
version:若msg.version ,直接丢弃(已过期);若 >,再检查 <code>index是否仍在有效范围内(防因前面操作导致偏移错位) - 编辑器需支持底层操作调用,比如 CodeMirror 6 的
view.dispatch(),Monaco 的executeEdits(),而不是走setValue()这种高层封装
前端必须维护本地操作暂存与回滚能力
用户正在输入时 WebSocket 断连,重连后不能只靠服务端补发——那些未发出的本地操作必须还在,且能和补发操作合并重放。
立即学习“前端免费学习笔记(深入)”;
- 每次键盘输入触发的操作,先存进一个
pendingOps队列,同时打上本地时间戳和随机clientId - 连接断开时,暂停发送,但允许用户继续编辑;重连后,先发
{"type":"sync:pending","ops":pendingOps}给服务端做 OT 合并,再接收服务端返回的权威状态 - 服务端返回的同步消息里如果含
rebase:true,说明你的本地操作已被转换,前端要按新index重算并应用,不能硬套原位置
光靠前端永远不够:必须和服务端 OT/CRDT 协同
前端所有“校验”“暂存”“重算”都只是防御性措施,最终一致性锚点永远在服务端。你看到的 version、rebase、clientId 全是服务端生成和裁定的。
- 不要自己实现 OT 变换函数——Java 后端用
sharedb或ottypes,Node.js 用ot-json0,它们输出的op结构前端才能安全消费 - 前端收到的每条消息,都应有服务端签发的
serverTimestamp,用于和本地操作排序,而非依赖Date.now() - 最易忽略的一点:前端必须监听
beforeunload,把未提交的pendingOps存 localStorage,否则刷新页面就真丢了


















