当前位置: 首页 > news >正文

如何快速实现多人协同编辑?

引言

协同编辑是目前成熟的在线文档编辑软件必备的功能,比如腾讯文档就支持多人协同编辑,基本都是采用监听command,然后同步此command给其他客户端来实现的,例如以下系列:

https://gcdn.grapecity.com.cn/showtopic-82517-1-1.html
https://gcdn.grapecity.com.cn/showtopic-82518-1-1.html
https://gcdn.grapecity.com.cn/showtopic-82519-1-1.html

这种做法可以快速实现大部分功能的协同操作,但是也有一些不足,我大致将这些不足分为两种类型:

第一种,command传递之后信息丢失,需要重写或修改command的,比如复制粘贴功能。

这种类型对应的是希望command生效,但实际上没有生效。

第二种,多人协同所必须的特殊功能,情况比较多:

1. 比如编辑一个单元格时,其他人不允许编辑此单元格,并有样式提醒;

2. A用户正在编辑时,B用户在上方插入了一行,此时A编辑的单元格也要下移,而不是保留在原位;

3. 缩放时不对其他页面有影响;

这种类型对应的是不希望command生效,或者希望改变command生效的效果。

如果你也在做协同,并且遇到了上述问题,那么这篇文章或许可以解答你心中的疑问。

先看下最终的实现效果吧:

协同编辑

在开始前,先对demo的架构做一个说明,我此次写的demo是html做前端,并用nodejs做服务端,前后端通信采用websocket的方式,目录结构如下:

大家在测试demo前,请务必认真阅读readme文件。

下面我就讲一下如何针对上面提到的几种情况做优化,以更好得满足协同的需求,整体的思路其实比较简单,无非就是对那些不满足需求的command做拦截,单独处理。以上提到的情况并不包含实现协同所需的全部功能,只是抛砖引玉,如果有其他没有考虑到的情况,可以用同样的方法处理。

一、向所有客户端同步command

这里用commandManager新增监听的方式来监听所有的操作,并用websocket发送到服务端。马赛克部分为后续其他代码逻辑,暂时不用看。

服务端仅做一个转发:

其他客户端接受到此消息,执行command即可:

到这里,开头提到的快速实现大部分操作的协同就已经完成了,后续的操作都是为了弥补当前方案的不足。

二、处理粘贴

粘贴的command同步到其他客户端时,会执行失败,仔细对比发出的command和接收的command,会发现其中两个字段发生了变化:

这两个数组内部本应该是Range对象,但是却被转换成了不同的Object,这是由于我们使用了JSON.stringify方法,而用此方法序列化时并不支持Range对象,所以我们在客户端接受到此信息时,需要重新将其还原为Range:

其实你可能会发现当存在fromRanges的时候,我直接用了copyTo方法实现了粘贴,并没有重新执行command,效果其实是一样的。

这里还隐含这另一种情况:从外部复制内容,粘贴到spread,这时fromRanges对象是不存在的,那么我们就需要执行command了,当然执行之前要把pastedRanges数组的值变为Range类型。

三、编辑状态唯一

即同一个单元格同一时间只能有一个用户编辑。这是协同编辑几乎必备的一个需求,看起来很简单,但事实上是比较复杂的。当客户端有用户开始编辑时,向服务端发送消息,

而服务端需要维护一个数组,记录所有当前正在被编辑的单元格信息,并向所有客户端同步

其他客户端收到消息后,用户如果要编辑此单元格,则禁止用户进入编辑状态

当然,用户可能希望看到有哪些人正在编辑哪些单元格,类似于这种效果:

这里是用自定义单元格的方案实现的:

这个功能算是初步实现了,但是考虑一下这种情况:如果你正在编辑时,其他用户在上方插入了一行呢?

Lily本来正在编辑A2,Alen在上方插入一行后,Lily应该编辑的是A3,但是以我们目前的实现方式,Lily编辑的仍然是A2。对应的,在上方删除行、在左侧插入删除列都会有同样的问题。

这里Lily和Alen两个人都会受到影响,Lily编辑的单元格应该移动,Alen被锁定的单元格也应该移动,而Alen这边比较简单,服务端根据插入行列更新锁定单元格信息就好,Lily这边则麻烦一些,需要记录下Lily已经输入的功能,并且在新的单元格打开,并开启输入框,其中callback函数就是选择新的输入框的逻辑,根据不同的状态有所不同,所以用回调函数的形式实现。

四、行列变动同步

相信你也注意到,在上述处理中,行列的变化信息是很重要的,在原生command的基础上还要有编辑框的处理逻辑,所以行列的变化也需要我们单独来处理,在客户端收到行列变化的消息时, 做出拦截:

并对编辑的框做出正确的移动

结语

到这里,这篇文章也接近尾声了,整体实现的思路其实比较简单,无非就是拦截那些不符合协同需求或者同步时有问题的command,并重新实现它们。这种方式能够快速实现简单的协同,并且做出定制化的修改。

但是这种方式也存在着一些问题,比如无法支持undo堆栈,你可以在代码中看到我会随时清空undo堆栈,阻止用户进行undoredo操作,这是因为用commandManager.execute的方式执行command时,一定会进入到堆栈,这就导致A用户的操作会出现在B用户的undo堆栈中,B用户撤销时,就有可能撤销A用户的操作。

除了上面这个问题以外,一定还有其他更深、更棘手的问题存在,所以要在实际项目中实现协同,我的想法是根据业务限制用户的操作类型,并对这些有限的操作针对性地开发协同功能,这样虽然效率比较低,但是由于涉及面小,更便于控制。

OK,以上就是这篇文章的全部内容了,欢迎读者在评论区留下你们的想法~

简单协同代码下载链接:

https://gcdn.grapecity.com.cn/forum.php?mod=attachment&aid=MjgzMDk0fGVlNTFkNGQ1fDE2OTA3NjM4Mjl8MHwxNzY0MDU%3D

扩展链接:

Spring Boot框架下实现Excel服务端导入导出

项目实战:在线报价采购系统(React +SpreadJS+Echarts)

Svelte 框架结合 SpreadJS 实现纯前端类 Excel 在线报表设计

相关文章:

如何快速实现多人协同编辑?

引言 协同编辑是目前成熟的在线文档编辑软件必备的功能,比如腾讯文档就支持多人协同编辑,基本都是采用监听command,然后同步此command给其他客户端来实现的,例如以下系列: https://gcdn.grapecity.com.cn/showtopic-…...

ThinkPHP 一对多关联

用一对多关联的前提 多的一方的数据库表有一的一方数据库表的外键。 举例,用户获取自己的所有文章 数据表结构如下 // 用户表 useruser_id - integer // 用户主键name - varchar // 用户名称// 文章表 articlearticle_id - integer // 文章主键title - varchar …...

C++基础篇(二)基本数组及示例

目录 一、一维数组1、定义和初始化2、访问和修改3、元素逆置和冒泡排序 二、二维数组(用指针进行访问与修改)1、定义和初始化2、访问与修改 三、更高维度的数组1、三维数组2、高维数组 一、一维数组 1、定义和初始化 在 C 中,可以使用下面的…...

C++多态练习题

目录 一.习题1: 解决下列测试代码所出现的问题 测试用例1: 测试用例2: 代码改进: 习题1总结: 二.习题2. 求类对象的大小 三.习题3: 代码解析 : 解析图: 四.习题4&#xff…...

ELD透明屏在智能家居中有哪些优点展示?

ELD透明屏是一种新型的显示技术,它能够在不需要背光的情况下显示图像和文字。 ELD透明屏的原理是利用电致发光效应,通过在透明基板上涂覆一层特殊的发光材料,当电流通过时,发光材料会发出光线,从而实现显示效果。 ELD…...

第十三章 利用PCA简化数据

文章目录 第十三章 利用PCA简化数据13.1降维技术13.2PCA13.2.1移动坐标轴 13.2.2在NumPy中实现PCA13.3利用PCA对半导体制造数据降维 第十三章 利用PCA简化数据 PCA(Principal Component Analysis,主成分分析)是一种常用的降维技术&#xff0…...

开源中文分词Ansj的简单使用

ANSJ是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram HMM分词模型:在Bigram分词的基础上,识别未登录词,以提高分词准确度。 虽然基本分词原理与ICTLAS的一样&#…...

251_多线程_创建一个多线程的图像处理应用,其中每个线程负责对一部分图像进行处理,然后将处理后的结果合并为最终图像

举一个更丰富的例子来说明多线程的用法。 我们将创建一个多线程的图像处理应用,其中每个线程负责对一部分图像进行处理,然后将处理后的结果合并为最终图像。 这个例子可以更好地展示多线程并发处理的优势。 假设有一个函数 processImageSection,它会对图像的一个特定区域进…...

[吐槽Edge浏览器]关于Edge浏览器的闪退问题

这个浏览器嘛,在谷歌浏览器不能页面翻译后,一直是用的高高兴兴的,可突然有一天,Edge浏览器页面加载不出来了。 很慌,大概就是页面崩溃、加载失败什么的都出现过。 修了整整一天,不知道原因在哪,…...

数据包在网络中传输的过程

ref: 【先把这个视频看完了】:数据包的传输过程【网络常识10】_哔哩哔哩_bilibili 常识都看看 》Ref: 1. 这个写的嘎嘎好,解释了为啥4层7层5层,还有数据包封装的问题:数据包在网络中的传输过程详解_数据包传输_张孟浩_jay的博客…...

Acwing.875 快速幂

题目 给定n组ai , bi, pi,对于每组数据,求出akimod pi的值。 输入格式 第一行包含整数n。 接下来n行,每行包含三个整数ai , bi,pi。输出格式 对于每组数据,输出一个结果,表示aibimod pi的值。 每个结果占一行。 数…...

【决策树-鸢尾花分类】

决策树算法简介 决策树是一种基于树状结构的分类与回归算法。它通过对数据集进行递归分割,将样本划分为多个类别或者回归值。决策树算法的核心思想是通过构建树来对数据进行划分,从而实现对未知样本的预测。 决策树的构建过程 决策树的构建过程包括以…...

类与对象(中--构造函数)

类与对象(中--构造函数) 1、构造函数的特性2、默认构造函数3、编译器自动生成的默认构造函数(无参的)(当我们不写构造函数时)3.1 编译器自动生成的默认构造函数只对 自定义类型的成员变量 起作用&#xff0…...

Makefile学习1

文章目录 Makefile学习1Makefile简介Makefile重要性Makefile内容1) 显式规则2) 隐晦规则3) 变量的定义4) 文件指示5) 注释 Makefile规则规则默认目标多目标多规则目标伪目标 Makefile目标依赖头文件依赖自动生成头文件依赖关系 Makefile命令Makefile变量变量定义和使用赋值立即…...

城市内涝监测预警系统,科学“智治”应对灾害

近日,台风“杜苏芮”以摧枯拉朽之势给我国东南沿海地区带来狂风骤雨,福建的三个国家气象观测站日降水量突破历史极值。之后,“杜苏芮”一路北上。中央气象台预报称,7月29日至8月1日,北京、天津、河北、山东西部、河南北…...

切片[::-1]解析列表list表示的“非负整数加1”

列表数位表示非负整数,熟练操作“满十进位”。 (本笔记适合熟练操作Python列表list的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅…...

Mac下certificate verify failed: unable to get local issuer certificate

出现这个问题,可以安装证书 在finder中查找 Install Certificates.command找到后双击,或者使用其他终端打开 安装完即可...

Django项目启动错误

uwsgi项目启动错误信息如下Did you install mysqlclient?Command pkg-config --exists mysqlclient returned non-zero exit status 1Command pkg-config --exists mariadb returned non-zero exit status 1.Traceback (most recent call last):File "/home/dream21th/co…...

Vue2 第十二节 Vue组件化编程 (二)

1. VueComponent 2. 单文件组件 一. VueComponent 组件本质上是一个名为VueComponent的构造函数&#xff0c;不是程序员定义的&#xff0c;是Vue.extend生成的只需要写<school/>或者<school><school/>&#xff0c;Vue解析时&#xff0c;会帮我们创建schoo…...

pycharm 远程连接服务器并且debug, 支持torch.distributed.launch debug

未经允许&#xff0c;本文不得转载&#xff0c;vx&#xff1a;837007389 文章目录 step1&#xff1a;下载专业版本的pycharmstep2 配置自动同步文件夹&#xff0c;即远程的工程文件和本地同步2.1 Tools -> Deployment -> configuration2.2 设置同步文件夹2.3 同步服务器…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

iview框架主题色的应用

1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题&#xff0c;无需引入&#xff0c;直接可…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...