本文目录导读:

- 方案一:基于 WebSocket + OT 算法(推荐,最接近Google Docs)
- 方案二:基于 WebSocket + CRDT(推荐,但复杂性高)
- 方案三:基于差分同步 + 锁(极简,但不推荐)
- 推荐方案总结(给PHP项目)
- 对于80%的PHP项目,最优路径是:
用PHP实现协同编辑是一个比较有挑战性的需求,因为PHP本身是同步、短连接的脚本语言,而协同编辑需要实时同步、冲突处理和状态管理。
不可能仅靠纯PHP+MySQL的同步请求实现真正的多人同时编辑(类似Google Docs),你需要结合WebSocket、OT(操作转换)或CRDT(无冲突复制数据类型)算法,以及前端编辑器。
以下是几种实现方案,按复杂度和推荐度排序:
基于 WebSocket + OT 算法(推荐,最接近Google Docs)
这是最标准的协同编辑架构,PHP通常作为后端API服务器,而WebSocket服务器需要用支持长连接的语言(如Node.js、Go)或者Swoole。
技术栈:
- 前端编辑器:
Slate.js、Quill.js、ProseMirror(支持自定义协同)。 - 实时通信:
WebSocket(推荐Swoole WebSocket Server或Ratchet,但Swoole性能更好,更现代)。 - 算法:
OT(操作转换, 如sharedb,ot.js的PHP移植版)。 - 同步层:
ShareJS(Node.js) 或自建OT引擎。 - PHP后端:处理鉴权、保存文档快照、历史版本、文件上传等。
实现步骤:
- PHP 提供文档内容与鉴权:用户打开编辑页时,PHP从数据库取出文档JSON,渲染到编辑器。
- 建立WebSocket连接:PHP生成一个Token,前端用这个Token连接PHP的WebSocket服务器,PHP验证Token并加入房间(Room)。
- 操作同步(OT核心):
- 用户A在编辑器里输入一个字符,编辑器生成一个操作对象(
{type: "insert", pos: 5, content: "a"})。 - 前端通过WebSocket发送这个操作。
- PHP WebSocket服务器收到操作后,需要转换(Transform)这个操作,使其相对于其他用户正在做的操作得到正确处理(比如B在位置3删除了字符,A的插入位置5需要变成位置4)。
- 如果PHP层面实现OT比较慢,通常做法是PHP只负责转发和持久化,OT引擎运行在Node.js上,或者PHP通过Redis Pub/Sub调用另一个服务。
- 用户A在编辑器里输入一个字符,编辑器生成一个操作对象(
- 广播:服务器将转换后的操作广播给房间内其他用户。
- 应用与保存:所有客户端应用操作,PHP服务端每5秒或操作积压时,将当前文档状态快照存回MySQL。
为什么PHP做OT引擎不太常见? PHP处理高并发、长连接(WebSocket)不是强项。目前生产环境下,协同编辑的Server端极少用纯PHP,除非用Swoole,Swoole可以让PHP像Node.js一样常驻内存,高效处理WebSocket和OT运算。
示例(Swoole WebSocket Server + OT思想):
// server.php (需要Swoole扩展)
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
// 假设你有一个简单的OT类
require_once 'OT.php';
$server = new Server("0.0.0.0", 9501);
$server->on('open', function (Server $server, $request) {
echo "connection open: {$request->fd}\n";
// 加入房间逻辑
$server->table->set($request->fd, ['room' => 'doc_123', 'cursor' => 0]);
});
$server->on('message', function (Server $server, $frame) {
$data = json_decode($frame->data, true);
$operation = $data['op']; // 用户的操作
$docId = $data['docId'];
// 1. 从Redis或内存获取当前文档状态
$currentDoc = getDocumentFromRedis($docId);
// 2. 使用OT算法将新操作应用到文档
$transformedOp = OT::transform($operation, getPendingOps($docId));
$newDoc = OT::apply($currentDoc, $transformedOp);
// 3. 存储新文档和操作
saveDocumentToRedis($docId, $newDoc);
addPendingOp($docId, $transformedOp);
// 4. 广播给房间内其他客户端(不包括自己)
foreach ($server->connections as $fd) {
if ($fd != $frame->fd && inRoom($fd, $docId)) {
$server->push($fd, json_encode(['op' => $transformedOp]));
}
}
});
$server->start();
基于 WebSocket + CRDT(推荐,但复杂性高)
CRDT(如 Yjs)比 OT 更适合现代编译器和分布式系统,它允许离线编辑后合并,对网络延迟容忍度更高。
技术栈:
- 前端编辑器:
Yjs(非常成熟的CRDT库)。 - 后端Sync Server:
y-websocket(Node.js) 或y-socket.io。 - PHP的角色:数据持久化层。
- PHP提供REST API:
GET /api/documents/:id(返回Yjs的二进制状态)。 - 当WebSocket服务器收到更新时,调用PHP的Webhook保存状态。
- 或者PHP直接读取Redis/MongoDB中的Yjs文档状态。
- PHP提供REST API:
实现流程:
- 前端使用
Yjs+Quill/ProseMirror。 - Yjs自动处理冲突(不需要手动OT)。
- Yjs通过WebSocket连接到一个协同服务器(可以是Node.js的
y-websocket,也可以是自己用PHP + Swoole实现的一个Yjs协议转发器)。 - PHP后端只需要在用户打开文档时,将Yjs的硬盘快照(Uint8Array)返回给客户端;在文档关闭或每隔一段时间,从Redis/MongoDB中取回当前状态并存入MySQL。
PHP代码示例(仅持久化层):
// 保存Yjs更新 (WebSocket server调用此API)
$requestPayload = file_get_contents('php://input'); // Yjs的二进制更新
$docId = $_GET['id'];
// 将更新追加存储到MongoDB或Redis (Yjs的文档由片段组成)
$collection->updateOne(
['_id' => $docId],
['$push' => ['updates' => new MongoDB\BSON\Binary($requestPayload, MongoDB\BSON\Binary::TYPE_GENERIC)]]
);
echo "ok";
注意:CRDT+Yjs方案中,PHP的角色非常轻,协同计算几乎全部放在前端和Node.js服务器上,PHP只负责存和读。
基于差分同步 + 锁(极简,但不推荐)
如果不追求实时,或者只是顺序编辑(一个人编辑时另一个人不能动),可以用悲观锁。
实现:
- 用户打开编辑页面时,PHP在数据库里给文档加一个
editing_lock。 - 前端每隔5秒发送一次
heartbeat,PHP更新last_heartbeat。 - 其他用户打开编辑器时,PHP检测到锁存在,强制只读,或者显示“张三正在编辑”。
- 用户保存时,上传整个文档内容,PHP获取锁并写入MySQL。
缺点: 体验非常差,完全不是“协同”,而是“轮流编辑”。
推荐方案总结(给PHP项目)
| 方案 | 难度 | 实时性 | 冲突处理 | 适合场景 | 服务器要求 |
|---|---|---|---|---|---|
| OT + Swoole | ⭐⭐⭐⭐ | 高 | 好(需自己实现OT) | 自研协同编辑,技术团队熟悉PHP | PHP + Swoole扩展 |
| CRDT (Yjs) + Node.js | ⭐⭐⭐ | 高 | 极好(Yjs处理) | 推荐:PHP做持久化,Node.js做协同服务 | PHP + Node.js |
| 差分同步+锁 | ⭐ | 低 | 差 | 极低需求、非实时协同 | 纯PHP即可 |
对于80%的PHP项目,最优路径是:
- 前端:使用 Yjs + Quill (文本) 或 Slate (富文本)。
- 协同服务器:购买或自己搭一个Node.js的y-websocket(这个服务器非常轻量,可以单独部署)。
- PHP后端:当用户打开文档时,从MySQL读取文档初始数据,调用Node.js协同服务器的API“加入文档”,当用户手动点保存时,或者PHP定时任务从Node.js拉取最终状态写入MySQL。
这样,PHP就专注于它擅长的:鉴权、业务逻辑、REST API、数据库持久化,而协同编辑的复杂实时逻辑由专门的工具(Yjs + Node.js)处理。