Skip to content

协同编辑

Tiny-editor-demo

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

在线协同演示

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

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

快速开始

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

前端配置

安装依赖

bash
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 # 设置 MongoDB 初始用户名
      MONGO_INITDB_ROOT_PASSWORD: admin # 设置 MongoDB 初始密码
    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}'
  environment:
    HOST: ${HOST:-0.0.0.0} # 设置后端监听的网络接口
    PORT: ${PORT:-1234} # 默认 1234 端口,可以使用环境变量修改
    MONGODB_URL: ${MONGODB_URL:-mongodb://admin:admin@mongodb:27017/?authSource=admin} # 如果你使用自己的 mongodb 服务需要修改此项
    MONGODB_DB: ${MONGODB_DB:-tinyeditor} # 数据库名称
    MONGODB_COLLECTION: ${MONGODB_COLLECTION:-documents} # 集合名称

  depends_on:
    - mongodb

volumes:
  mongodb_data:

如果你需要更换映射端口等,可创建 .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 mongo \
  -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=admin \
    -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@127.0.0.1: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
const editor = new FluentEditor('#editor', {
  theme: 'snow',
  modules: {
    'collaborative-editing': {
      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
const editor = new FluentEditor('#editor', {
  theme: 'snow',
  modules: {
    'collaborative-editing': {
      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',
}

const editor = new FluentEditor('#editor', {
  theme: 'snow',
  modules: {
    'collaborative-editing': {
      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