[]
        
首页
开发者学堂
文档
论坛
市场
生态机会
活动
在线咨询 立即试用
(Showing Draft Content)

协同插件-快照与变更集

在实时协同编辑中,快照(Snapshot)变更集(ChangeSet) 是为确保多用户同时编辑同一文档时的数据一致性、效率和实时性而引入的两个核心概念。这些概念解决了并发操作、数据同步和性能优化等关键挑战。下面将探讨它们的定义、用途和实现细节。

安装

import '@grapecity-software/spread-sheets-collaboration-addon';

协同类

为提供协同能力,工作簿中新增了一个属性用于获取 Collaboration 类,该类提供协同相关功能。

// field GC.Spread.Sheets.@Workbook.collaboration: GC.Spread.Sheets.Collaboration.Collaboration
/**
 * 工作簿的协同管理器。
 * @type {GC.Spread.Sheets.Collaboration.Collaboration}
 * //此示例展示如何获取快照。
 * @example
 * var snapshot = workbook.collaboration.toSnapshot();
 */

快照(Snapshot)

为什么使用快照

  • 在多用户环境中,每个用户需要了解文档的当前状态。快照表示文档在特定版本下的完整状态。

  • 快照允许客户端快速获取最新文档版本,作为后续编辑的基准。

协同中的价值

  • 一致性:快照为所有用户提供统一的起始点,确保所有人基于相同的文档状态进行编辑。

  • 初始化与恢复:新用户或断开后重新连接的用户可通过快照同步到最新状态。

快照格式

快照使用专为实时协同设计的新数据格式,与之前的 ssjsonsjs 格式不同。

API

快照捕获工作簿在特定版本的完整状态。以下方法支持工作簿实例与快照之间的转换:

/**
 * 仅用于协同场景,将工作簿状态保存为快照。
 * @return {object} snapshot - 快照对象
 */
Workbook.collaboration.prototype.toSnapshot = function (): any;
/**
 * 仅用于协同场景,从快照恢复工作簿状态。
 * @param {object} snapshot - 快照对象
 */
Workbook.collaboration.prototype.fromSnapshot = function (snapshot: any): void;

操作(Op)

什么是操作

对文档模型的每一次修改都记录为一个操作(Op,Operation)。

为什么使用操作

  • 每次编辑后传输整个文档(新快照)会消耗过多带宽。操作描述导致新快照的具体变更,例如 setValue(设置值)或 addRow(添加行)。

  • 仅发送操作即可实现同步,避免重复传输完整文档。

API

每个操作包含 type 字段描述修改类型:

export interface IOpComponent{
    type: GC.Spread.Sheets.Collaboration.OpType;
}

OpType 的详细定义请参考文档:操作类型枚举

变更集(ChangeSet)

什么是变更集

  • 变更集(ChangeSet) 是相关操作(Ops)的集合,通常代表一个单一的逻辑修改单元。它描述了文档如何从一个版本过渡到另一个版本。

  • 在SpreadJS中:

    • 单步模式(Single Mode,默认):由单个命令生成的操作(Ops)会被归类到一个变更集(ChangeSet)中。

    • 批处理模式(Batch Mode):开发人员可将多个修改合并到一个变更集(ChangeSet)中,以减少服务器负载和网络占用(需同步执行)。详细信息请参见“批处理操作(Batch Operations)”章节。

为什么使用变更集

  • 逻辑分组:变更集组织相关操作,使修改更具内聚性。例如,单个操作可能影响多个模型,以变更集形式提交比单独操作更清晰。

  • 原子性:变更集确保一组操作要么全部应用,要么全部不应用,维持数据一致性,避免部分状态。

  • 冲突解决:多用户场景中编辑可能冲突,变更集提供更高级别的抽象,使系统能高效比较和合并变更。

  • 历史追踪:变更集记录完整修改历史,支持变更追踪、回滚到特定版本及调试或审计。

  • 性能优化:将多个操作捆绑到变更集中,网络传输和同步更高效,尤其在带宽有限时。

协同中的价值

  • 效率:变更集紧凑且传输快,适合实时协同。

  • 并发处理:通过操作转换(OT),变更集管理用户同时操作,确保结果一致。

变更集限制

仅源自同一快照的工作簿被视为同一文档,可相互应用操作。

API

协同提供两个用于应用和监听变更集的 API:

interface IChangeSet {
    ops: IOpComponent[];
}
/**
 * 仅用于协同场景,将变更集应用到文档。
 * @param {changeSet: GC.Spread.Sheets.Collaboration.IChangeSet} changeSet - 变更集
 */
Workbook.collaboration.prototype.applyChangeSet = (changeSet: GC.Spread.Sheets.Collaboration.IChangeSet) => void;
/**
 * 仅用于协同场景,监听变更集。
 * @param {changeSetHandler: GC.Spread.Sheets.Collaboration.IChangeSetHandler} onOpHandler - 监听变更集的回调
 */
Workbook.collaboration.prototype.onChangeSet = (onChangeSetHandler: GC.Spread.Sheets.Collaboration.IChangeSetHandler) => void;

批处理操作(Batch Operations)

什么是批处理操作?

批处理操作允许开发人员将多个同步的工作簿操作(例如,批量配置更新、迭代式数据插入/删除)分组到一个变更集(ChangeSet)中。通过在一系列操作的开头使用startBatchOp()、结尾使用endBatchOp()进行包裹,所有中间操作(Ops)会被合并为一个原子性变更。

为何使用批处理操作?

批处理操作通过解决多操作场景中的常见问题,优化了协作工作流程:

  • 原子性(Atomicity):分组后的操作会作为一个统一的整体变更应用于所有协作者,确保数据一致性(例如,迭代式数据更新后,公式引用仍保持有效)。

  • 高效性(Efficiency):更少的变更集(ChangeSet)可在协作过程中减少带宽占用和服务器负载。

  • 简化的撤销/重做(Simplified Undo/Redo):尽管批处理操作设计用于不支持撤销的批量更新,但它能与SpreadJS的命令管理器无缝集成,允许将整个批处理作为单个步骤进行恢复。

  • 实际使用场景(Practical Use Case):适用于工作簿初始化或大规模更新(例如,获取远程数据并对特定单元格应用迭代式变更)——在这类场景中,分散的操作原本会导致性能下降。

应用程序接口(API)协作功能提供以下两个API,用于将多个操作(Ops)分组到单个变更集(ChangeSet)中:

  • startBatchOp():启动批处理操作,允许将多个操作分组并合并为一个变更集(ChangeSet)。

Workbook.collaboration.prototype.startBatchOp(): void
  • endBatchOp():结束当前批处理操作,将收集的所有操作最终合并为一个变更集(ChangeSet)。

Workbook.collaboration.prototype.endBatchOp(): void;

注意

  • startBatchOp()endBatchOp()函数必须成对使用,且顺序正确。

  • startBatchOp()endBatchOp()之间的操作必须是同步的。包含异步逻辑可能会导致不可预测的问题。

示例(Example)按以下代码定义一个包含批处理操作的变更集(ChangeSet)。

spread.collaboration.startBatchOp();
sheet.setValue(0, 0, "Hello"); // 在单元格(0,0)设置值为“Hello”
sheet.setFormula(0, 2, "=SUM(A1:B1)"); // 在单元格(0,2)设置公式“=SUM(A1:B1)”
spread.collaboration.endBatchOp(); 
// 上述所有操作将合并到一个变更集(ChangeSet)中

其他功能

注册类型

在协同场景中,注册协同类型是必要步骤,可避免撤销行为异常等问题。工作簿包含注册 OT_Type 的方法,用于处理额外操作信息(如冲突解决):

export interface IOT_Type{
    uri?: string;
    transform?: (op1: GC.Spread.Sheets.Collaboration.IChangeSet, op2: GC.Spread.Sheets.Collaboration.IChangeSet, side: 'left' | 'right') => GC.Spread.Sheets.Collaboration.IChangeSet;
    transformX?: (op1: GC.Spread.Sheets.Collaboration.IChangeSet, op2: GC.Spread.Sheets.Collaboration.IChangeSet, side: 'left' | 'right') => GC.Spread.Sheets.Collaboration.IChangeSet[];
}
/**
 * 仅用于协同场景,注册协同类型。
 * @param {GC.Spread.Sheets.Collaboration.IOT_Type} type - 协同类型
 */
Workbook.collaboration.prototype.registerCollaborationType(type: GC.Spread.Sheets.Collaboration.IOT_Type): void;

示例

import * as GC from '@grapecity-software/spread-sheets'
import { type } from '@grapecity-software/spread-sheets-collaboration-client';

const workbook = new GC.Spread.Sheets.Workbook('ss');
workbook.collaboration.registerCollaborationType(type);

更多详情请见:扩展包安装与绑定

用户

新增用户概念及相关 API,支持用户绑定、权限设置和协同状态展示。详见:用户与权限 API

示例实现

利用快照和变更集可实现高级功能,以下是两个示例:

示例1:同步页面上的两个 Spread 实例

使用 onChangeSet 监听模型变更,实时同步两个 Spread 实例:

var spread = new GC.Spread.Sheets.Workbook(document.getElementById('ss'));
var spread2 = new GC.Spread.Sheets.Workbook(document.getElementById('ss2'));
   
spread2.collaboration.fromSnapshot(spread.collaboration.toSnapshot());
spread.collaboration.onChangeSet((changeSet) => {
  spread2.collaboration.applyChangeSet(changeSet);
});
spread2.collaboration.onChangeSet((changeSet) => {
  spread.collaboration.applyChangeSet(changeSet);
});

示例2:存储和恢复状态

监听变更集并存储到 localStorage,以便页面刷新后恢复状态:

// 初始化 SpreadJS 工作簿
var spread = new GC.Spread.Sheets.Workbook(document.getElementById('ss'));

// 存储初始快照
const initialSnapshot = spread.collaboration.toSnapshot();
localStorage.setItem('snapshot', JSON.stringify(initialSnapshot));
applyChangeSet(spread);

// 应用 localStorage 中存储的快照和变更集
function applyChangeSet (spread) {
    // 从 localStorage 获取快照和变更
    const storedSnapshot = JSON.parse(localStorage.getItem('snapshot'));
    const storedChanges = JSON.parse(localStorage.getItem('changes') || '[]');

    if (storedSnapshot && spread) {
        spread.collaboration.fromSnapshot(storedSnapshot);

        spread.suspendPaint();
        // 应用变更集
        storedChanges.forEach((changeSet) => {
            spread.collaboration.applyChangeSet(changeSet);
        });
        spread.resumePaint();
    }
}

// 监听并存储变更集
spread.collaboration.onChangeSet((changeSet) => {
    // 从 localStorage 获取变更,默认为空数组
    let changes = JSON.parse(localStorage.getItem('changes') || '[]');
    // 添加新变更集
    changes.push(changeSet);
    // 保存到 localStorage
    localStorage.setItem('changes', JSON.stringify(changes));
});