Skip to content

协同编辑

Tiny-editor-demo

TinyEditor 支持多人实时协作编辑,支持多种连接协议(如 WebSocket、WebRTC),可自定义后端持久化,适用于多场景的在线协同编辑需求。

在线协同演示

整个协同编辑系统由三部分组成:前端 TinyEditor、中间层协作引擎 Yjs 和后端服务(用于数据同步和持久化)。前端编辑器将操作传递给 YjsYjs 通过不同的连接协议(如 WebSocketWebRTC)实现多端同步, 并支持将数据持久化到后端数据库(如 MongoDB)。 Tiny-editor-demo

下面是一个完整的协同编辑演示:

快速开始

协同编辑功能需要配置前端和后端服务。

前端配置

安装依赖

重要提示: 由于项目使用了 patch 补丁,必须先在项目根目录运行 pnpm i 安装所有依赖,然后再安装协同编辑所需的额外依赖。

bash
# 首先在项目根目录安装所有依赖(必须执行)
pnpm i

# 然后安装协同编辑所需的额外依赖
pnpm i quill-cursors y-protocols y-quill yjs y-indexeddb y-websocket

引入协同编辑模块

javascript
import FluentEditor, { CollaborationModule } from '@opentiny/fluent-editor'
FluentEditor.register('modules/collaborative-editing', CollaborationModule, true)

编辑器基础配置:

javascript
const editor = new FluentEditor('#editor', {
  theme: 'snow',
  modules: {
    'collaborative-editing': {
      provider: {
        type: 'websocket',
        options: {
          serverUrl: 'ws://localhost:1234',
          roomName: 'Tiny-Editor-Demo',
        },
      },
    },
  },
})

在 Vue 项目中集成协作编辑:YuQue.vue

后端服务

可选择 Docker 容器化启动或本地部署

Docker 容器化部署(推荐)

  1. 拉取 Docker 镜像,使用 Docker Compose 一键启动:
bash
docker pull yinlin124/collaborative-editor-backend:latest
  1. 创建 docker-compose.yml 文件,内容如下:
yaml
services:
  mongodb:
    image: mongo:latest
    container_name: yjs-mongodb
    restart: always
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin!123
    volumes:
      - mongodb_data:/data/db

  websocket-server:
    image: yinlin124/collaborative-editor-backend:latest
    container_name: yjs-websocket-server
    restart: always
    ports:
      - '${PORT:-1234}:${PORT:-1234}'
    env_file:
      - .env
    depends_on:
      - mongodb

volumes:
  mongodb_data:
  1. 在项目根目录下新建 .env 文件:
env
HOST=0.0.0.0
# 注意这里的 PORT 需与前端配置中的 serverUrl 端口一致
PORT=1234
MONGODB_URL=mongodb://admin:admin!123@mongodb:27017/?authSource=admin
MONGODB_DB=tinyeditor
MONGODB_COLLECTION=documents
GC=true

可参照下方表格进行配置 .env 文件

变量名必需默认值说明
HOST-服务器监听地址
PORT-WebSocket 服务端口
MONGODB_URL-MongoDB 连接字符串
MONGODB_DB-MongoDB 数据库名称
MONGODB_COLLECTION-MongoDB 集合名称
GCtrue是否启用 Yjs 垃圾回收
  1. 在项目根目录下运行 docker-compose 启动容器:
bash
docker compose up

启动后即可使用 ws://localhost:1234 作为前端 serverUrl 配置

本地部署

  1. 启动 MongoDB(如果有其他 MongoDB 服务可跳过此步骤)
bash
docker run -d \
  --name mongodb \
  -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD="admin!123" \
  -v mongodb_data:/data/db \
  mongo:latest
  1. 进入协同编辑后端子包目录
bash
cd packages/collaborative-editing-backend
  1. 创建 .env 文件
env
HOST=0.0.0.0
PORT=1234
MONGODB_URL=mongodb://admin:admin!123@localhost:27017/?authSource=admin
MONGODB_DB=tinyeditor
MONGODB_COLLECTION=documents
GC=true
  1. 启动本地服务器
bash
pnpm install -g pm2
pnpm install
pnpm start

启动后即可使用 ws://localhost:1234 作为前端 serverUrl 配置


编辑器配置说明

Provider 配置

Provider 用于管理和同步多个用户之间的数据,它负责将本地的编辑操作与远程的其他用户进行实时同步。TinyEditor 支持多种 Provider 类型,常用的有 WebSocketWebRTC,你也可以根据本文实现自定义的 Provider。

WebSocket Provider

参数类型必填默认值说明
serverUrlstring-WebSocket 服务器地址
roomNamestring-房间名称
connectbooleantrue是否自动连接
paramsRecord<string, string>-连接参数
protocolsstring[]-WebSocket 协议
resyncIntervalnumber-重新同步间隔(毫秒)
maxBackoffTimenumber-最大退避时间

示例

javascript
provider: {
  type: 'websocket',
  options: {
    serverUrl: 'wss://120.26.92.145:1234',
    roomName: 'tiny-editor-demo',
    connect: true,
    resyncInterval: 3000,
    maxBackoffTime: 2500,
    protocols: ['json'],
  }
}

WebRTC Provider

注意: 需要额外安装 WebRTC 依赖 pnpm i y-webrtc,并且搭配 WebRTC 后端使用

参数类型必填默认值说明
type'webrtc'-提供者类型
roomNamestring-房间名称
signalingstring[]-信令服务器列表
filterBcConnsboolean-是否过滤广播连接
maxConnsnumber-最大连接数
passwordstring-房间密码
peerOptsRecord<string, unknown>-WebRTC 对等连接选项

WebRTC 前端配置示例

javascript
const editor = new FluentEditor('#editor', {
  theme: 'snow',
  modules: {
    'collaborative-editing': {
      provider: {
        type: 'webrtc',
        options: {
          roomName: 'Tiny-Editor-WebRTC',
          signaling: ['wss://signaling.yjs.dev'],
        },
      },
    },
  },
})

自定义 Provider

TinyEditor 支持注册自定义的 Provider 类型,您可以实现自己的连接提供者。

提示: Yjs 生态系统提供了多种现成的 Provider 可供选择和参考,详见:Yjs Connection Provider。您可以基于这些 Provider 进行二次开发,或者作为实现自定义 Provider 的参考。

UnifiedProvider 接口

所有自定义 Provider 都必须实现 UnifiedProvider 接口:

typescript
interface UnifiedProvider {
  type: string // Provider 类型标识
  awareness: Awareness // 感知实例
  document: Y.Doc // Yjs 文档
  connect: () => void // 连接方法
  destroy: () => void // 销毁方法
  disconnect: () => void // 断开连接方法
  isConnected: boolean // 连接状态
  isSynced: boolean // 同步状态

  // 事件处理器
  onConnect?: () => void // 连接成功回调
  onDisconnect?: () => void // 断开连接回调
  onError?: (error: Error) => void // 错误回调
  onSyncChange?: (isSynced: boolean) => void // 同步状态变化回调
}
创建自定义 Provider
  1. 实现 Provider 类
typescript
import type { ProviderConstructorProps, UnifiedProvider } from '@opentiny/fluent-editor'
import type { Awareness } from 'y-protocols/awareness'
import * as Y from 'yjs'

export class MyCustomProvider implements UnifiedProvider {
  type = 'my-custom'
  isConnected = false
  isSynced = false

  constructor({ options, awareness, doc, onConnect, onDisconnect, onError, onSyncChange }: ProviderConstructorProps<{ endpoint: string }>) {
    this.document = doc
    this.awareness = awareness
    this.onConnect = onConnect
    this.onDisconnect = onDisconnect
    this.onError = onError
    this.onSyncChange = onSyncChange
    // 以上参数都会传入,可直接接收或重新定义

    // 自定义逻辑
    this.connect()
  }

  connect = () => {
    // 连接逻辑
    // 例如
    // const provider = new WebsocketProvider(
    //     options.serverUrl,
    //     options.roomName,
    //     this.document,
    //     {
    //       awareness: this.awareness,
    //       ...options,
    //     },
    // )
    // provider.connect();
  }

  disconnect = () => {}
  destroy = () => {}
}
  1. 注册 Provider
typescript
import { registerProviderType } from '@opentiny/fluent-editor'
import { MyCustomProvider } from './MyCustomProvider'

registerProviderType('my-custom', MyCustomProvider)
  1. 使用自定义 Provider
typescript
const editor = new FluentEditor('#editor', {
  modules: {
    'collaborative-editing': {
      provider: {
        type: 'my-custom',
        options: {
          endpoint: 'https://my-service.com/api',
        },
      },
    },
  },
})

Awareness 配置

Awareness 实现用户在线状态、光标位置等信息的实时同步。每个用户的在线状态、名称、颜色、光标位置等会自动广播给其他协作者,实现多人编辑时的身份和操作可视化。

参数类型必填说明
stateAwarenessState用户状态信息
timeoutnumber用户状态超时时间(ms)

AwarenessState 结构

参数类型必填默认值说明
namestringUser ${id}用户名称
colorstring#ff6b6b用户颜色,用于光标和选区的颜色显示

示例

javascript
awareness: {
  state: {
    name: `user${Math.random().toString(36).substring(2, 8)}`,
    color: `#${Math.floor(Math.random() * 16777215).toString(16)}`
  },
  timeout: 30000,
}

事件回调

回调函数参数说明
onConnect成功连接到协作服务器时触发
onDisconnect与协作服务器连接断开时触发
onSyncChangeisSynced: boolean文档同步状态变化时触发,true 表示已同步

Cursors 配置

cursors 默认开启,并且支持以下配置(详细配置可见 quill-cursors):

参数类型默认值说明
templatestring-光标模板
hideDelayMsnumber5000光标隐藏延迟时间
hideSpeedMsnumber0光标隐藏动画速度
selectionChangeSourcestring-选择变化源
transformOnTextChangebooleantrue文本变化时是否转换光标

示例

注意光标模板内的类名不可变

javascript
const CURSOR_CLASSES = {
  SELECTION_CLASS: 'ql-cursor-selections',
  CARET_CONTAINER_CLASS: 'ql-cursor-caret-container',
  CARET_CLASS: 'ql-cursor-caret',
  FLAG_CLASS: 'ql-cursor-flag',
  NAME_CLASS: 'ql-cursor-name',
}

cursors: {
  template: `
    <span class="${CURSOR_CLASSES.SELECTION_CLASS}"></span>
    <span class="${CURSOR_CLASSES.CARET_CONTAINER_CLASS}">
      <span class="${CURSOR_CLASSES.CARET_CLASS}"></span>
    </span>
    <div class="${CURSOR_CLASSES.FLAG_CLASS}">
      <small class="${CURSOR_CLASSES.NAME_CLASS}"></small>
    </div>
  `,
  hideDelayMs: 300,
  hideSpeedMs: 300,
  transformOnTextChange: true,
}

更多后端服务支持

y-websocket-server

可以使用 y-websocket-server 快速搭建 WebSocket 服务器。

安装依赖:

bash
git clone https://github.com/yjs/y-websocket-server.git
cd y-websocket-server
pnpm i

启动服务:

操作系统启动命令
Ubuntu/MacOSHOST=localhost PORT=1234 YPERSISTENCE=./dbDir npx y-websocket
Windows PowerShell$env:HOST="localhost"; $env:PORT="1234"; $env:YPERSISTENCE="./dbDir"; npx y-websocket

HOST指定可访问地址,PORT指定暴露端口,YPERSISTENCE指定持久化目录。

WebRTC 服务器

可以使用 y-webrtc-server 快速搭建 WebRTC 服务器。

安装依赖:

克隆 WebRTC 服务端仓库并安装依赖:

bash
git clone https://github.com/yjs/y-webrtc.git
cd y-webrtc
pnpm i

启动服务:

操作系统启动命令
Ubuntu/MacOSHOST=localhost PORT=4444 npx y-webrtc
Windows PowerShell$env:HOST="localhost"; $env:PORT="4444"; npx y-webrtc

自定义数据库持久化

TinyEditor 基于 WebSocket 提供了自定义的协同编辑后端服务,支持 MongoDB 持久化和 Docker 容器化部署。

详细的自定义持久化服务配置和部署请参考:collaborative-editing-backend

Made with ❤ by