【大数据】Neo4j图数据查询python前端返回值处理

1,142 阅读7分钟

image.png

前言,在昨天的文章中,我们使用了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"
    }
  ]
}

因此通过这个方法,就能生成前端所需数据,蟹蟹~