最近开始做自己的第一个开源项目:一个基于思维导图的测试用例管理系统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. [mooc]open course on github

    来自多位GitHub网友在GitHub分享的几组学习课程项目, 学习课程包含清华,北大,浙大,中科大,上海交大, 等中国多所名校的英语,AI高数,人工智能等课程以及一些讲义考题. 如果你想了解这些大学 ...

  2. Re.FFT

    前言 上虽然算是学过了但是实质上还是根本什么都不会 看大佬们的模板去A了模题(手动滑稽) 于是下定决心要理解FFT的代码 一些的证明主要是从算法导论和两位大佬的博客上学的 大佬1  大佬2 在这过程中 ...

  3. centos7环境搭建命令List

    npm -ivh jdk-8u191-linux-x64.rpm adduser sai passwd sai whereis sudoers vim /etc/sudoers rpm -qa | g ...

  4. Java 8 特性 —— 默认方法和静态方法

    Java 8 新增了接口的默认方法.简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法.我们只需在方法名前面加个 default 关键字即可实现默认方法. 为什么要有这个特性?之前的 ...

  5. CF_528D

    一句话题意 给你两个串s.t,长度为n.m,字符集为"ATGC",当且仅 当[i - k; i + k]中存在一个j,使得s[j ] = t[x]时,s[i ]可以 和t[x]匹配 ...

  6. Java Web乱码原因与解决

    Java Web乱码原因与解决 一.了解编码常识: 1.ASCII 码 众所周知,这是最简单的编码.它总共可以表示128个字符,0~31是控制字符如换行.回车.删 除等,32~126是打印字符,可以通 ...

  7. (六) 编写vivid

    title: 编写vivid date: 2019/4/23 19:40:00 toc: true --- 编写vivid 新内核对video_buf的封装更好了,很多函数基本上套个名字就好了,这个可 ...

  8. 环境判断:区别h5打开还是weixin打开?

    var source_platform = '' , pf_source = 2; //系统平台 //区别是否是 微信浏览器 , source_platform 系统平台 2018.12.6新增 va ...

  9. 【作业3.0】HansBug的第三次博客规格总结

    转眼间第三次作业了,似乎需要说点啥,那就说点. 规格&工业 说到这个,不得不提一下软件开发的发展史. 历史的进程 早在上世纪50年代,就已经有早期的编程语言出现,也开始有一些程序编写者出现(多 ...

  10. Mysql5.7数据导出提示--secure-file-priv选项问题的解决方法

    mysql可使用into outfile参数把表中的数据到处到csv,示例如下: select user_id from weibo_comment into outfile '/home/dazha ...