Golang + Gin + cytocsape.js + neo4j

Thanks for following topic:

  1. Go-Gin 学习笔记 github-link 【from: 夜未央天未明】
    博主用清晰的代码结构很好的展示了Gin 框架, 所以本文不再赘述有关Gin 框架的内容
  2. 用cytoscape.js展示neo4j网络关系图【from: 钟柱】
    作者所用事例简单清晰,解释的很详细,有助于初学者上手

本文将focus 在:

  • neo4j golang access api
  • response jquery neo4j data with Gin

Neo4j API

在neo4j 的官方文档中,给出了golang api 的官方文档:
Using Neo4j from Go
并且在文末提供了 expample github project 的链接:
https://github.com/neo4j-examples/golang-bolt-movie-example
但是,该项目使用的api 在五年前已经不再维护了,所提供的API 也无法访问neo4j 2.x
所以,请不要再使用此driver去开发neo4j的项目了。这个项目名称叫:

  • github.com/johnnadratowski/golang-neo4j-bolt-driver

如果你所借鉴的代码还在使用以下声明,请及时替换,并且根据官方新的driver 进行修改:

import (

    "github.com/johnnadratowski/golang-neo4j-bolt-driver"
)

新的官方driver 在这里
https://github.com/neo4j/neo4j-go-driver
可以这样引用:

import (
    "github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

安装的时候,最好在自己Go 的安装目录下建立v4子目录,然后download : neo4j v4.2.1 source code解压后copy 到 你的v4 目录下。
Btw, 我使用的是 VS code. 是在vs code 的terminal 窗口下安装的Gin 和 neo4j:

  • go get -u -v github.com/gin-gonic/gin
  • go get github.com/neo4j/neo4j-go-dirver/neo4j

write API可以参见driver 本身提供的例子:
utils_test.go

我自写了两个 read API: 读 node 和 读relationship
返回的是 slice: []neo4j.Node 和 []neo4j.Relationship

package database

import (
    "log"
    "github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

func CreateDriver(uri, username, password string) (neo4j.Driver, error) {
    return neo4j.NewDriver(uri, neo4j.BasicAuth(username, password, ""))
}

func CloseDriver(driver neo4j.Driver) error {
    return driver.Close()
}

func NodeCreate(driver neo4j.Driver, Cypher string, DB string) error {

    session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite})
    defer session.Close()

    _, err := session.WriteTransaction(func(tx neo4j.Transaction) (interface{}, error) {
        result, err := tx.Run(Cypher, nil)
        if err != nil {
            log.Println("wirte to DB with error:", err)
            return nil, err
        }
        return result.Consume()
    })

    return err
}

func NodeQuery(driver neo4j.Driver, Cypher string, DB string) ([]neo4j.Node, error ) {

    var list []neo4j.Node
    session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
    defer session.Close()
    _, err := session.ReadTransaction(func(tx neo4j.Transaction)(interface{}, error) {
        result, err := tx.Run(Cypher, nil)
        if err != nil {
            return nil, err
        }

        for result.Next() {
            record := result.Record()
            if value, ok := record.Get("n"); ok {
                node  := value.(neo4j.Node)
                list = append(list, node)
            }
        }
        if err = result.Err(); err != nil {
            return nil, err
        }

        return list, result.Err()
    })

    if err != nil {
        log.Println("Read error:", err)
    }
    return list, err
}

func EdgeQuery(driver neo4j.Driver, Cypher string, DB string) ([]neo4j.Relationship, error ) {

    var list []neo4j.Relationship
    session := driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
    defer session.Close()
    _, err := session.ReadTransaction(func(tx neo4j.Transaction)(interface{}, error) {
        result, err := tx.Run(Cypher, nil)
        if err != nil {
            log.Println("EdggeQuery Run failed: ", err)
            return nil, err
        }
        for result.Next() {
            record := result.Record()
            if value, ok := record.Get("r"); ok {
                relationship  := value.(neo4j.Relationship)
                list = append(list, relationship)

            }
        }
        if err = result.Err(); err != nil {
            return nil, err
        }
        return list, result.Err()
    })

    if err != nil {
        log.Println("Read error:", err)
    }
    return list, err
}

我在application 里调用的这两个 read API 的example code:
(虽然预留了db name,但是neo4j community 版不支持 multiple database,所以暂时没用)

package models

import (
    "fmt"
    "log"
    "strconv"
    "../database"
)

type Aomobj struct {
    NodeId          string  json:"id"          form:"id"
    ObjId           string  json:"objId"       form:"objId"
    ObjString       string  json:"name"        form:"name"
}

type Aomedge struct {
    EdgeId          string  json:"id"          form:"id"
    EdgeName        string  json:"relationship" form:"relationship"
    StartId         string  json:"source"      form:"source"
    EndId           string  json:"target"      form:"target"
}

type NodeData  struct {
    Data Aomobj json:"data" form:"data"
}
type EdgeData  struct {
    Data Aomedge    json:"data" form:"data"
}

var (
    neo4jURL = "bolt://localhost:7687"
)

func GetAomObjList( count int32) (nodes []NodeData) {

    nodes = make([]NodeData, 0)

    driver, err := database.CreateDriver(neo4jURL, "neo4j", "neo4j")
    if err != nil {
        log.Println("error connecting to neo4j:", err)
        return nil
    }

    data, err := database.NodeQuery(driver, fmt.Sprintf("MATCH (n:AOM) RETURN  n LIMIT %d", count ), "")

    for i:=0; i < len(data); i++ {
         var node NodeData
         node.Data.NodeId = strconv.FormatInt(data[i].Id, 10)
         node.Data.ObjId  = data[i].Props["OID"].(string)
         node.Data.ObjString = data[i].Props["Desc"].(string)

         nodes = append(nodes, node)
    }

    database.CloseDriver(driver)
    return nodes
}

func GetAomObjRelationship( count int32) (Edges []EdgeData) {

    Edges = make([]EdgeData, 0)

    driver, err := database.CreateDriver(neo4jURL, "neo4j", "neo4j")
    if err != nil {
        log.Println("error connecting to neo4j:", err)
        return nil
    }

    data, err := database.EdgeQuery(driver, fmt.Sprintf("MATCH ()-[r]->() RETURN r LIMIT %d",count ), "")

    for i:=0; i < len(data); i++ {
        var edge EdgeData
         edge.Data.EdgeId = "r" + strconv.FormatInt(data[i].Id,10)
         edge.Data.EdgeName =  data[i].Type
         edge.Data.StartId = strconv.FormatInt(data[i].StartId, 10)
         edge.Data.EndId = strconv.FormatInt(data[i].EndId,10)
         Edges = append(Edges, edge)
    }

    database.CloseDriver(driver)
    return Edges
}

之所以要在app 数据结构(Aomobj 和 Aomedge)外面套一层 “data” 封装,是为了后面返回给cytoscape.js 的时候方便些.

注意 !!!

edge.Data.EdgeId = "r" + strconv.FormatInt(data[i].Id,10)

我在生成edge ID 的时候,增加了字母 “r” (relationship)而不是直接使用了neo4j分配的ID,因为neo4j生成的edge id 和 node id 是有重叠(overlap)的,所以在此要和node 的id 区分开,因为:

*** cytoscape.js &#x4E0D;&#x5141;&#x8BB8;edge &#x548C;node &#x7684;id &#x6709;&#x51B2;&#x7A81;&#xFF01;&#xFF01;&#xFF01;**

please reference:cytoscape.issue 779
每个node 的结构可以在neo4j 的brower 里查看, 这里给出一个例子,便于大家理解:

Golang + Gin + cytocsape.js + neo4j
图片里的就是一个 aomObj node.

“identity” 字段是 neo4j 自动分配的,数据类型 int64, 存在neo4j.Node.Id 中。
我在 app 读取的语句:

    node.Data.NodeId = strconv.FormatInt(data[i].Id, 10)

之所以转成string 类型,是因为在传递给cytoscape.js 的时候,所有字段都是string 类型的。
labels 也可以读取到,它对应着字段:neo4j.Node.Labels
* 注意 是labels,因为一个node 支持被mark 上多种label

其它的属性保存在neo4j.Node.Props 中,需要根据这个node 的已有属性去读取。比如我创建这个node 的时候,建立的”OID” 属性,那么读的时候 就这样:

    data[i].Props["OID"].(string)

关于 “Edge”, 也就是relationship 和 node 一样,有id 和属性,但是没有label. it looks like this:

Golang + Gin + cytocsape.js + neo4j

在Gin 里返回 jquery 的结果

在web browser 上的点击,触发里浏览器向server 发送请求,server 回应了http 200 和 一个html 后,在浏览器端运行了响应的js, 在此以 “钟柱” 的code.js 为例:

$(function(){
  $.get('/home/graph', function(result) {
    console.log('come into jquery GET')
    var style = [
        {
            selector: 'node',
            css: {
                'content': 'data(name)',
                'color': 'white',
                'text-valign': 'center',
                'text-outline-color': '#6699FF',
                'background-color': '#6699FF',
                'height': 60,
                'width': 60,
                'font-size': '8px',
                'font-weight': 'bold',
            }
        },
        {
            selector: 'edge',
            css: {
                'content': 'data(relationship)',
                'curve-style': 'bezier',
                'target-arrow-shape': 'triangle',
                'line-color': '#CCCCCC',
                'width': 1,
                'font-size': 8px',
            }
        }
    ];

    var cy = cytoscape({
      container: document.getElementById('cy'),
      style: style,
      layout: { name: 'cose'},
      elements: result.elements
      console.log('Get data in Jquery')
      console.log(result.elements)
    });
  }, 'json');
});

code.js trigger GET 请求,server 端响应这个请求后,返回 result 参数:

    elements: result.elements

这个result 参数是json 格式的,用于传递给cytoscape.js使用
大致的格式 looks like this:

{
  "elements": {
    "edges": [
      {"data": {"relationship": "isNotFriend", "source": "16", "target": "318"}},
    ],
    "nodes": [
      {"data": {"id": "16", "label": "AOM", "OID": 100, "name": "Tom"}},
      {"data": {"id": "318", "label": "AOM", "OID": "900", "name": "Jerry"}},
    ]
  }
}

我们用读取API 从neo4j 中读取数据后,只要组织成以上结构,然后调用Gin.H{} 就可以了,不需要调用encoding.json 方法.

注意所有需要转成json 格式的变量名称,依旧需要遵循首字母大写的原则

package apis

import (
    "log"
    "net/http"
    "../models"
    "github.com/gin-gonic/gin"
)

type elements struct {
     Nodes  []models.NodeData   json:"nodes" form:"nodes"
     Edges  []models.EdgeData   json:"edges" form:"edges"
}

func GetAomObj(c *gin.Context) {

    log.Println("call GetAomObj")
    nodes :=  models.GetAomObjList(200)
    edges := models.GetAomObjRelationship(200)

    e :=elements{Nodes:nodes, Edges: edges}

    c.JSON(http.StatusOK, gin.H{"elements": e})
}

打开浏览器的调试功能,可以看到js 的log和返回的数据:

Golang + Gin + cytocsape.js + neo4j

Original: https://blog.csdn.net/weixin_44104670/article/details/113075483
Author: 带枷头陀
Title: Golang + Gin + cytocsape.js + neo4j

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/595850/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球