beego 无法直接实现协同编辑,因其缺乏状态同步和操作变换(ot/crdt)能力;需基于 websocket 自建状态服务,集成 ot 库处理冲突,并用 redis 或内存 map 管理实时状态。

Beego 本身不内置实时协同编辑能力,要实现在线协同文档(如类似 Google Docs 的多用户实时编辑),后端必须补足「状态同步」和「操作变换(OT)或 CRDT」逻辑——Beego 只负责 HTTP 路由、WebSocket 接入、数据持久化等基础设施,核心协同算法得自己写或集成第三方库。
为什么不能只靠 beego.Controller 处理协同编辑
协同编辑不是普通 CRUD:多个客户端同时修改同一份文档时,直接用 c.Ctx.WriteString 或简单保存 JSON 会导致冲突覆盖。Beego 的 Controller 是无状态的、每次请求新建实例,无法维持文档实时状态或广播变更。
常见错误现象包括:
- 用户 A 输入 “hello”,用户 B 同时输入 “world”,最终只存下其中一端内容
- WebSocket 连接断开后重连,丢失中间操作,光靠数据库快照无法还原编辑历史
- 用
beego.Session存文档状态——Session 不共享、不跨进程、不支持并发读写,会立刻出错
必须用 WebSocket + 独立状态服务
Beego 支持 WebSocket,但仅提供连接建立和消息收发基础能力,文档状态必须交由外部服务管理。推荐两种轻量落地方式:
- 用
gorilla/websocket替代 Beego 内置 WebSocket(更稳定、文档更全),在controllers中单独起一个 handler 函数处理 ws 升级,避免被 Beego 中间件干扰 - 文档状态用内存 map +
sync.RWMutex管理(适合单机小规模),key 是文档 ID,value 是当前内容 + 操作队列;不要塞进 Beego 的AppConfig或全局变量,否则热重载(bee run)会清空状态 - 若需水平扩展,必须引入 Redis + PUB/SUB,每个 Beego 实例订阅同一个 channel,用 Lua 脚本保证操作原子性;此时
beego.Cache不能替代 Redis,它只是本地缓存,不同实例间不互通
操作合并必须自己实现 OT 或用现成库
前端发来的每次 keystroke 或 selection change 都是独立操作(insert/delete/retain),Beego 不提供 OT 引擎。你得在 controller 的 WebSocket handler 里做三件事:
- 接收操作前,先从存储中拉取该文档的最新版本号(version stamp)和内容快照
- 调用 OT 库(如
github.com/ihcsim/ot-go)将新操作 transform 到当前版本上,再应用;失败则返回 conflict 响应,让前端触发 rebase - 成功后,把操作写入 MySQL/PostgreSQL 的
operations表(含 doc_id、op_json、version、timestamp),并广播给其他连接的 client
别试图用 Beego ORM 的 Insert 直接存整个文档字符串——这会丢失操作粒度,无法做 undo/redo 或权限级编辑控制(比如只允许改某段落)。
Beego 配置和路由的关键调整点
默认 app.conf 的 EnableDocs = true 和 Swagger 自动生成会暴露内部接口细节,协同编辑的 WebSocket 地址(如 /ws/doc/:id)不应出现在文档里,需手动关闭:
- 在
conf/app.conf中设EnableDocs = false,避免docs/doc.go被 bee 工具扫描生成公开 API 页面 - WebSocket 路由不能走
beego.Router的 RESTful 注册方式,要用beego.Handler显式挂载:beego.Handler("/ws/doc/:id", &WebSocketHandler{}),否则参数解析可能出错 - 务必在
main.go的beego.BeeApp.Run()前调用beego.InsertFilter加身份校验 filter,因为 WebSocket upgrade 请求不经过 Controller 的Prepare方法
最易被忽略的是:WebSocket 连接生命周期与 HTTP 请求完全不同,defer 在 handler 函数退出时就执行了,但连接可能还活着——清理资源(如从 map 删除 client)必须放在 conn.Close() 回调或心跳超时检测里,而不是靠 Beego 的请求上下文自动释放。











