栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Golang + Gin + cytocsape.js + neo4j

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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)
//				log.Println("Edgeid:", relationship.Id, ">>>Node:", relationship.StartId, "---", relationship.Type, "--->","Node:",relationship.EndId)
			}
		}
		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 ), "")

	//log.Println("Lenght of data is :", len(data))
	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)   /// OID 是我自己创建 neo4j db entry 的时候,添加的私有属性
		 node.Data.ObjString = data[i].Props["Desc"].(string) /// "Desc" is same with "OID", 私有属性
		 
		 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 ), "")

	//log.Println("Lenght of data is :", len(data))
	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 不允许edge 和node 的id 有冲突!!!**

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

图片里的就是一个 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:

在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',	//set node style
            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',	// set egde style
            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和返回的数据:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945252.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号