效果如下:
搜索李淳罡:
添加节点和关系,在这里添加李淳罡-喜欢-绿袍,如下多出李淳罡和绿袍的关系和绿袍的节点
在neo4j中,存储如下图
SDN似乎无法任意存储关系,故在这里使用的neo4j-java-driver完成的以上功能:
[neo4jConfig.java]
加载驱动和提供session
package com.sandalen.water.config;
import com.sandalen.water.PropertiesClass.Neo4jProperties;
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Neo4jConfig {
@Autowired
private Neo4jProperties neo4jProperties;
@Bean(name="NeoSession")
public Session initDriver(){
Session session = null;
try
{
Driver driver = GraphDatabase.driver(neo4jProperties.getURI(), AuthTokens.basic(neo4jProperties.getUsername(), neo4jProperties.getPassword()));
session = driver.session();
return session;
}
catch (Exception e){
e.printStackTrace();
return session;
}
}
}
[CypherUtils.java]
这里主要定义了一些cypher语句
package com.sandalen.water.other;
import org.springframework.stereotype.Component;
@Component
public class CypherUtils {
public static String createSingle(String entity){
return "merge (m:Person{name:\""+entity+"\"}) return m";
}
public static String createRelationByTwoNodes(String entityFrom,String relation,String entityTo){
return "match(m:Person{name:\""+entityFrom+"\"}),(n:Person{name:\""+entityTo+"\"}) merge (m)-[r:"+relation+"]->(n) return m,n,r";
}
public static String searchAll(String entityName){
if(entityName == "" || entityName == null){
return "match (m)-[edge]->(n) return m,edge,n";
}
return "match (m)-[edge]-(n) where n.name='" + entityName + "' return m,edge,n";
}
}
[Neo4jUtils.java]
添加、查找关系和节点的逻辑实现,其中添加的逻辑是先查询输入的两个实体,若任意实体不存在则先创建,再根据这两个节点添加关系。
查找的逻辑很简单,不再赘述。
package com.sandalen.water.util;
import com.sandalen.water.other.CypherUtils;
import org.neo4j.driver.v1.*;
import org.neo4j.driver.v1.types.Relationship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class Neo4jUtils {
private static Session session;
@Autowired
public Neo4jUtils(Session NeoSession){
Neo4jUtils.session= NeoSession;
}
public static boolean create(String entityFrom,String relation,String entityTo){
try {
StatementResult statementResult = session.run(CypherUtils.createSingle(entityFrom));
List<Record> entityFromlist = statementResult.list();
StatementResult entityToResult = session.run(CypherUtils.createSingle(entityTo));
List<Record> entityToList = entityToResult.list();
if(entityFromlist.size() == 0){
session.run(CypherUtils.createSingle(entityFrom));
}
if(entityToList.size() == 0){
session.run(CypherUtils.createSingle(entityTo));
}
StatementResult result = session.run(CypherUtils.createRelationByTwoNodes(entityFrom, relation, entityTo));
return true;
}
catch (Exception e){
e.printStackTrace();
return false;
}
}
public static Map<String,Object> searchAll(String entityName){
StatementResult result = session.run(CypherUtils.searchAll(entityName));
List<Record> list = result.list();
Map<String,Object> resultMap = new HashMap<>();
Set<String> nodes = new HashSet<>();
List<String> relationships = new ArrayList<>();
for (Record r : list){
String start_node = r.get("m").get("name").toString().replace("\"","");
String relationship = r.get("edge").asRelationship().type().replace("\"","");
String end_node = r.get("n").get("name").toString().replace("\"","");
// System.out.println(start_node + "-" + relationship + "->" + end_node );
relationship = start_node + "-" + relationship + "-" + end_node;
nodes.add(start_node);
nodes.add(end_node);
relationships.add(relationship);
}
resultMap.put("nodes",nodes);
resultMap.put("relationships",relationships);
return resultMap;
}
}
[controller]
package com.sandalen.water.controller;
import com.sandalen.water.bean.RespBean;
import com.sandalen.water.service.KgService;
import com.sandalen.water.util.Neo4jUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RequestMapping("/kg")
@RestController
public class KgController {
@Autowired
private KgService kgService;
@RequestMapping("/saveEntity")
public RespBean saveEntity(String entityFrom,String relation,String entityTo){
boolean isCreated = Neo4jUtils.create(entityFrom, relation, entityTo);
if(isCreated){
return RespBean.ok("创建数据成功");
}
return RespBean.error("创建失败");
}
@RequestMapping("/search")
public RespBean searchAll(String entityName){
Map<String, Object> map = Neo4jUtils.searchAll(entityName);
return RespBean.ok("获取数据成功",map);
}
}
[前端]
前端代码只提供页面的,我使用的是element Ui和vue
<template>
<div class="kgContainer">
<div class="alert alert-info" role="alert" style="margin-bottom: 20px;">
<h1>Prompt</h1>
<font size="4">输入你想要搜索的知识</font>
</div>
<div class="search" style="text-align: center;margin-bottom: 50px;">
<el-input v-model="entityName" placeholder="请输入内容" style="width: 400px;"></el-input>
<el-button type="primary" icon="el-icon-search" @click="initData">搜索</el-button>
<el-input v-model="form.entityFrom" placeholder="请输入内容" style="width: 100px;"></el-input>
<el-input v-model="form.relation" placeholder="请输入内容" style="width: 100px;"></el-input>
<el-input v-model="form.entityTo" placeholder="请输入内容" style="width: 100px;"></el-input>
<el-button type="primary" icon="el-icon-search" @click="add">添加</el-button>
</div>
<div class="kgShow" id="kgShow" style="width: 100%;">
</div>
<!--<div class="test" id="tst" style="width: 100%;height: 500px;"></div>-->
</div>
</template>
<script>
var echarts = require('echarts');
export default {
data() {
return {
entityName: '',
option: '',
form: {
entityFrom: '',
relation: '',
entityTo: ''
},
nodes: [],
relationships: []
}
},
mounted() {
this.initData()
this.tst()
},
inject: ['reload'],
methods: {
initData() {
this.$store.dispatch('kg/search', this.entityName).then(response => {
const nodes = response.nodes
const relationship = response.relationships
const nodesList = []
for(let i = 0; i < nodes.length; i++) {
const tmp = {
name: nodes[i],
symbolSize: 50,
itemStyle: {
normal: {
show: true,
}
}
}
nodesList.push(tmp)
}
const links = []
for(let i = 0; i < relationship.length; i++) {
const relationshipArray = relationship[i].split("-")
const tmp = {
source: relationshipArray[0],
target: relationshipArray[2],
name: relationshipArray[1]
}
links.push(tmp)
}
const option = {
title: {
text: '雪中悍刀行'
},
tooltip: {
formatter: function(x) {
return x.data.name;
}
},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
series: [{
type: 'graph',
layout: 'force',
symbolSize: 80,
roam: true,
label: {
normal: {
show: true,
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
edgeLabel: {
normal: {
textStyle: {
fontSize: 20
}
}
},
force: {
repulsion: 3000, //斥力
edgeLength: [20, 80] //默认距离
},
// layout:'circular',
draggable: true,
lineStyle: {
normal: {
width: 2,
color: '#4b565b',
curveness: 0.2,
length: 20
}
},
edgeLabel: {
normal: {
show: true,
formatter: function(x) {
return x.data.name;
}
}
},
data: nodesList,
links: links
}]
}
this.option = option
this.showKg()
})
},
add() {
this.$store.dispatch('kg/save', this.form).then(response => {
if(response.status == 200) {
this.$message({
message: response.msg,
type: 'success'
})
this.reload()
}
})
},
getInfo(params) {
alert(params)
},
showKg() {
const kgShow = document.getElementById('kgShow')
const myChart = echarts.init(kgShow, 'light')
myChart.setOption(this.option)
let data = myChart._model.option.series[0].data;
myChart.on("click", (chartParam) => {
// console.log(myChart._model)
// console.log(chartParam)
const entityName = data[chartParam.dataIndex].name
this.entityName = entityName
this.initData()
});
myChart.on("mouseover", (chartParam) => {
console.log(chartParam)
})
}
}
}
</script>
<style>
.kgContainer {
width: 100%;
background-color: #FFFFFF;
padding: 32px;
}
.kgShow {
width: 100%;
height: 500px;
}
</style>