🚀 多人协同编辑系统

技术方案设计文档 & API 接口开发文档

版本 v2.0 | 更新时间:2026 年 3 月

📋 1. 项目概述

文档说明

本文档详细描述了一个支持多人实时协同编辑的文档系统的完整技术方案和 API 接口规范。系统采用先进的 CRDT(Conflict-free Replicated Data Type)算法确保数据一致性,支持数百人同时在线编辑同一文档。

1.1 核心功能

1.2 技术指标

指标项 目标值 说明
并发用户数 ≥ 500 人/文档 单文档最大支持并发编辑人数
同步延迟 < 100ms P99 延迟,局域网环境
可用性 99.9% 系统月度可用性指标
数据一致性 最终一致性 所有客户端最终达到一致状态
消息吞吐量 ≥ 10,000 TPS 单节点每秒处理消息数

🏗️ 2. 系统架构设计

2.1 整体架构图

客户端层
(Web/Mobile)
⇄ WebSocket/HTTP
接入网关
(Load Balancer)
应用服务层
(Node.js/Go)
数据持久层
(MongoDB/Redis)

2.2 架构分层说明

🖥️ 客户端层

  • Web 端:React + TypeScript
  • 富文本编辑器:ProseMirror / TipTap
  • CRDT 引擎:Yjs
  • WebSocket 客户端
  • 本地缓存:IndexedDB

🌐 接入层

  • Nginx 负载均衡
  • WebSocket 连接管理
  • SSL/TLS 终止
  • 限流熔断
  • DDoS 防护

⚙️ 应用服务层

  • 协同编辑服务(Node.js)
  • 文档管理服务(Go)
  • 用户认证服务
  • 消息推送服务
  • 版本控制服务

💾 数据层

  • MongoDB:文档内容存储
  • Redis:会话缓存、在线状态
  • PostgreSQL:用户、权限元数据
  • MinIO/S3:附件存储
  • Kafka:操作日志队列

2.3 核心模块划分

模块名称 职责描述 技术实现
Connection Manager 管理 WebSocket 连接生命周期 ws/uWebSockets.js
Document Provider 提供文档 CRDT 数据同步 Yjs + y-websocket
Operation Handler 处理编辑操作的转换和广播 自定义 OT/CRDT 引擎
Presence Service 维护用户在线状态和光标位置 Redis Pub/Sub
Version Control 文档版本管理和快照 MongoDB GridFS
Auth Service 用户认证和权限校验 JWT + OAuth2

🛠️ 3. 技术选型

3.1 前端技术栈

技术领域 选型方案 选型理由
框架 React 18 + TypeScript 组件化开发、类型安全、生态丰富
编辑器内核 ProseMirror / TipTap 可定制性强、支持协同编辑、性能优秀
CRDT 库 Yjs v13+ 高性能、小体积、完善的协同原语
状态管理 Zustand / Jotai 轻量级、响应式、TypeScript 友好
网络通信 WebSocket API + Axios 实时双向通信 + RESTful API
本地存储 IndexedDB + LocalStorage 大容量离线存储 + 配置缓存

3.2 后端技术栈

技术领域 选型方案 选型理由
主要语言 Node.js 20 LTS + Go 1.21 高并发 I/O + 高性能计算
WebSocket 服务 uWebSockets.js 业界最高性能 WebSocket 库
API 框架 Express / Fastify + Gin 轻量快速、中间件丰富
CRDT 服务端 Yjs + y-websocket 与前端同构、简化开发
消息队列 Apache Kafka 高吞吐、持久化、流处理
缓存 Redis 7 Cluster 高性能、数据结构丰富、Pub/Sub

3.3 数据库选型

数据库 用途 数据特点
MongoDB 6.0 文档内容、操作日志 JSON 文档、灵活 Schema、水平扩展
PostgreSQL 15 用户信息、权限、元数据 关系型、ACID、复杂查询
Redis 7 会话、在线状态、热点数据 内存 KV、超高吞吐、过期策略
MinIO 附件、图片、文件存储 S3 兼容、分布式、低成本

3.4 基础设施

📊 4. 数据模型设计

4.1 核心实体关系图

┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│    User     │      │   Document   │      │  Workspace  │
├─────────────┤      ├──────────────┤      ├─────────────┤
│ id          │◄────►│ id           │◄────►│ id          │
│ email       │      │ title        │      │ name        │
│ username    │      │ workspace_id │      │ owner_id    │
│ avatar      │      │ created_by   │      │ created_at  │
│ created_at  │      │ created_at   │      └─────────────┘
└─────────────┘      │ updated_at   │
                     │ content      │
                     └──────────────┘
                            │
                            ▼
                     ┌──────────────┐
                     │   Operation  │
                     ├──────────────┤
                     │ id           │
                     │ document_id  │
                     │ user_id      │
                     │ type         │
                     │ data         │
                     │ timestamp    │
                     │ version      │
                     └──────────────┘

4.2 MongoDB Schema 设计

Document 集合

{
  "_id": "ObjectId",
  "title": "String",
  "workspaceId": "ObjectId",
  "createdBy": "ObjectId",
  "updatedAt": "Date",
  "createdAt": "Date",
  "content": {
    "type": "Binary",  // Yjs 二进制更新数据
    "encoding": "UInt8Array"
  },
  "snapshot": {
    "type": "Binary",  // 定期快照
    "version": "Number",
    "createdAt": "Date"
  },
  "metadata": {
    "tags": ["String"],
    "color": "String",
    "icon": "String"
  },
  "settings": {
    "allowComments": "Boolean",
    "allowSuggestions": "Boolean",
    "lockEditing": "Boolean"
  },
  "stats": {
    "viewCount": "Number",
    "editCount": "Number",
    "collaboratorCount": "Number"
  },
  "version": "Number",  // 当前版本号
  "isDeleted": "Boolean",
  "deletedAt": "Date"
}

Operation 集合(操作日志)

{
  "_id": "ObjectId",
  "documentId": "ObjectId",
  "userId": "ObjectId",
  "username": "String",
  "type": "String",  // insert, delete, update, format
  "data": {
    "position": "Number",
    "length": "Number",
    "content": "String",
    "attributes": "Object"
  },
  "timestamp": "Date",
  "version": "Number",
  "clientId": "String",  // 客户端唯一标识
  "siteId": "Number",    // CRDT site ID
  "lamport": "Number",   // Lamport 时间戳
  "vectorClock": "Object"
}

User 集合

{
  "_id": "ObjectId",
  "email": "String",
  "username": "String",
  "passwordHash": "String",
  "avatar": "String",
  "role": "String",  // admin, member, guest
  "workspaces": ["ObjectId"],
  "preferences": {
    "theme": "String",
    "language": "String",
    "notifications": "Object"
  },
  "lastActiveAt": "Date",
  "createdAt": "Date",
  "isVerified": "Boolean",
  "isOnline": "Boolean"
}

4.3 PostgreSQL Schema 设计

-- 工作空间表
CREATE TABLE workspaces (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    owner_id UUID NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 文档权限表
CREATE TABLE document_permissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    document_id UUID NOT NULL,
    user_id UUID NOT NULL,
    role VARCHAR(50) NOT NULL,  -- owner, editor, commenter, viewer
    granted_by UUID,
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP,
    UNIQUE(document_id, user_id)
);

-- 评论表
CREATE TABLE comments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    document_id UUID NOT NULL,
    parent_comment_id UUID,
    user_id UUID NOT NULL,
    content TEXT NOT NULL,
    position_start INTEGER,
    position_end INTEGER,
    status VARCHAR(20) DEFAULT 'active',  -- active, resolved, deleted
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 索引优化
CREATE INDEX idx_document_permissions_doc ON document_permissions(document_id);
CREATE INDEX idx_document_permissions_user ON document_permissions(user_id);
CREATE INDEX idx_comments_document ON comments(document_id);
CREATE INDEX idx_operations_document_version ON operations(document_id, version);

4.4 Redis 数据结构

# 在线用户集合
SET workspace:{workspaceId}:users {userId}
EXPIRE workspace:{workspaceId}:users 300

# 文档活跃用户
HSET doc:{docId}:presence {userId} "{jsonUserData}"
EXPIRE doc:{docId}:presence 60

# 用户光标位置
HSET doc:{docId}:cursors {userId} "{cursorData}"
EXPIRE doc:{docId}:cursors 30

# 会话 Token
SETEX session:{sessionId} 86400 "{sessionData}"

# 速率限制计数器
INCR rate_limit:{userId}:{endpoint}
EXPIRE rate_limit:{userId}:{endpoint} 60

# 发布订阅频道
PUBLISH doc:{docId}:updates "{updateMessage}"

⚡ 5. 协同算法设计

⚠️ 算法选择说明

本系统采用 CRDT(Conflict-free Replicated Data Type) 作为核心协同算法,相比传统 OT(Operational Transformation)算法,CRDT 具有去中心化、数学可证明一致性、更好的离线支持等优势。

5.1 CRDT vs OT 对比

特性 CRDT OT
一致性保证 数学可证明 依赖转换函数正确性
架构模式 去中心化 P2P 中心化服务器
离线支持 优秀 一般
实现复杂度 中等
性能开销 中等
代表产品 Figma, Notion Google Docs

5.2 Yjs CRDT 实现原理

核心概念

操作流程示例

// 客户端 A 操作
const ydoc = new Y.Doc();
const ytext = ydoc.getText('content');

// 用户输入 "Hello"
ytext.insert(0, 'Hello');

// 生成更新
const update = Y.encodeStateAsUpdate(ydoc);

// 发送到服务端和其他客户端
websocket.send(update);

// 客户端 B 接收更新
const remoteUpdate = websocket.receive();
Y.applyUpdate(ydoc, remoteUpdate);

// 自动合并,保证一致性

5.3 冲突解决策略

文本插入冲突

场景:两个用户同时在位置 5 插入不同文本

解决方案:使用 Client ID + Lamport 时间戳确定顺序

if (lamportA !== lamportB) {
    return lamportA < lamportB ? -1 : 1;
} else {
    return clientIdA < clientIdB ? -1 : 1;
}

格式化冲突

场景:用户对同一段文本应用不同格式

解决方案:Last-Writer-Wins + 属性合并

// 格式属性合并策略
const mergedAttributes = {
    ...baseAttributes,
    ...newAttributes,
    timestamp: Math.max(timestampA, timestampB)
};

5.4 性能优化策略

🔌 6. RESTful API 接口文档

API 规范说明

  • 基础 URL: https://api.collab-doc.com/v2
  • 认证方式: Bearer Token (JWT)
  • 请求格式: JSON (Content-Type: application/json)
  • 响应格式: JSON + 标准 HTTP 状态码
  • 版本控制: URL Path Versioning (/v2/)

6.1 认证授权接口

POST /auth/register

用户注册

创建新用户账户

请求参数
{
  "email": "user@example.com",
  "username": "string (3-20 字符)",
  "password": "string (最少 8 位)",
  "inviteCode": "string (可选)"
}
响应示例
{
  "success": true,
  "data": {
    "userId": "uuid",
    "email": "user@example.com",
    "username": "username",
    "accessToken": "jwt_token",
    "refreshToken": "refresh_token"
  },
  "message": "注册成功"
}
POST /auth/login

用户登录

请求参数
{
  "email": "user@example.com",
  "password": "string"
}
响应示例
{
  "success": true,
  "data": {
    "userId": "uuid",
    "accessToken": "jwt_token",
    "refreshToken": "refresh_token",
    "expiresIn": 3600
  }
}
POST /auth/refresh

刷新 Token

请求头
Authorization: Bearer {refreshToken}
响应示例
{
  "success": true,
  "data": {
    "accessToken": "new_jwt_token",
    "expiresIn": 3600
  }
}

6.2 文档管理接口

GET /documents

获取文档列表

查询参数
{
  "workspaceId": "uuid (可选)",
  "page": "number (默认 1)",
  "limit": "number (默认 20)",
  "sortBy": "updatedAt|createdAt|title",
  "order": "asc|desc",
  "search": "string (可选)"
}
响应示例
{
  "success": true,
  "data": {
    "documents": [
      {
        "id": "uuid",
        "title": "文档标题",
        "workspaceId": "uuid",
        "createdBy": {"id": "uuid", "name": "用户名"},
        "updatedAt": "ISO8601",
        "collaborators": 5,
        "preview": "文档预览内容..."
      }
    ],
    "pagination": {
      "total": 100,
      "page": 1,
      "limit": 20,
      "totalPages": 5
    }
  }
}
POST /documents

创建文档

请求体
{
  "title": "新文档",
  "workspaceId": "uuid",
  "content": "初始内容 (可选)",
  "templateId": "uuid (可选)",
  "visibility": "private|workspace|public"
}
响应示例
{
  "success": true,
  "data": {
    "id": "uuid",
    "title": "新文档",
    "createdAt": "ISO8601",
    "accessUrl": "/docs/uuid/edit"
  }
}
GET /documents/:documentId

获取文档详情

路径参数
documentId: string (required)
查询参数
{
  "includeContent": "boolean (默认 true)",
  "version": "number (可选,获取历史版本)"
}
响应示例
{
  "success": true,
  "data": {
    "id": "uuid",
    "title": "文档标题",
    "content": "完整内容",
    "createdBy": {...},
    "collaborators": [...],
    "permissions": "owner|editor|viewer",
    "version": 42,
    "updatedAt": "ISO8601"
  }
}
PUT /documents/:documentId

更新文档元数据

请求体
{
  "title": "新标题 (可选)",
  "workspaceId": "uuid (可选)",
  "settings": {
    "allowComments": true,
    "lockEditing": false
  }
}
DELETE /documents/:documentId

删除文档

软删除,可在回收站恢复

响应示例
{
  "success": true,
  "message": "文档已移至回收站"
}

6.3 工作空间接口

GET /workspaces

获取我的工作空间

响应示例
{
  "success": true,
  "data": {
    "workspaces": [
      {
        "id": "uuid",
        "name": "工作空间名称",
        "role": "owner|member",
        "documentCount": 25,
        "memberCount": 10
      }
    ]
  }
}
POST /workspaces

创建工作空间

请求体
{
  "name": "工作空间名称",
  "description": "描述 (可选)",
  "visibility": "private|public"
}

6.4 权限管理接口

POST /documents/:documentId/permissions

添加文档协作者

请求体
{
  "userId": "uuid",
  "role": "editor|commenter|viewer",
  "expiresIn": 86400 (可选,秒)
}
GET /documents/:documentId/permissions

获取文档权限列表

DELETE /documents/:documentId/permissions/:userId

移除协作者权限

6.5 版本历史接口

GET /documents/:documentId/versions

获取版本历史

查询参数
{
  "limit": "number (默认 50)",
  "before": "ISO8601 (可选)"
}
响应示例
{
  "success": true,
  "data": {
    "versions": [
      {
        "version": 42,
        "timestamp": "ISO8601",
        "userId": "uuid",
        "username": "用户名",
        "operationCount": 15,
        "summary": "添加了第三章内容"
      }
    ]
  }
}
POST /documents/:documentId/versions/:version/restore

恢复到指定版本

6.6 评论接口

POST /documents/:documentId/comments

添加评论

请求体
{
  "content": "评论内容",
  "positionStart": 100,
  "positionEnd": 150,
  "parentCommentId": "uuid (可选,回复评论)"
}
PUT /comments/:commentId

更新评论

DELETE /comments/:commentId

删除评论

6.7 错误响应格式

// 标准错误响应
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "人类可读的错误描述",
    "details": {}  // 可选的详细错误信息
  }
}

// 常见错误码
{
  "UNAUTHORIZED": "未授权访问",
  "FORBIDDEN": "权限不足",
  "NOT_FOUND": "资源不存在",
  "VALIDATION_ERROR": "参数验证失败",
  "RATE_LIMIT_EXCEEDED": "请求频率超限",
  "DOCUMENT_LOCKED": "文档已被锁定",
  "VERSION_CONFLICT": "版本冲突"
}

📡 7. WebSocket 实时通信协议

WebSocket 连接说明

  • 连接 URL: wss://ws.collab-doc.com/v2/ws
  • 认证方式: Query Parameter Token 或 Handshake Header
  • 心跳间隔: 30 秒
  • 重连策略: 指数退避 (1s, 2s, 4s, 8s, 16s, 30s)

7.1 连接建立

连接 URL 格式

wss://ws.collab-doc.com/v2/ws?token={jwt_token}&documentId={doc_id}&clientId={client_id}

握手响应

// 服务端发送的欢迎消息
{
  "type": "welcome",
  "data": {
    "clientId": "assigned_client_id",
    "documentId": "doc_id",
    "serverTime": "ISO8601",
    "protocolVersion": "2.0"
  }
}

7.2 消息协议格式

通用消息结构

{
  "type": "message_type",
  "requestId": "uuid (可选,用于请求 - 响应配对)",
  "timestamp": "number (毫秒时间戳)",
  "data": {}
}

7.3 客户端 → 服务端消息

WS join-document

加入文档房间

{
  "type": "join-document",
  "requestId": "uuid",
  "data": {
    "documentId": "uuid",
    "lastSyncedVersion": "number (可选)"
  }
}

// 响应
{
  "type": "document-state",
  "requestId": "uuid",
  "data": {
    "documentId": "uuid",
    "content": "binary_update",
    "version": 42,
    "collaborators": [...],
    "cursors": {...}
  }
}
WS sync-update

发送协同更新

{
  "type": "sync-update",
  "data": {
    "update": "base64_encoded_binary",
    "version": 43,
    "clientId": "client_id"
  }
}
WS cursor-update

更新光标位置

{
  "type": "cursor-update",
  "data": {
    "selection": {
      "anchor": {"pos": 100, "offset": 0},
      "head": {"pos": 150, "offset": 5}
    },
    "scrollPosition": {"top": 500},
    "userColor": "#FF5733"
  }
}
WS awareness-update

更新感知状态

{
  "type": "awareness-update",
  "data": {
    "status": "active|idle|offline",
    "typing": true,
    "metadata": {
      "browser": "Chrome 120",
      "platform": "Windows"
    }
  }
}
WS request-snapshot

请求文档快照

{
  "type": "request-snapshot",
  "requestId": "uuid",
  "data": {
    "version": 40
  }
}

7.4 服务端 → 客户端消息

WS sync-update

广播协同更新

{
  "type": "sync-update",
  "data": {
    "update": "base64_encoded_binary",
    "version": 43,
    "clientId": "other_client_id",
    "userId": "user_id"
  }
}
WS cursor-broadcast

广播光标位置

{
  "type": "cursor-broadcast",
  "data": {
    "clientId": "client_id",
    "userId": "user_id",
    "username": "用户名",
    "selection": {...},
    "color": "#FF5733"
  }
}
WS user-joined

用户加入通知

{
  "type": "user-joined",
  "data": {
    "userId": "uuid",
    "username": "用户名",
    "avatar": "url",
    "color": "#FF5733",
    "role": "editor"
  }
}
WS user-left

用户离开通知

{
  "type": "user-left",
  "data": {
    "userId": "uuid",
    "username": "用户名"
  }
}
WS awareness-change

感知状态变更

{
  "type": "awareness-change",
  "data": {
    "clientId": "client_id",
    "userId": "user_id",
    "status": "idle",
    "typing": false
  }
}
WS version-created

新版本创建通知

{
  "type": "version-created",
  "data": {
    "version": 43,
    "timestamp": "ISO8601",
    "userId": "uuid",
    "operationCount": 10
  }
}
WS error

错误消息

{
  "type": "error",
  "requestId": "uuid (如果是对请求的响应)",
  "data": {
    "code": "SYNC_VERSION_MISMATCH",
    "message": "版本不匹配",
    "expectedVersion": 42,
    "receivedVersion": 40
  }
}

7.5 心跳与连接维护

// 客户端发送心跳
{
  "type": "ping",
  "timestamp": 1234567890
}

// 服务端响应
{
  "type": "pong",
  "timestamp": 1234567890,
  "serverTime": 1234567895
}

// 超时断开:90 秒无通信自动断开

7.6 断线重连流程

  1. 检测到连接断开
  2. 保存未发送的操作队列到 IndexedDB
  3. 按指数退避策略重试连接
  4. 连接成功后发送 join-document 携带 lastSyncedVersion
  5. 服务端返回缺失的更新
  6. 重放本地未发送的操作
  7. 恢复正常同步

🔒 8. 安全设计

8.1 认证安全

8.2 授权控制

角色 查看 编辑 评论 分享 删除 权限管理
Owner
Editor
Commenter
Viewer

8.3 数据安全

8.4 防护措施

🚀 9. 部署方案

9.1 生产环境架构

Cloudflare CDN
+ DDoS 防护
Nginx LB
(集群)
K8s Pods
WebSocket 服务
MongoDB
Replica Set

9.2 Kubernetes 资源配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: collab-ws-service
spec:
  replicas: 5
  selector:
    matchLabels:
      app: collab-ws
  template:
    spec:
      containers:
      - name: ws-server
        image: collab-doc/ws-server:v2.0
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        ports:
        - containerPort: 8080
        env:
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: redis-url
---
apiVersion: v1
kind: Service
metadata:
  name: ws-service
spec:
  selector:
    app: collab-ws
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

9.3 扩缩容策略

9.4 监控指标

指标类别 关键指标 告警阈值
性能 P99 延迟、TPS、错误率 延迟>500ms, 错误率>1%
资源 CPU、内存、磁盘、网络 CPU>80%, 内存>85%
业务 在线用户数、文档数、操作数 突增/突降 50%
连接 WebSocket 连接数、断线率 断线率>5%