协同编辑文档的实时同步需采用统一状态模型、增量操作广播、客户端本地优先与冲突可收敛机制,通过带id和时间戳的结构化操作(如insert/delete/format)实现ot或crdt合并,配合websocket长连接、防抖提交、断线重连与服务端轻量状态管理。

用 WebSocket 实现协同编辑文档的实时状态同步,核心在于:**统一状态模型 + 增量操作广播 + 客户端本地优先 + 冲突可收敛**。不是简单地把整个文档内容反复发来发去,而是传递“发生了什么变化”,再由各端基于一致规则合并更新。
设计轻量、可序列化的操作(OT 或 CRDT)
直接同步 DOM 或富文本 HTML 极易出错且难以比对。推荐使用结构化操作(Operation),例如:
-
插入文字:
{ type: 'insert', pos: 12, text: 'hello' } -
删除字符:
{ type: 'delete', pos: 5, len: 3 } -
格式变更(如加粗):
{ type: 'format', from: 8, to: 15, attr: { bold: true } }
所有操作必须带客户端唯一 ID(如 clientId)和逻辑时间戳(如 seq: 1),便于排序与去重。若追求强一致性,可用 OT(Operational Transformation);若希望离线友好、无中心协调,可选 CRDT(如 yjs 底层的 Yjs 类型)。
WebSocket 连接与消息协议约定
服务端用 Node.js(ws 库)或 NestJS 等建立长连接,客户端用原生 WebSocket:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 连接建立后,客户端立即发送
{ type: 'join', docId: 'xxx', clientId: 'user-abc' }加入文档房间 - 服务端维护房间内所有客户端连接,并广播操作(不回传给发送者):
{ type: 'op', op: {...}, timestamp: Date.now() } - 客户端收到操作后,不直接应用,先存入本地操作队列,按
timestamp+clientId排序后依次 transform & apply(OT 场景)或 merge(CRDT 场景)
客户端本地编辑与防抖同步
用户每输入一个字就发一次操作?太频繁。应做合理节流:
- 监听
input或compositionend事件,收集连续编辑为一个操作批次 - 用
setTimeout防抖(如 200ms 内无新输入则提交) - 每次发送前,检查该操作是否已被服务端确认(通过服务端返回的
ack(seq)),避免重复提交 - 断网时缓存未确认的操作,恢复连接后重发(需服务端支持幂等处理)
服务端需做简单但关键的状态管理
WebSocket 本身无状态,服务端要负责:
- 按
docId分房间,用 Map 存储活跃连接与 clientID 映射 - 记录每个客户端最后收到的
seq,用于断线重连时补发丢失操作(可配合 Redis 持久化最近 N 条操作) - 不存储完整文档——只转发操作;真正文档状态由客户端自己维护(符合协同编辑“最终一致”原则)
- 拒绝非法操作(如
pos超出当前长度),返回错误消息供前端提示
不复杂但容易忽略。关键不在 WebSocket 多难,而在操作定义是否干净、广播是否有序、本地合并是否可靠。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










