【大数据】图数据库后端程序部署测试

127 阅读7分钟

image.png

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情

前言

昨天的文章中,我们描述了python flask部署python后端程序的原理,简而言之python flask虽然完成了部分server的功能,但是由于性能以及安全性较差,因此不适用于生产环境,由于请求经常以http服务的形式体现,因此仅仅使用uwsgi + python flask的形式不足以满足日常需求,引入nginx使得后端访问方式更加灵活与安全,如使用代理等技术,能够有效隐藏真实服务端口等,更加利于拓展。综上,python flask结合uwsgi与nginx是一个成熟的python web服务框架。今天将对之前的程序进行重构,并完成线上部署。


项目结构

image.png

其中程序分为app.py,文件夹中,config文件配置了neo4j数据库的链接方式,Controller文件夹保存了主要服务的实现,data文件夹是原始数据与数据插入脚本,Dto定义了部分数据传输的格式等,Utils文件夹保存了一些数据库链接、数据处理等基本工具单元。

neo4j.ini

[Neo4J]
REMOTE_URL=http://127.0.0.1:7474
REMOTE_PASS=XXX
REMOTE_USER=neo4j

其中REMOTE_URL是neo4j的连接地址,REMOTE_PASS是数据库连接密码,REMOTE_USER为用户名。

searchController.py

from Utils.connectUtils import connect2Neo4J
from Dto.ResultDto import ResultDto
from Utils.enumCode import Code
from Utils.enumMsg import Msg
from Utils.dataFormatUtils import dataFormat

class search4Node():
    def __init__(self):
        self.conn = connect2Neo4J().conn
        self.maxNodes = 300


    def getAllNodes(self, labels = None, properties = None, limit = None):
        cypher = "match (n) return n"
        if labels:
            cypher = f"match (n:{labels}) return id(n),n"
        if properties:
            cypher = "match (n:{0}{{name:'{1}'}}) return id(n),n".format(labels,properties)
        if limit:
            cypher += ' limit {}'.format(limit)
        else:
            cypher = f"match (n) return n limit {self.maxNodes}"
        print(cypher)
        result = self.conn.run(cypher)
        return result.data()


    def searchFirstLevel(self):
        result = self.conn.run("match (n:company{name:'cisco'}) -[r]-> (m:product) return m,r,n limit 20")
        return result.data()

    def get2PointRel(self, nodeA, nodeB, propertiesA=None, propertiesB=None, limit=50):
        try:
            cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB})  return id(m),m ,id(r),r,id(n),n"
            if propertiesA is not None and propertiesB is None:
                cypher = f"match (n:{nodeA}{{name:'{propertiesA}'}}) -[r]-> (m:{nodeB})  return id(m),m ,id(r),r,id(n),n"
            if propertiesB and propertiesA is None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}})  return id(m),m ,id(r),r,id(n),n"
            if propertiesA is not None and propertiesB is not None:
                cypher = f"match (n:{nodeA}{{name:'{propertiesA}'}}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) return id(m),m ,id(r),r,id(n),n"
            if limit == None:
                limit = 10
                cypher += ' limit {}'.format(limit)
            else:
                cypher += ' limit {}'.format(limit)
            print(cypher)
            result = self.conn.run(cypher)
            result = result.data()
            result = dataFormat().formatPointRelations(result)
            return ResultDto(code=Code.Success.value, msg=Msg.Success.value,data=result).retrunMsg
        except Exception as e:
            print(e)
            return ResultDto(code=Code.ServiceError.value, msg="{}:{}".format(Msg.ServiceError.value, e),
                             data="节点信息查询失败").retrunMsg

    def get3PointRel(self, nodeA, nodeB, nodeC, propertiesA=None, propertiesB=None, propertiesC=None, limit=10):
        try:
            cypher = ""
            if propertiesA is not None and propertiesB is None and propertiesC is None:
                cypher = f"match (n:{nodeA}{{name:'{propertiesA}'}}) -[r]-> (m:{nodeB}) - [t]->(o:{nodeC}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"
            if propertiesA is None and propertiesB is not None and propertiesC is None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) - [t]->(o:{nodeC}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"
            if propertiesA is  None and propertiesB is None and propertiesC is not None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}) - [t]->(o:{nodeC}{{name:'{propertiesC}'}}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"


            if propertiesA is not None and propertiesB is not None and propertiesC is None:
                cypher = f"match (n:{nodeA}{{name:'{propertiesA}'}}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) - [t]->(o:{nodeC}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"
            if propertiesA is not None and propertiesB is not None and propertiesC is  None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) - [t]->(o:{nodeC}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"
            if propertiesA is None and propertiesB is not None and propertiesC is not None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) - [t]->(o:{nodeC}{{name:'{propertiesC}'}}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"

            if propertiesA is not None and propertiesB is not None and propertiesC is not None:
                cypher = f"match (n:{nodeA}{{name:'{propertiesA}'}}) -[r]-> (m:{nodeB}{{name:'{propertiesB}'}}) - [t]->(o:{nodeC}{{name:'{propertiesC}'}}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"

            if propertiesA is None and propertiesB is None and propertiesC is None:
                cypher = f"match (n:{nodeA}) -[r]-> (m:{nodeB}) - [t]->(o:{nodeC}) return id(n) ,n ,id(m),m, id (o), o ,id(r) ,r ,id(t), t"
            if limit == None:
                limit = 10
                cypher += ' limit {}'.format(limit)
            else:
                cypher += ' limit {}'.format(limit)
            result = self.conn.run(cypher)
            result = dataFormat().formatPointRelations(result.data())
            return ResultDto(code=Code.Success.value, msg=Msg.Success.value,data=result).retrunMsg
        except Exception as e:
            return ResultDto(code=Code.ServiceError.value, msg="{}:{}".format(Msg.ServiceError.value, e),
                             data="节点信息查询失败").retrunMsg

数据查询部分和之前的类似,依旧使用的是cypher语句完成查询操作,根据参数不同生成不同的查询语句完成数据库查询操作。

ResultDto.py



class ResultDto(object):
    '''
    返回结果
    '''


    def __init__(self,code,msg,data):
        self.code = code
        self.msg = msg
        self.data = data
        self.ReturnMsg()

    def ReturnMsg(self):
        self.retrunMsg =  {"code": self.code, "msg": self.msg, "data": self.data}

定义了数据的返回形式,保护状态码,消息以及返回数据。

connectUtils.py

from configparser import RawConfigParser

from py2neo import Graph, Node, Relationship, NodeMatcher, NodeMatch,RelationshipMatcher


class connect2Neo4J():
    def __init__(self):
        self.getNeoCfg()
        self.conn = Graph(self.REMOTE_URL, auth=(self.REMOTE_USER, self.REMOTE_PASS))

    def getNeoCfg(self):
        config = RawConfigParser()
        # 获取配置文件的真实路径
        path = r"config/neo4j.ini"
        config.read(path, encoding="utf-8")
        self.REMOTE_URL = config.get("Neo4J","REMOTE_URL")
        self.REMOTE_PASS = config.get("Neo4J","REMOTE_PASS")
        self.REMOTE_USER = config.get("Neo4J","REMOTE_USER")



    def creatNode(self, labels, properties):
        insertNode = Node(labels, name=properties)
        self.conn.create(insertNode)
        return insertNode

    def creatRelationship(self, nodeA, nodeB, properties):
        relation = Relationship(nodeA, properties, nodeB)
        self.conn.create(relation)

    def searchNode(self, labels, limit=None):
        if limit:
            data = NodeMatch(self.conn, labels=frozenset({'{}'.format(labels)})).limit(limit)
        else:
            node_matcher = NodeMatcher(self.conn)
            data = node_matcher.match(labels)
        return data


    def getRelations(self,nodes = None,r_type = None,limit = None):
        relation = RelationshipMatcher(self.conn)
        relationData = relation.match(nodes=nodes, r_type='{}'.format(r_type)).limit(limit)
        return relationData

数据库连接工具类,首先加载配置文件,然后完成数据库的连接操作,生成数据库连接对象。

dataFormatUtils.py

数据处理的工具类

from Utils.connectUtils import connect2Neo4J


class dataFormat(object):
    def __init__(self):
        self.vlunCategory = {'company': 0, 'product': 1, 'cvenumberNode': 2}
        self.categories = [            {"name": "companyElement", "keyword": {}, "base": "companyElement"},            {"name": "productElement", "keyword": {}, "base": "productElement"},            {"name": "cvenumberElement", "keyword": {}, "base": "cvenumberElement"},        ]

    def formatEchartNodes(self, itemList):
        resList = []
        for index, item in enumerate(itemList):
            category = str(item["n"]).split(" {name:")[0].split(":")[1]
            categoryIndex = self.vlunCategory[category]
            name = item["n"].get("name")
            resList.append({
                "name": f"{name}",
                "value": 1,
                "category": categoryIndex
            })
        return resList

    def formatG6Nodes(self):
        pass

    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(")")
                    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 {"nodes": nodes, "edges": edges, "combos": combos}

    def formatPointRelations(self, result):
        nodes = []
        edges = []
        tempDic = {}
        for line in result:
            if line.get("n"):
                # 第一层
                # Node('company', name='teclib-edition')
                label = str(line.get("n")).split(" {name: '")[0].split(":")[1]
                value = str(line.get("n")).split(" {name: '")[1].rstrip("'})")
                if value not in tempDic:
                    # MATCH (n:company{name:"cisco"})-[r]->() RETURN COUNT(r)
                    childrenNum = \
                    connect2Neo4J().conn.run(f"MATCH (n:{label}{{name:'{value}'}})-[r]->() RETURN COUNT(r)").data()[                        0].get("COUNT(r)")
                    node = {
                        "id": value,
                        "name": value,
                        "level": 0,
                        "childrenNum": childrenNum,
                        "tag": value
                    }
                    nodes.append(node)
                    tempDic[value] = 1
            if line.get("m"):
                # 第二层
                label = str(line.get("m")).split(" {name: '")[0].split(":")[1]
                value = str(line.get("m")).split(" {name: '")[1].rstrip("'})")
                if value not in tempDic:
                    childrenNum = \
                    connect2Neo4J().conn.run(f"MATCH (n:{label}{{name:'{value}'}})-[r]->() RETURN COUNT(r)").data()[
                        0].get("COUNT(r)")
                    tags = str(line.get("n")).split(" {name: '")[1].rstrip("'})")
                    edge = {
                        "source": tags,
                        "target": value
                    }
                    edges.append(edge)
                    node = {
                        "id": value,
                        "name": value,
                        "level": 1,
                        "childrenNum": childrenNum,
                        "tag": value,
                        "isLeaf": False,
                        "tags": [
                            tags
                        ]
                    }
                    nodes.append(node)
                    tempDic[value] = 1
            if line.get("o"):
                # 第三层
                label = str(line.get("o")).split(" {name: '")[0].split(":")[1]
                value = str(line.get("o")).split(" {name: '")[1].rstrip("'})")
                if value not in tempDic:
                    tags = str(line.get("m")).split(" {name: '")[1].rstrip("'})")
                    edge = {
                        "source": tags,
                        "target": value
                    }
                    edges.append(edge)
                    node = {
                        "id": value,
                        "name": value,
                        "level": 2,
                        "isLeaf": True,
                        "tags": [
                            tags
                        ]
                    }
                    nodes.append(node)
                    tempDic[value] = 1

        return {"nodes": nodes, "edges": edges}

根据前端小姐姐的新的数据返回格式要求,更新了返回数据处理类,需要啥格式数据,制造啥数据格式。

enumCode.py

from enum import Enum

class Code(Enum):
    Error = 4001
    NameOrPwdError = 4002
    ParameterError = 4003
    NameNotExist = 4004
    ServiceError = 4005
    OriPasswordError = 4006
    UserNameExist = 4007
    RandomCodeError = 4009
    TokenError = 4010


    TokenExpire = 1001
    TokenParsingFailed = 1002

    Success = 200

enumMsg.py

from enum import Enum

class Msg(Enum):
    Error = "失败"
    Success = "成功"
    NameOrPwdError = "用户名或密码错误"
    TokenExpire = "Token已过期"
    TokenParsingFailed = "Token解析失败"
    ParameterError = "参数错误"
    NameNotExist = "用户名不存在"
    ServiceError = "内部服务异常"
    OriPasswordError = "原始密码错误"
    UserNameExist = "用户名重复"
    RandomCodeError = "验证码错误"
    TokenError = "token过期或不正确"

定义了一些错误类型和返回码。

app.py

from flask import Flask, request

from Utils.enumCode import Code
from Controller.searchController import search4Node
from Utils.dataFormatUtils import dataFormat
from Dto.ResultDto import ResultDto

app = Flask(__name__)

@app.route('/getRelations', methods=['GET'])
def getRelations():
    nodeA = request.args.get("nodeA")
    nodeB = request.args.get("nodeB")
    nodeC = request.args.get("nodeC")
    nodeAprop = request.args.get("nodeAprop")
    nodeBprop = request.args.get("nodeBprop")
    nodeCprop = request.args.get("nodeCprop")
    limit = request.args.get("limit")

    if nodeC == None and nodeCprop == None:
        return search4Node().get2PointRel(nodeA, nodeB,nodeAprop, nodeBprop,limit)
    else:
        return search4Node().get3PointRel(nodeA, nodeB, nodeC, nodeAprop, nodeBprop, nodeCprop, limit)


if __name__ == '__main__':
    app.run(host='127.0.0.1',port=5010)

主程序,目前只写了一个接口,完成了节点和关系的查询操作

项目通过uwsgi和nginx部署之后,在远端即可用postman完成调用,如下:

image.png

但是存在一个问题,neo4j的查询,返回50个节点基本是按照自己的处理逻辑,如何返回想要的,需要在研究一下,明天将使用redis完成前端初始页面的展示,蟹蟹~