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 适配器实现
- 适配器说明
适配器 MongoDb
继承了协同服务的数据库接口,负责:
- 文档信息存取
- 操作记录管理
- 快照存储与更新
- 分片快照管理
可根据业务需要,增加 事务(session) 或 并发冲突检测。
- 适配器核心实现
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 数据库的适配实现,主要包括:
- 集合初始化:定义所需集合与索引。
- 数据库****适配器:支持文档、操作、快照的存储与管理。
- 里程碑存储:提供快照的高效回溯能力。
- 服务集成:在
DocumentServices
中配置 MongoDB 适配器。
借助 MongoDB 的高性能与灵活数据结构,SpreadJS 协同服务可实现稳定、可扩展的文档协作平台。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
从AI调用到AI智能体:全面解析三种AI应用的技术架构
一、AI应用开发的范式革命 在当今全球企业加速数字化转型的浪潮中,人工智能(AI)已不再是边缘性的技术点缀,而是深度嵌入业务流程、重塑价值创造方式的核心驱动力。我们正见证一场深刻的范式革命:AI正从单一的功能性工具,演变为能够理解、协作乃至自主决策的"数字员工"。 随着AI能力的不断演进,AI应用的开发模式也呈现出多样化和多层次的特点。从最基础的API调用,到结构化的工作流,再到具备高度自主性的智能体,这三种主流模式分别对应着不同的业务场景、技术复杂度和实现成本。技术决策者、架构师和开发者面临着一个关键问题:如何为不同阶段、不同需求的AI应用选择并设计出清晰、可扩展、高效率且成本可控的软件技术架构? 本文旨在为这一核心议题提供一个从理论到实践的完整指南。我们将首先清晰界定"AI调用"、"AI工作流"和"AI智能体"这三种模式的核心特征与差异。随后,我们将深入剖析每种模式的技术架构蓝图,详细拆解其关键组件、技术选型考量以及数据与调用流程,来帮助读者在实际项目中做出明智的技术选型和架构设计,从而驾驭这场由AI驱动的应用开发范式革命。 二、 AI应用的三种模式 按照业务场景的复杂程度,我们把...
-
下一篇
使用 MySQL 为 SpreadJS 协同服务器提供存储支持
在多人实时编辑的场景下,SpreadJS 协同服务器需要持久化存储 文档信息、操作日志、快照分片 以及 里程碑快照。 如果你的系统更偏向关系型数据库,那么 MySQL 就是一个很合适的选择。 本文将带你实现 SpreadJS 协同服务器的 MySQL 数据库适配器。 🗂️ 数据库建表设计 我们需要 4 张核心表: documents:存储文档基本信息(文档 ID、类型、版本号、快照版本号) operations:存储用户的操作记录,用于 OT 算法重放 snapshot_fragments:存储快照的分片数据 milestone_snapshot:存储里程碑快照,提升文档恢复速度 📌 建表 SQL 示例: CREATE TABLE IF NOT EXISTS documents( id VARCHAR(255) PRIMARY KEY, type VARCHAR(255) NOT NULL, version INT NOT NULL, snapshot_version INT NOT NULL ); CREATE TABLE IF NOT EXISTS operations( do...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池