Webman需通过WebSocket+OT/CRDT实现协作脑图,因HTTP无状态无法满足低延迟、保序与冲突解决;须独立ws进程、token鉴权、原子操作序列化、Redis有序队列、批量广播及前端节流渲染。

Webman 本身不直接支持实时协作脑图,必须自己搭 WebSocket + 操作同步逻辑,否则节点冲突、光标错位、历史丢失全是常态。
为什么不能直接用 Webman 的 HTTP 路由做协作
Webman 是高性能 PHP Swoole 框架,适合处理大量并发请求,但它默认的 HTTP 请求是无状态、单向的。协作脑图要求:多个用户同时编辑同一张图、操作秒级广播、冲突时保留语义(比如两个用户同时改同一个节点标题)。HTTP 轮询或长连接无法满足低延迟与操作保序——你看到别人删了节点,其实他刚点下回车,你本地还没收到;你双击编辑,对方正在拖动父节点,结果你的修改被覆盖。
- WebSocket 是唯一可行通道:必须用
Webman\WebSocket\Server启一个独立 ws 进程,绑定到/ws路径 - 不能复用 HTTP Session:每个 ws 连接需独立鉴权,建议在握手阶段校验
token并关联user_id和map_id - 操作必须序列化为 OT(Operational Transformation)或 CRDT 格式,不能传原始 DOM 或 JSON 全量 diff——否则网络抖动时极易错乱
关键数据结构:怎么定义一次“可合并”的编辑操作
协作不是把 JSON.stringify(mapData) 推来推去,而是把用户行为拆成原子操作。例如双击改节点文本,应生成:{type: 'update_node', id: 'n123', field: 'text', value: '新标题', version: 15}。这个 version 不是时间戳,而是基于当前客户端已知的最新操作序号自增。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 服务端收到后,先按
map_id分组进内存队列,再用Redis ZSET做全局操作排序(score = version),避免多 worker 进程间顺序错乱 - 每个操作必须带
client_id,用于广播时过滤“自己发的操作”,否则会触发本地重复渲染 - 删除节点不能只发
{type: 'delete', id: 'n123'},要附带parent_id和index_in_parent,否则对方重排子节点时位置错位
性能瓶颈常出在哪儿
很多人测压时发现 50 人在线就卡顿,问题往往不在 WebSocket 连接数,而在服务端对操作的广播策略和前端渲染节奏。
- 别用
foreach($clients as $client) $client->send(...):Webman 的ConnectionInterface::send()是同步阻塞调用,100 个客户端就要串行发 100 次。改用$server->broadcast()批量投递 - 前端收到操作流后,别每条都
render():应攒批(如 16ms 内最多合并 3 条)、跳过非可视区域变更、对频繁更新的节点加debounce(比如输入框每 300ms 合并一次update_node) - Redis 不要存整张图:只存操作日志(
map:123:ops)、当前快照哈希(map:123:snapshot_hash)、最后 100 条操作缓存(map:123:recent_ops)。全量图数据仍走 MySQL/PostgreSQL,定期快照备份
导出与兼容性绕不开的坑
协作归协作,最终要导出 PNG/PDF/Markdown,而这些格式依赖服务端渲染或前端 canvas 截图——但协作过程中 canvas 可能正被多人高频重绘,截图极易截到半更新状态。
- 导出请求必须走独立 HTTP 接口(如
POST /api/map/123/export),服务端拉取当前快照+最新操作日志,离线合成一张干净图,再返回下载链接 - 不要让前端
toDataURL():移动端 canvas 渲染精度低,缩放后文字糊成一片;Safari 对foreignObject支持差,含 SVG 图标的导图会空白 - Markdown 导出不是简单遍历 JSON:要识别
node.type === 'note'插入引用块,node.icon === 'link'自动转成[描述](url),否则协作中加的语义信息全丢
真正难的不是连上 WebSocket,而是让两个人同时拖拽不同分支时,松手瞬间的坐标、层级、折叠状态全部对齐。这需要前后端对“操作语义”有完全一致的理解,而不是靠试错堆 patch。


















