SpreadJS 協同服務器 MongoDB 數據庫適配支持

為了支持 SpreadJS 協同編輯場景,協同服務器需要持久化存儲文檔、操作、快照及里程碑數據。本文介紹了 MongoDB 數據庫適配器的實現方法,包括集合初始化、適配器接口實現以及里程碑存儲支持。

一、MongoDB 集合初始化

協同編輯服務需要以下集合(Collections)來存儲數據:

  • documents:存儲文檔基本信息(類型、版本號、快照版本號)。
  • operations:存儲操作記錄,用于實現基于 OT 的操作重放。
  • snapshot_fragments:存儲快照的分片數據,支持大文檔分段管理。
  • milestone_snapshot:存儲里程碑快照,用于回溯和恢復。

初始化腳本示例如下:

export async function InitCollections(client) {const collectionsToCreate = [{ name: 'documents', indexes: [{ key: { id: 1 }, unique: true }] },{ name: 'operations', indexes: [{ key: { doc_id: 1, version: 1 }, unique: true }] },{ name: 'snapshot_fragments', indexes: [{ key: { doc_id: 1, fragment_id: 1 }, unique: true }] },{ name: 'milestone_snapshot', indexes: [{ key: { doc_id: 1, version: 1 }, unique: true }] },];await client.connect();const db = client.db(dbName);
const existingCollections = (await db.listCollections().toArray()).map(c => c.name);
for (const col of collectionsToCreate) {if (!existingCollections.includes(col.name)) {await db.createCollection(col.name);console.log(`Collection '${col.name}' created.`);} else {console.log(`Collection '${col.name}' already exists.`);}for (const idx of col.indexes) {await db.collection(col.name).createIndex(idx.key, { unique: idx.unique });}}
await client.close();
}

二、MongoDB 適配器實現

  1. 適配器說明

適配器 MongoDb 繼承了協同服務的數據庫接口,負責:

  • 文檔信息存取
  • 操作記錄管理
  • 快照存儲與更新
  • 分片快照管理

可根據業務需要,增加 事務(session)并發沖突檢測

  1. 適配器核心實現
export class MongoDb extends Db {constructor(client) {super();this.client = client;this.client.connect()this.db = client.db(dbName);}async getDocument(docId) {const documents = this.db.collection('documents');let row = await documents.findOne({ id: docId });if (row) {return {id: row.id,type: row.type,version: row.version,snapshotVersion: row.snapshot_version};}}async getSnapshot(docId) {const documents = this.db.collection('documents');let row = await documents.findOne({ id: docId });if (!row) {return null;}const fragments = await this.getFragments(docId);return {id: row.id,v: row.snapshot_version,type: row.type,fragments: fragments};}async getFragments(docId) {const fragments = this.db.collection('snapshot_fragments');const rows = await fragments.find({ doc_id: docId }).toArray();if (rows.length === 0) {return {};}const results = {};for (const row of rows) {results[row.fragment_id] = JSON.parse(row.data);}return results;}async getFragment(docId, fragmentId) {const fragments = this.db.collection('snapshot_fragments');const row = await fragments.findOne({ doc_id: docId, fragment_id: fragmentId });if (row) {return JSON.parse(row.data);}return null;}async getOps(docId, from, to) {const operations = this.db.collection('operations');const query = { doc_id: docId, version: { $gte: from } };if (to !== undefined) {query.version.$lte = to;}const rows = await operations.find(query).toArray();if (rows.length === 0) {return [];}return rows.map(row => JSON.parse(row.operation));}async commitOp(docId, op, document) {try {const documents = this.db.collection('documents');const operations = this.db.collection('operations');const row = await documents.findOne({ id: docId });if (op.create) {if (row) {throw new Error(`Document with id ${docId} already exists.`);}await documents.insertOne({id: docId,type: document.type,version: document.version,snapshot_version: document.snapshotVersion},);await operations.insertOne({doc_id: docId,version: op.v,operation: JSON.stringify(op)},);return true;}else if (op.del) {if (!row) {throw new Error(`Document with id ${docId} does not exist.`);}await documents.deleteOne({ id: docId },);return true;}else {if (!row || row.version !== op.v) {throw new Error(`Document with id ${docId} does not exist or version mismatch.`);}await operations.insertOne({doc_id: docId,version: op.v,operation: JSON.stringify(op)},);await documents.updateOne({ id: docId },{ $set: { version: document.version } },);return true;}}catch (error) {console.error('Error committing operation:', error);return false;}finally {}}async commitSnapshot(docId, snapshot) {try {const documents = this.db.collection('documents');const fragments = this.db.collection('snapshot_fragments');const row = await documents.findOne({ id: docId },);if (!row) {throw new Error(`Document with id ${docId} does not exist.`);}const currentSnapshotVersion = row.snapshot_version;if (snapshot.fromVersion !== currentSnapshotVersion || snapshot.v <= currentSnapshotVersion) {throw new Error(`Snapshot version mismatch: expected ${currentSnapshotVersion}, got ${snapshot.v}`);}await documents.updateOne({ id: docId },{ $set: { snapshot_version: snapshot.v } },);if (snapshot.fragmentsChanges.deleteSnapshot) {fragments.deleteMany({ doc_id: docId },);}else {const { createFragments, updateFragments, deleteFragments } = snapshot.fragmentsChanges;if (createFragments) {const createOps = Object.entries(createFragments).map(([id, data]) => ({doc_id: docId,fragment_id: id,data: JSON.stringify(data)}));if (createOps.length > 0) {await fragments.insertMany(createOps,);}}if (updateFragments) {const updateOps = Object.entries(updateFragments).map(([id, data]) => ({updateOne: {filter: { doc_id: docId, fragment_id: id },update: { $set: { data: JSON.stringify(data) } }}}));if (updateOps.length > 0) {await fragments.bulkWrite(updateOps,// { session });}}if (deleteFragments) {const deleteOps = deleteFragments.map(id => ({deleteOne: {filter: { doc_id: docId, fragment_id: id }}}));if (deleteOps.length > 0) {await fragments.bulkWrite(deleteOps,);}}}return true;}catch (error) {console.error('Error committing snapshot:', error);return false;}finally {}}async close() {await this.client.close();}
}

三、里程碑數據存儲

里程碑快照用于優化快照恢復性能(避免從頭重放所有操作)。 實現類 MongoMilestoneDb 提供 保存讀取 接口:

export class MongoMilestoneDb {constructor(client, interval) {this.client = client;this.interval = interval ? interval : 1000;this.db = client.db(dbName);}
async saveMilestoneSnapshot(snapshot) {const milestones = this.db.collection('milestone_snapshot');await milestones.insertOne({doc_id: snapshot.id,version: snapshot.v,snapshot: JSON.stringify(snapshot)});return true;}
async getMilestoneSnapshot(id, version) {const milestones = this.db.collection('milestone_snapshot');const row = await milestones.findOne({ doc_id: id, version: { $lte: version } },{ sort: { version: -1 } });if (row) {return JSON.parse(row.snapshot);}return null;}
}

四、在 DocumentServices 中配置

完成適配器與里程碑數據庫后,需要在 DocumentServices 中進行配置:

const documentServices = new OT.DocumentServices(
{ db: new MongoDb(mongoClient),milestoneDb: new MongoMilestoneDb(mongoClient, 500)
});

這樣,SpreadJS 協同服務器即可通過 MongoDB 實現文檔存儲、操作日志管理、快照與里程碑維護,保證協同編輯過程的高效與可擴展。

五、總結

本文展示了 SpreadJS 協同服務器對 MongoDB 數據庫的適配實現,主要包括:

  1. 集合初始化:定義所需集合與索引。
  2. 數據庫****適配器:支持文檔、操作、快照的存儲與管理。
  3. 里程碑存儲:提供快照的高效回溯能力。
  4. 服務集成:在 DocumentServices 中配置 MongoDB 適配器。

借助 MongoDB 的高性能與靈活數據結構,SpreadJS 協同服務可實現穩定、可擴展的文檔協作平臺。

擴展鏈接

使用 MySQL 為 SpreadJS 協同服務器提供存儲支持

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/94032.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94032.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94032.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ubuntu 主機名:精通配置與管理

主機名&#xff08;hostname&#xff09;是Linux系統中用于標識網絡上特定設備的名稱&#xff0c;它在網絡通信、服務配置&#xff08;如 Kubernetes 集群、數據庫&#xff09;以及日志記錄中扮演著至關重要的角色。對于初學者來說&#xff0c;配置主機名似乎很簡單&#xff0c…

C/C++ 協程:Stackful 手動控制的工程必然性

&#x1f680; C/C 協程&#xff1a;Stackful 手動控制的工程必然性 引用&#xff1a; C/C 如何正確的切換協同程序&#xff1f;&#xff08;基于協程的并行架構&#xff09; #mermaid-svg-SXgplRf3WRYc8A7l {font-family:"trebuchet ms",verdana,arial,sans-serif;…

新手向:使用STM32通過RS485通信接口控制步進電機

新手向&#xff1a;使用STM32通過RS485通信接口控制步進電機 準備工作 本文使用的STM32芯片是STM32F407ZGTx&#xff0c;使用的電機是57步進電機&#xff0c;驅動器是用的是時代超群的RS485總線一體化步進電機驅動器&#xff08;42 型&#xff1a;ZD-M42P-485&#xff09;。使…

設計模式筆記_行為型_命令模式

1.命令模式介紹命令模式&#xff08;Command Pattern&#xff09;是一種行為設計模式&#xff0c;它將請求或操作封裝為對象&#xff0c;使得可以用不同的請求對客戶端進行參數化。命令模式的核心思想是將方法調用、請求或操作封裝到一個獨立的命令對象中&#xff0c;從而使得客…

詳解MySQL中的多表查詢:多表查詢分類講解、七種JOIN操作的實現

精選專欄鏈接 &#x1f517; MySQL技術筆記專欄Redis技術筆記專欄大模型搭建專欄Python學習筆記專欄深度學習算法專欄 歡迎訂閱&#xff0c;點贊&#xff0b;關注&#xff0c;每日精進1%&#xff0c;與百萬開發者共攀技術珠峰 更多內容持續更新中&#xff01;希望能給大家帶來…

vue3+elemeent-plus, el-tooltip的樣式修改不生效

修改后的樣式&#xff0c;直接貼圖&#xff0c;經過刪除出現懸浮1、在書寫代碼的時候切記effect“light”&#xff0c;如果你需要的是深色的樣式:disabled"!multiple" 是否禁用<el-tooltip effect"light" placement"top" content"請先選…

網頁作品驚艷亮相!這個浪浪山小妖怪網站太治愈了!

大家好呀&#xff01;今天要給大家分享一個超級治愈的網頁作品——浪浪山小妖怪主題網站&#xff01;這個純原生開發的項目不僅顏值在線&#xff0c;功能也很能打哦&#xff5e;至于靈感來源的話&#xff0c;要從一部動畫說起。最近迷上了治愈系動畫&#xff0c;就想做一個溫暖…

搭建最新--若依分布式spring cloudv3.6.6 前后端分離項目--步驟與記錄常見的坑

首先 什么拉取代碼&#xff0c;安裝數據庫&#xff0c;安裝redis&#xff0c;安裝jdk這些我就不說了 導入數據庫 &#xff1a;數據庫是分庫表的 &#xff0c;不要建錯了 【一定要注意&#xff0c;不然nacos讀取不到配置文件】這個是給nacos用的這個是給項目配置或項目用的2. 服…

分布式唯一 ID 生成方案

在復雜分布式系統中&#xff0c;往往需要對大量的數據和消息進行唯一標識。如在美團點評的金融、支付、餐飲、酒店、貓眼電影等產品的系統中&#xff0c;數據日漸增長&#xff0c;對數據分庫分表后需要有一個唯一 ID 來標識一條數據或消息&#xff0c;數據庫的自增 ID 顯然不能…

飛算JavaAI賦能高吞吐服務器模擬:從0到百萬級QPS的“流量洪峰”征服之旅

引言&#xff1a;當“流量洪峰”來襲&#xff0c;如何用低代碼馴服高并發&#xff1f; 在數字化時代&#xff0c;從電商平臺的“雙11”大促到社交網絡的突發熱點事件&#xff0c;再到金融系統的實時交易高峰&#xff0c;服務器時刻面臨著**高吞吐量&#xff08;High Throughput…

C#數據訪問幫助類

一.中文注釋using System; using System.Data; using System.Xml; using System.Data.SqlClient; using System.Collections;namespace Microsoft.ApplicationBlocks.Data.Ch {/// <summary>/// SqlServer數據訪問幫助類/// </summary>public sealed class SqlHelp…

B站 韓順平 筆記 (Day 21)

目錄 1&#xff08;面向對象高級部分練習題&#xff09; 1.1&#xff08;題1&#xff09; 1.2&#xff08;題2&#xff09; 1.3&#xff08;題3&#xff09; Vehicles接口類&#xff1a; Horse類&#xff1a; Boat類&#xff1a; Plane類&#xff1a; VehiclesFactory…

Linux(十四)——進程管理和計劃任務管理

文章目錄前言一、程序與進程的關系1.1 程序與進程的定義1.2 父進程與子進程二、查看進程信息2.1 ps 命令&#xff08;重點&#xff09;2.2 動態查看進程信息top命令&#xff08;重點&#xff09;2.3 pgrep命令查詢進程信息2.4 pstree命令以樹形結構列出進程信息三、進程的啟動方…

太陽光模擬器在無人機老化測試中的應用

在無人機技術飛速發展的當下&#xff0c;其戶外作業環境復雜多變&#xff0c;長期暴露在陽光照射下&#xff0c;部件老化問題日益凸顯&#xff0c;嚴重影響無人機的性能與壽命。紫創測控Luminbox專注于太陽光模擬器技術創新與精密光學測試系統開發&#xff0c;其涵蓋的 LED、鹵…

網絡原理-TCP_IP

1.UDP&#xff08;即用戶數據報協議&#xff09;UDP是一種無連接的傳輸層協議&#xff0c;提供簡單的、不可靠的數據傳輸服務。它不保證數據包的順序、可靠性或重復性&#xff0c;但具有低延遲和高效率的特點。UDP協議段格式16位UDP?度,表?整個數據報(UDP?部UDP數據)的最??…

GitHub Actions YAML命令使用指南

version: 2 updates:- package-ecosystem: "github-actions"directory: "/"schedule:interval: "weekly"這段代碼是 Dependabot 的配置文件&#xff08;通常放在 .github/dependabot.yml 中&#xff09;&#xff0c;它的作用是 自動化管理 GitHu…

決策樹算法學習總結

一、經典決策樹算法原理 &#xff08;一&#xff09;ID3 算法 核心思想&#xff1a;以 “信息增益” 作為劃分屬性的選擇標準&#xff0c;通過最大化信息增益來提升數據集的 “純度”。 關鍵概念 —— 信息增益&#xff1a;指某個屬性帶來的 “熵減”&#xff08;即純度提升量&…

內網安全——出網協議端口探測

在實戰中難免會遇到各種各樣的情況&#xff0c;其中對于目標主機是否出網這是一個十分值得收集的信息&#xff0c;因為完全不出網你就獲取不到主機了 端口 Linux 系統 對于 Linux 系統&#xff0c;探測其允許出網的端口&#xff0c;這里使用的是 Linux 的自帶命令&#xff0c;所…

C#WPF實戰出真汁13--【營業查詢】

1、營業查詢介紹本模塊是最后一個模塊&#xff0c;該板塊需要的功能有&#xff1a;營業數據列表&#xff0c;查詢數據&#xff0c;導出數據&#xff0c;數據統計。2、UI設計布局TabControl 是 WPF 中用于創建多頁標簽式界面的控件&#xff0c;常用于組織多個子內容區域。每個子…

基于 Java 和 MySQL 的精品課程網站

基于 Java 和 MySQL 的精品課程網站設計與實現一、 畢業設計&#xff08;論文&#xff09;任務書摘要&#xff1a;近年來&#xff0c;教育信息化發展十分迅猛&#xff0c;人們的教育觀念、教育手段、學習方法、學習渠道等等都發生了重大的變化。知識性人才也已經日益成為了一個…