前言,在昨天的文章中,我们使用了python脚本做了数据库查询操作,完成单节点查询,两节点之间关系查询以及三节点关系查询的操作,均使用neo4j的cypher语句,使用match方法,对于单节点,仅需使用 match (n:labels) return n,或者 match (n:labels{name:'properties'}) return n,而关系查询则需要有两个节点的类别,才能够查询关系,如 match (n:company) -[r]-> (m:product) return n,m,其中r表示 两个节点的关系,今天文章将处理返回的数据,生成前端所需的数据格式。
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
G6
G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。旨在让关系变得透明,简单。让用户获得关系数据的 Insight。前端小姐姐要使用G6作为可视化方案,因此我们要把数据做成前端能够接受的格式,不同于echcart,G6提供了更加丰富的样式和交互,如collapse-expand-combo就提供了若图配置有布局,则该 behavior 被触发后会触发图的重新布局。若希望避免重新布局,可以通过监听 combo 点击事件和 graph.collapseExpandCombo API 控制收缩展开逻辑。而echart 缺乏交互性,因此使用G6是更好的选择。
同时,前端所需的数据格式如下:
import G6 from '@antv/g6';
// define the CSS with the id of your menu
// 我们用 insert-css 演示引入自定义样式
// 推荐将样式添加到自己的样式文件中
// 若拷贝官方代码,别忘了 npm install insert-css
insertCss(`
.g6-component-contextmenu {
position: absolute;
list-style-type: none;
padding: 10px 8px;
left: -150px;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid #e2e2e2;
border-radius: 4px;
font-size: 12px;
color: #545454;
}
.g6-component-contextmenu span {
cursor: pointer;
list-style-type:none;
list-style: none;
margin-left: 0px;
}
.g6-component-contextmenu span:hover {
color: #5B8FF9;
}
`);
const tipDiv = document.createElement('div');
tipDiv.innerHTML = `Double click to collapse/expand a combo; Context menu with right click to uncombo/re-combo. 双击 Combo 进行展开/收起;右键菜单选择解散/重组`;
document.getElementById('container').appendChild(tipDiv);
const data = {
nodes: [
{
id: '0',
comboId: 'a',
},
{
id: '1',
comboId: 'a',
},
{
id: '2',
comboId: 'a',
},
{
id: '3',
comboId: 'a',
},
...
],
edges: [
{
source: '0',
target: '1',
},
{
source: '0',
target: '2',
},
{
source: '0',
target: '3',
},
...
],
combos: [
{
id: 'a',
label: 'Combo A',
},
{
id: 'b',
label: 'Combo B',
},
{
id: 'c',
label: 'Combo C',
},
{
id: 'd',
label: 'Combo D',
parentId: 'b',
},
],
};
数据格式并不复杂,分别是节点nodes,边edges和节点类别combos,其中节点包含两个属性,自身Id和所属combos的Id,edges节点则是节点中自身Id的值,而combos的两个值则为节点中的combos id值和label,即节点大类标签。
而我们通过python中的match语句查询节点之间的关系,则返回结果如下所示:
{'id(n)': 6214, 'n': Node('company', name='haskell'), 'id(m)': 6273, 'm': Node('product', name='aeson'), 'id (o)': 6234, 'o': Node('cvenumberNode', name='CVE-2022-3433'), 'id(r)': 5259, 'r': 拥有(Node('company', name='haskell'), Node('product', name='aeson')), 'id(t)': 5297, 't': 暴露漏洞(Node('product', name='aeson'), Node('cvenumberNode', name='CVE-2022-3433'))}
{'id(n)': 6215, 'n': Node('company', name='brainvire'), 'id(m)': 6413, 'm': Node('product', name='disable_user_login'), 'id (o)': 6237, 'o': Node('cvenumberNode', name='CVE-2022-2350'), 'id(r)': 5242, 'r': 拥有(Node('company', name='brainvire'), Node('product', name='disable_user_login')), 'id(t)': 5299, 't': 暴露漏洞(Node('product', name='disable_user_login'), Node('cvenumberNode', name='CVE-2022-2350'))}
{'id(n)': 6220, 'n': Node('company', name='slack_morphism_project'), 'id(m)': 6295, 'm': Node('product', name='slack_morphism'), 'id (o)': 6657, 'o': Node('cvenumberNode', name='CVE-2022-39292'), 'id(r)': 5244, 'r': 拥有(Node('company', name='slack_morphism_project'), Node('product', name='slack_morphism')), 'id(t)': 5267, 't': 暴露漏洞(Node('product', name='slack_morphism'), Node('cvenumberNode', name='CVE-2022-39292'))}
{'id(n)': 6222, 'n': Node('company', name='misp-project'), 'id(m)': 6245, 'm': Node('product', name='malware_information_sharing_platform'), 'id (o)': 6298, 'o': Node('cvenumberNode', name='CVE-2022-42724'), 'id(r)': 5304, 'r': 拥有(Node('company', name='misp-project'), Node('product', name='malware_information_sharing_platform')), 'id(t)': 5248, 't': 暴露漏洞(Node('product', name='malware_information_sharing_platform'), Node('cvenumberNode', name='CVE-2022-42724'))}
{'id(n)': 6227, 'n': Node('company', name='solarwinds'), 'id(m)': 6714, 'm': Node('product', name='network_configuration_manager'), 'id (o)': 6407, 'o': Node('cvenumberNode', name='CVE-2021-35226'), 'id(r)': 5670, 'r': 拥有(Node('company', name='solarwinds'), Node('product', name='network_configuration_manager')), 'id(t)': 5653, 't': 暴露漏洞(Node('product', name='network_configuration_manager'), Node('cvenumberNode', name='CVE-2021-35226'))}
{'id(n)': 6238, 'n': Node('company', name='shortpixel'), 'id(m)': 6256, 'm': Node('product', name='enable_media_replace'), 'id (o)': 6656, 'o': Node('cvenumberNode', name='CVE-2022-2554'), 'id(r)': 5262, 'r': 拥有(Node('company', name='shortpixel'), Node('product', name='enable_media_replace')), 'id(t)': 5279, 't': 暴露漏洞(Node('product', name='enable_media_replace'), Node('cvenumberNode', name='CVE-2022-2554'))}
{'id(n)': 6242, 'n': Node('company', name='crealogix'), 'id(m)': 6296, 'm': Node('product', name='ebics_server'), 'id (o)': 6279, 'o': Node('cvenumberNode', name='CVE-2022-3442'), 'id(r)': 5302, 'r': 拥有(Node('company', name='crealogix'), Node('product', name='ebics_server')), 'id(t)': 5245, 't': 暴露漏洞(Node('product', name='ebics_server'), Node('cvenumberNode', name='CVE-2022-3442'))}
{'id(n)': 6244, 'n': Node('company', name='linuxmint'), 'id(m)': 6706, 'm': Node('product', name='warpinator'), 'id (o)': 6221, 'o': Node('cvenumberNode', name='CVE-2022-42725'), 'id(r)': 5247, 'r': 拥有(Node('company', name='linuxmint'), Node('product', name='warpinator')), 'id(t)': 5226, 't': 暴露漏洞(Node('product', name='warpinator'), Node('cvenumberNode', name='CVE-2022-42725'))}
{'id(n)': 6254, 'n': Node('company', name='premium-themes'), 'id(m)': 6236, 'm': Node('product', name='cryptocurrency_pricing_list_and_ticker'), 'id (o)': 6412, 'o': Node('cvenumberNode', name='CVE-2021-25044'), 'id(r)': 5241, 'r': 拥有(Node('company', name='premium-themes'), Node('product', name='cryptocurrency_pricing_list_and_ticker')), 'id(t)': 5261, 't': 暴露漏洞(Node('product', name='cryptocurrency_pricing_list_and_ticker'), Node('cvenumberNode', name='CVE-2021-25044'))}
{'id(n)': 6255, 'n': Node('company', name='resmush.it'), 'id(m)': 6216, 'm': Node('product', name='resmush.it_image_optimizer'), 'id (o)': 6291, 'o': Node('cvenumberNode', name='CVE-2022-2448'), 'id(r)': 5243, 'r': 拥有(Node('company', name='resmush.it'), Node('product', name='resmush.it_image_optimizer')), 'id(t)': 5222, 't': 暴露漏洞(Node('product', name='resmush.it_image_optimizer'), Node('cvenumberNode', name='CVE-2022-2448'))}
因此需要对数据进行处理,生成G6所需样式。
代码如下:
def formatG6Relations(self, itemList):
nodeIdDic = {}
combos = []
nodes = []
edges = []
testLine = itemList[0]
if testLine.get("r") != None:
combos.append({"id": 'a', "label": 'company'})
combos.append({"id": 'b', "label": 'product'})
if testLine.get("t") != None:
combos.append({"id": 'c', "label": 'cveNumber'})
for line in itemList:
for k, v in line.items():
if k == "r":
company = str(v).split(")-[:拥有 {}]->(")[0].lstrip("(")
product = str(v).split(")-[:拥有 {}]->(")[1].rstrip(")")
print(company)
print(product)
if company in nodeIdDic:
company_id = nodeIdDic.get(company)
nodes.append({"id": company_id, "comboId": "a"})
else:
nodeIdDic[company] = len(nodeIdDic)
nodes.append({"id": len(nodeIdDic), "comboId": "a"})
if product in nodeIdDic:
product_id = nodeIdDic.get(product)
nodes.append({"id": product_id, "comboId": "b"})
else:
nodeIdDic[product] = len(nodeIdDic)
nodes.append({"id": len(nodeIdDic), "comboId": "b"})
edges.append({"source": nodeIdDic.get(company), "target": nodeIdDic.get(product)})
if k == "t":
product = str(v).split("-[:暴露漏洞 {}]->(")[0].lstrip("(")
cveNumber = str(v).split("-[:暴露漏洞 {}]->(")[1].rstrip(")")
if product in nodeIdDic:
product_id = nodeIdDic.get(product)
nodes.append({"id": product_id, "comboId": "b"})
else:
nodeIdDic[product] = len(nodeIdDic)
nodes.append({"id": len(nodeIdDic), "comboId": "b"})
if cveNumber in nodeIdDic:
cveNumber_id = nodeIdDic.get(cveNumber)
nodes.append({"id": cveNumber_id, "comboId": "b"})
else:
nodeIdDic[cveNumber] = len(nodeIdDic)
nodes.append({"id": len(nodeIdDic), "comboId": "c"})
edges.append({"source": nodeIdDic.get(product), "target": nodeIdDic.get(cveNumber)})
return json.dumps({"nodes": nodes, "edges": edges, "combos": combos})
大致思路是,使用一个字典作为ID的唯一标识,遍历查询的返回结果,根据唯一标识id,生成对应的edges或者nodes节点,并将全部的返回结果使用json进行dumps,返回结果如下:
{
"nodes": [
{
"id": 1,
"comboId": "a"
},
{
"id": 2,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 3,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 4,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 5,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 6,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 7,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 8,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 9,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 10,
"comboId": "b"
},
{
"id": 0,
"comboId": "a"
},
{
"id": 11,
"comboId": "b"
}
],
"edges": [
{
"source": 0,
"target": 1
},
{
"source": 0,
"target": 2
},
{
"source": 0,
"target": 3
},
{
"source": 0,
"target": 4
},
{
"source": 0,
"target": 5
},
{
"source": 0,
"target": 6
},
{
"source": 0,
"target": 7
},
{
"source": 0,
"target": 8
},
{
"source": 0,
"target": 9
},
{
"source": 0,
"target": 10
}
],
"combos": [
{
"id": "a",
"label": "company"
},
{
"id": "b",
"label": "product"
}
]
}
因此通过这个方法,就能生成前端所需数据,蟹蟹~