最近开始做自己的第一个开源项目:一个基于思维导图的测试用例管理系统MinderCase,在做了一周的技术调研后,决定采用kityminder-editor作为思维导图编辑器,为了支持实时存储,当思维导图内容变化时使用JSON-Patch计算出内容变化产生的diffPatches,然后将diffPatches传给后台映射为对应的MongoDB操作符,执行更新操作

JSON-Patch是用来描述JSON数据变化的一种形式。使用它可以避免在JSON数据只发生部分修改时发送整个文档。与HTTP Patch方法是天造地设的一对,它允许以符合标准的方式对HTTP API进行部分更新。
kityminder-editor使用JSON结构来存储数据,每个节点包括一个data字段表示节点本身的属性,children数组包含该节点的子节点,依次递归嵌套。在编辑思维导图的时候,最多的动作就是对节点的增、删、改,对于这种树形结构,借鉴传统关系型数据库的设计思路,可以使用节点引用的存储方法,参考MongoDB树形结构建模,但是在很多情况下,节点的增删查改会会引起大量的数据库操作,因此可以充分发挥MongoDB的优势,把整个JSON数据存储在一个document中。最终的存储结构很简单,除了数据库自动生成的_id_v字段,只有data字段表示完整的思维导图数据。

{
"_id": ObjectId("5b589668eac4d118dabf9546"),
"data": {
"root": {
"data": {
"id": "bn5xbxbefk00",
"created": 1532532217325,
"text": "思维导图"
},
"children": [
{
"data": {
"id": "bna5hhbc6xc0",
"created": 1532961461384,
"text": "分支主题"
},
"children": [
]
}
]
},
"template": "default",
"theme": "fresh-blue",
"version": "1.4.43"
},
"__v": NumberInt("0")
}

当思维导图内容发生变化时,会触contentchange事件,在这个事件中我们使用exportJson方法导出当前思维导图的JSON结构数据,与上一次保存的数据进行JSON Diff,生成diffPatches,如下所示,删除一个节点引发的数据变化,可以使用diffPatches操作数组表示。

window.editor.minder.on("contentchange", () => {
const newMinderData = minder.exportJson();
const diffPatches = compare(oldMinderData, newMinderData);
this.minderData = newMinderData;
diffPatches.length > 0 &&
axios
.patch(`/minder/${id}`, diffPatches)
.then(function(response) {
oldMinderData = newMinderData;
console.log(response);
})
.catch(function(error) {
console.log(error);
});
});



后台在接收到前端传来的diffPatches后,将其映射为MongoDB的操作符,比如addreplace操作可以映射为$set操作,remove可以映射为$unset操作,目前为止,我发现kityminder-editor的操作引发的diff仅限这三种操作。

const diffPatches = ....;
const opMap = {
add: "$set",
replace: "$set",
remove: "$unset"
};
const diffPatches = ctx.request.body; //
const update = {};
diffPatches.reduce((prev, cur) => {
const { op, path, value = 1 } = cur;
if (!prev[opMap[op]]) {
prev[opMap[op]] = {};
}
prev[opMap[op]][`data${path.replace(/\//g, ".")}`] = value;
return prev;
}, update);

实践过程中发现remove操作映射成$unset操作会有一些问题,当进行删除操作是,对应的patch操作是没有value的,上面给了一个默认值为1,该操作映射为$unset执行后,对应对象字段或者数组元素会变成null,如下图所示。

实践过程中发现对象字段变为null,如上图中的priority字段变成null,对思维导图的渲染不会产生影响,但是子节点数组children数组中包含null元素会导致思维导图加载失败。实际上MongoDB对于数组的元素的删除有其对应的操作符$pull,遗憾的是该操作符并不能根据数组索引来删除数组,关于这方面的具体问题可以参考In mongoDb, how do you remove an array element by its index,总的来说,目前没有好的方法解决这个问题,即使我尝试修改产生diffPatches的代码,给remove操作添加上value字段,当一个操作引发对个patch时,在更新数据库时可能会发生冲突导致更新失败。因此只能“曲线救国”,在读取思维导图数据时,递归遍历所有节点的子节点数组,过滤除null值,这样在渲染整个思维导图数据时就不会发生错误。考虑到读取数据的实时性要求不高,而修改数据实时保存实时性要求比较高,这个方案还是可以接受的。

function isValidArr(arr) {
return Array.isArray(arr) && arr.length;
}
function removeInvalidChild(node = {}){
const { children } = node || {};
if(isValidArr(children)){
node.children = children.filter((item) => {
removeInvalidChild(item);
return item;
})
}
}
removeInvalidChild(root);

到此为止,所有的技术论证都已完成,下一步就是现实整个系统了。

kityminder-editor + MongoDB 思维导图数据自动实时保存方案的更多相关文章

  1. Web思维导图实现的技术点分析(附完整源码)

    简介 思维导图是一种常见的表达发散性思维的有效工具,市面上有非常多的工具可以用来画思维导图,有免费的也有收费的,此外也有一些可以用来帮助快速实现的JavaScript类库,如:jsMind.KityM ...

  2. jsMind思维导图模式展示数据

    效果图: jsmind组件下载地址:https://files.cnblogs.com/files/fengyeqingxiang/jsmind.zip 后端代码,此处以C#编写的后台,Java或其他 ...

  3. Java,面试题,简历,Linux,大数据,常用开发工具类,API文档,电子书,各种思维导图资源,百度网盘资源,BBS论坛系统 ERP管理系统 OA办公自动化管理系统 车辆管理系统 各种后台管理系统

    Java,面试题,简历,Linux,大数据,常用开发工具类,API文档,电子书,各种思维导图资源,百度网盘资源BBS论坛系统 ERP管理系统 OA办公自动化管理系统 车辆管理系统 家庭理财系统 各种后 ...

  4. 轻松认识JVM运行时数据区域(使用思维导图)

    下面是个人阅读周志明编写的深入浅出Java虚拟机做成思维导图的笔记,线条.颜色和图片的视觉印象比起单纯文字笔记好得太多了,文字笔记的枯燥以及硬性记忆我就不再多说,特别对于JVM这块略微有点枯燥的知识, ...

  5. effective c++ 思维导图

    历时两个多月的时间,终于把effective c++又复习了一遍,比较慢,看的是英文版,之前看的时候做过一些笔记,但不够详细,这次笔者是从头到尾的翻译了一遍,加了一些标题,先记录到word里面,然后发 ...

  6. JavaSE教程-02Java基本语法-思维导图

    思维导图看不清楚时: 1)可以将图片另存为图片,保存在本地来查看 2)右击在新标签中打开放大查看 1.注释 定义:用于解释说明程序作用的文字 注释类别 单行注释 格式: //注释文字 多行注释 格式: ...

  7. Java基础(含思维导图)

    很早之前整理的Java基础的一些知识点,思维导图: 1.'别名现象' 对一个对象赋值另一个对象,会指向新的对象引用,赋值前的对象引用会由于不再被引用而被gc回收: 而基本类型则不同.基本类型存储了实际 ...

  8. 浅谈Java的主要学习要点_上海尚学堂java培训课程思维导图

    Java是一种可以撰写跨平台应用程序的面向对象的程序设计语言.Java 技术具有卓越的通用性.高效性.平台移植性和安全性,广泛应用于PC.数据中心.游戏控制台.科学超级计算机.移动电话和互联网,同时拥 ...

  9. C++第三章复习与总结(思维导图分享)

    在完成了第三章的学习后,为了便于日后的复习整理,我制作了一张思维导图,有需要的可以自取. 函数的定义与使用 带默认值的函数 在C++中我们可以为函数添加默认的参数值,在调用时可不传入或部分传入参数,为 ...

随机推荐

  1. 钉钉相关功能介入开发系列一:获取access_token

    获取access_token的基本代码,与微信不同的是钉钉的token正常情况下有效期为7200秒,有效期内重复获取返回相同结果,并自动续期,比微信方便多了 //基本信息 string appkey ...

  2. 集合源码分析[2]-AbstractList 源码分析

    AbstractList 类型:抽象类 接口的继承以及实现关系 继承AbstractCollection 实现List接口 典型方法实现解析 public List<E> subList( ...

  3. 将字符串存储到注册表中,长度一定是 strlen(text) + 1

    参考:https://docs.microsoft.com/en-us/windows/desktop/sysinfo/registry-value-types 将字符串存储到注册表中,长度参数一定要 ...

  4. nginx日志格式定义和nginx.conf配置模板说明

    在http的功能里添加log_format模块,内容如下: log_format main escape=json '{ "@timestamp": "$time_iso ...

  5. python计算斐波那契数列

    斐波那契数列就是黄金分割数列 第一项加第二项等于第三项,以此类推 第二项加第三项等于第四项 代码如下 这一段代码实现fib(n)函数返回第n项,PrintFN(m,n,i)函数实现输出第i项斐波那契数 ...

  6. (一)初探Maven之本地安装与配置本地仓库

    引言:          大多数IDE都会自带maven,如果你打开c盘用户目录,你会看到一个.m2文件这就是编译器自带maven的仓库父目录,但由于其在C盘和其他原因,一般我们会自己重新安装mave ...

  7. Stm32之通用定时器复习

    因为毕业设计要用到PWM调光很久都没用到Stm32的定时器,有些内容已经遗忘,为了回顾复习相关内容今天开下通用定时器这一章节的数据手册. 1.时钟 通用定时器一般是TIM2~TIM5,TIM1.TIM ...

  8. C# 用Serializer.ToXml()方法转换成两种格式的XML

    常见XML格式两种: 这种是属性的格式,实体的Model属性上面加上这个特性 [XmlAttribute] <AAA aa="/> 这种是标签的格式,实体的Model属性上面加上 ...

  9. 论文学习笔记--无缺陷样本产品表面缺陷检测 A Surface Defect Detection Method Based on Positive Samples

    文章下载地址:A Surface Defect Detection Method Based on Positive Samples 第一部分  论文中文翻译 摘要:基于机器视觉的表面缺陷检测和分类可 ...

  10. 肺结节CT影像特征提取(五)——肺结节CT影像ROI区域灰度直方图及其图形化

    在博客肺结节CT影像特征提取中,已经实现了肺结节的灰度.纹理和形态特征的提取.但是,对于进一步了解ROI区域像素值或者说CT值的分布来说,还存在一定的不足,不能够很好的显示ROI区域. 因此,本文将进 ...