Feat: 实现知识图谱数据库的检索方法,并对外提供接口
This commit is contained in:
@@ -40,7 +40,10 @@ type Config struct {
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
if err := godotenv.Load("../config/.env"); err != nil {
|
||||
var err error
|
||||
err = godotenv.Load("../config/.env")
|
||||
err = godotenv.Load("./config/.env")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading .env file: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1",
|
||||
"label": "人工智能",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 1,
|
||||
"description": "计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的机器",
|
||||
"aliases": ["机器智能", "AI"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"label": "机器学习",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.9,
|
||||
"description": "人工智能的一个子领域,让计算机系统能够从数据中学习和改进"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"label": "深度学习",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.8,
|
||||
"description": "基于人工神经网络的机器学习方法"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"label": "神经网络",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.7,
|
||||
"description": "受人脑启发的计算模型"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"label": "TensorFlow",
|
||||
"type": "工具",
|
||||
"properties": {
|
||||
"importance": 0.6,
|
||||
"language": "Python, C++",
|
||||
"author": "Google"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"label": "PyTorch",
|
||||
"type": "工具",
|
||||
"properties": {
|
||||
"importance": 0.6,
|
||||
"language": "Python",
|
||||
"author": "Meta"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"label": "自然语言处理",
|
||||
"type": "应用",
|
||||
"properties": {
|
||||
"importance": 0.7,
|
||||
"description": "处理和理解人类语言的技术"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"label": "计算机视觉",
|
||||
"type": "应用",
|
||||
"properties": {
|
||||
"importance": 0.7,
|
||||
"description": "让计算机理解和解析视觉信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"label": "强化学习",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.6,
|
||||
"description": "通过与环境交互来学习最优策略"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"label": "监督学习",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.5,
|
||||
"description": "使用标记数据进行学习"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"label": "GPT",
|
||||
"type": "应用",
|
||||
"properties": {
|
||||
"importance": 0.8,
|
||||
"author": "OpenAI",
|
||||
"description": "Generative Pre-trained Transformer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"label": "CNN",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.6,
|
||||
"description": "卷积神经网络,用于处理网格状数据"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"label": "RNN",
|
||||
"type": "概念",
|
||||
"properties": {
|
||||
"importance": 0.6,
|
||||
"description": "循环神经网络,用于处理序列数据"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "14",
|
||||
"label": "数据挖掘",
|
||||
"type": "应用",
|
||||
"properties": {
|
||||
"importance": 0.5,
|
||||
"description": "从大量数据中发现模式和知识"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e1",
|
||||
"source": "1",
|
||||
"target": "2",
|
||||
"label": "包含",
|
||||
"type": "关系"
|
||||
},
|
||||
{
|
||||
"id": "e2",
|
||||
"source": "2",
|
||||
"target": "3",
|
||||
"label": "子领域",
|
||||
"type": "关系"
|
||||
},
|
||||
{
|
||||
"id": "e3",
|
||||
"source": "3",
|
||||
"target": "4",
|
||||
"label": "基于",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e4",
|
||||
"source": "3",
|
||||
"target": "5",
|
||||
"label": "框架",
|
||||
"type": "工具关系"
|
||||
},
|
||||
{
|
||||
"id": "e5",
|
||||
"source": "3",
|
||||
"target": "6",
|
||||
"label": "框架",
|
||||
"type": "工具关系"
|
||||
},
|
||||
{
|
||||
"id": "e6",
|
||||
"source": "1",
|
||||
"target": "7",
|
||||
"label": "应用",
|
||||
"type": "应用领域"
|
||||
},
|
||||
{
|
||||
"id": "e7",
|
||||
"source": "1",
|
||||
"target": "8",
|
||||
"label": "应用",
|
||||
"type": "应用领域"
|
||||
},
|
||||
{
|
||||
"id": "e8",
|
||||
"source": "2",
|
||||
"target": "9",
|
||||
"label": "包含",
|
||||
"type": "关系"
|
||||
},
|
||||
{
|
||||
"id": "e9",
|
||||
"source": "2",
|
||||
"target": "10",
|
||||
"label": "包含",
|
||||
"type": "关系"
|
||||
},
|
||||
{
|
||||
"id": "e10",
|
||||
"source": "7",
|
||||
"target": "11",
|
||||
"label": "技术实现",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e11",
|
||||
"source": "3",
|
||||
"target": "12",
|
||||
"label": "主要架构",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e12",
|
||||
"source": "3",
|
||||
"target": "13",
|
||||
"label": "主要架构",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e13",
|
||||
"source": "8",
|
||||
"target": "12",
|
||||
"label": "核心技术",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e14",
|
||||
"source": "7",
|
||||
"target": "13",
|
||||
"label": "核心技术",
|
||||
"type": "技术依赖"
|
||||
},
|
||||
{
|
||||
"id": "e15",
|
||||
"source": "2",
|
||||
"target": "14",
|
||||
"label": "相关技术",
|
||||
"type": "相关"
|
||||
},
|
||||
{
|
||||
"id": "e16",
|
||||
"source": "5",
|
||||
"target": "12",
|
||||
"label": "支持",
|
||||
"type": "技术支持"
|
||||
},
|
||||
{
|
||||
"id": "e17",
|
||||
"source": "6",
|
||||
"target": "13",
|
||||
"label": "支持",
|
||||
"type": "技术支持"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8,22 +8,22 @@ import (
|
||||
)
|
||||
|
||||
type GraphHandler struct {
|
||||
dataService *services.DataService
|
||||
service services.GraphService
|
||||
}
|
||||
|
||||
func NewGraphHandler(dataService *services.DataService) *GraphHandler {
|
||||
func NewGraphHandler(service services.GraphService) *GraphHandler {
|
||||
return &GraphHandler{
|
||||
dataService: dataService,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *GraphHandler) GetGraphData(c *gin.Context) {
|
||||
data := h.dataService.GetGraphData()
|
||||
data := h.service.GetGraphData()
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
func (h *GraphHandler) GetStats(c *gin.Context) {
|
||||
stats := h.dataService.GetStats()
|
||||
stats := h.service.GetStats()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"stats": stats,
|
||||
"timestamp": gin.H{
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type NodeHandler struct {
|
||||
dataService *services.DataService
|
||||
service services.GraphService
|
||||
}
|
||||
|
||||
func NewNodeHandler(dataService *services.DataService) *NodeHandler {
|
||||
func NewNodeHandler(service services.GraphService) *NodeHandler {
|
||||
return &NodeHandler{
|
||||
dataService: dataService,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (h *NodeHandler) GetNodeByID(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
node, found := h.dataService.GetNodeByID(nodeID)
|
||||
node, found := h.service.GetNodeByID(nodeID)
|
||||
if !found {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "Not Found",
|
||||
@@ -49,7 +49,7 @@ func (h *NodeHandler) GetNeighbors(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
neighbors, found := h.dataService.GetNeighbors(nodeID)
|
||||
neighbors, found := h.service.GetNeighbors(nodeID)
|
||||
if !found {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "Not Found",
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type SearchHandler struct {
|
||||
dataService *services.DataService
|
||||
service services.GraphService
|
||||
}
|
||||
|
||||
func NewSearchHandler(dataService *services.DataService) *SearchHandler {
|
||||
func NewSearchHandler(service services.GraphService) *SearchHandler {
|
||||
return &SearchHandler{
|
||||
dataService: dataService,
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (h *SearchHandler) SearchNodes(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
results := h.dataService.SearchNodes(query)
|
||||
results := h.service.SearchNodes(query)
|
||||
|
||||
c.JSON(http.StatusOK, results)
|
||||
}
|
||||
|
||||
+9
-7
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
|
||||
"knowledge-graph-backend/config"
|
||||
"knowledge-graph-backend/handlers"
|
||||
neo4jClient "knowledge-graph-backend/neo4j"
|
||||
"knowledge-graph-backend/services"
|
||||
)
|
||||
|
||||
@@ -18,14 +20,14 @@ func main() {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
dataService, err := services.NewDataService(cfg.Data.FilePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize data service: %v", err)
|
||||
}
|
||||
driver := neo4jClient.NewDriver(cfg.Neo4j)
|
||||
defer driver.Close(context.Background())
|
||||
|
||||
graphHandler := handlers.NewGraphHandler(dataService)
|
||||
searchHandler := handlers.NewSearchHandler(dataService)
|
||||
nodeHandler := handlers.NewNodeHandler(dataService)
|
||||
graphService := services.NewNeo4jService(driver)
|
||||
|
||||
graphHandler := handlers.NewGraphHandler(graphService)
|
||||
searchHandler := handlers.NewSearchHandler(graphService)
|
||||
nodeHandler := handlers.NewNodeHandler(graphService)
|
||||
|
||||
router := gin.Default()
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||
|
||||
"knowledge-graph-backend/models"
|
||||
)
|
||||
|
||||
type Neo4jService struct {
|
||||
driver neo4j.DriverWithContext
|
||||
}
|
||||
|
||||
func NewNeo4jService(driver neo4j.DriverWithContext) *Neo4jService {
|
||||
return &Neo4jService{driver: driver}
|
||||
}
|
||||
|
||||
func (s *Neo4jService) GetGraphData() models.GraphData {
|
||||
ctx := context.Background()
|
||||
var nodes []models.Node
|
||||
var edges []models.Edge
|
||||
|
||||
nodeResult, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
"MATCH (n) RETURN n",
|
||||
map[string]any{},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Error querying nodes: %v\n", err)
|
||||
return models.GraphData{}
|
||||
}
|
||||
for _, record := range nodeResult.Records {
|
||||
if v, ok := record.Get("n"); ok {
|
||||
if n, ok := v.(neo4j.Node); ok {
|
||||
nodes = append(nodes, neo4jNodeToModel(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edgeResult, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
"MATCH (a)-[r]->(b) RETURN a.id AS source, b.id AS target, r",
|
||||
map[string]any{},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Error querying edges: %v\n", err)
|
||||
return models.GraphData{Nodes: nodes}
|
||||
}
|
||||
for _, record := range edgeResult.Records {
|
||||
source, _ := record.Get("source")
|
||||
target, _ := record.Get("target")
|
||||
v, _ := record.Get("r")
|
||||
if rel, ok := v.(neo4j.Relationship); ok {
|
||||
edge := neo4jRelToModel(rel)
|
||||
if s, ok := source.(string); ok {
|
||||
edge.Source = s
|
||||
}
|
||||
if t, ok := target.(string); ok {
|
||||
edge.Target = t
|
||||
}
|
||||
edges = append(edges, edge)
|
||||
}
|
||||
}
|
||||
|
||||
return models.GraphData{Nodes: nodes, Edges: edges}
|
||||
}
|
||||
|
||||
func (s *Neo4jService) GetNodeByID(id string) (models.Node, bool) {
|
||||
ctx := context.Background()
|
||||
result, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
"MATCH (n {id: $id}) RETURN n",
|
||||
map[string]any{"id": id},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil || len(result.Records) == 0 {
|
||||
return models.Node{}, false
|
||||
}
|
||||
v, _ := result.Records[0].Get("n")
|
||||
if n, ok := v.(neo4j.Node); ok {
|
||||
return neo4jNodeToModel(n), true
|
||||
}
|
||||
return models.Node{}, false
|
||||
}
|
||||
|
||||
func (s *Neo4jService) SearchNodes(query string) []models.Node {
|
||||
if query == "" {
|
||||
return []models.Node{}
|
||||
}
|
||||
ctx := context.Background()
|
||||
result, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
`MATCH (n)
|
||||
WHERE toLower(n.label) CONTAINS toLower($query)
|
||||
OR toLower(n.id) CONTAINS toLower($query)
|
||||
OR (n.type IS NOT NULL AND toLower(n.type) CONTAINS toLower($query))
|
||||
RETURN n`,
|
||||
map[string]any{"query": query},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil {
|
||||
return []models.Node{}
|
||||
}
|
||||
var nodes []models.Node
|
||||
for _, record := range result.Records {
|
||||
v, _ := record.Get("n")
|
||||
if n, ok := v.(neo4j.Node); ok {
|
||||
nodes = append(nodes, neo4jNodeToModel(n))
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (s *Neo4jService) GetNeighbors(nodeID string) (models.NeighborResponse, bool) {
|
||||
ctx := context.Background()
|
||||
|
||||
_, exists := s.GetNodeByID(nodeID)
|
||||
if !exists {
|
||||
return models.NeighborResponse{}, false
|
||||
}
|
||||
|
||||
result, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
`MATCH ({id: $id})-[r]-(m)
|
||||
RETURN m, r, startNode(r).id AS source, endNode(r).id AS target`,
|
||||
map[string]any{"id": nodeID},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil {
|
||||
return models.NeighborResponse{Nodes: []models.Node{}, Edges: []models.Edge{}}, true
|
||||
}
|
||||
|
||||
neighborMap := make(map[string]models.Node)
|
||||
edgeMap := make(map[string]models.Edge)
|
||||
|
||||
for _, record := range result.Records {
|
||||
if v, ok := record.Get("m"); ok {
|
||||
if n, ok := v.(neo4j.Node); ok {
|
||||
modelNode := neo4jNodeToModel(n)
|
||||
neighborMap[modelNode.ID] = modelNode
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := record.Get("r"); ok {
|
||||
if rel, ok := v.(neo4j.Relationship); ok {
|
||||
edge := neo4jRelToModel(rel)
|
||||
if source, ok := record.Get("source"); ok {
|
||||
if s, ok := source.(string); ok {
|
||||
edge.Source = s
|
||||
}
|
||||
}
|
||||
if target, ok := record.Get("target"); ok {
|
||||
if t, ok := target.(string); ok {
|
||||
edge.Target = t
|
||||
}
|
||||
}
|
||||
edgeMap[edge.ID] = edge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var neighborNodes []models.Node
|
||||
for _, n := range neighborMap {
|
||||
neighborNodes = append(neighborNodes, n)
|
||||
}
|
||||
|
||||
var relatedEdges []models.Edge
|
||||
for _, e := range edgeMap {
|
||||
relatedEdges = append(relatedEdges, e)
|
||||
}
|
||||
|
||||
return models.NeighborResponse{
|
||||
Nodes: neighborNodes,
|
||||
Edges: relatedEdges,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *Neo4jService) GetStats() map[string]int {
|
||||
ctx := context.Background()
|
||||
result, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
`MATCH (n)
|
||||
RETURN count(n) AS totalNodes,
|
||||
sum(CASE WHEN n.type = '概念' THEN 1 ELSE 0 END) AS conceptNodes,
|
||||
sum(CASE WHEN n.type = '工具' THEN 1 ELSE 0 END) AS toolNodes,
|
||||
sum(CASE WHEN n.type = '应用' THEN 1 ELSE 0 END) AS applicationNodes`,
|
||||
map[string]any{},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err != nil || len(result.Records) == 0 {
|
||||
return map[string]int{}
|
||||
}
|
||||
|
||||
record := result.Records[0]
|
||||
totalNodes := getInt(record, "totalNodes")
|
||||
conceptNodes := getInt(record, "conceptNodes")
|
||||
toolNodes := getInt(record, "toolNodes")
|
||||
applicationNodes := getInt(record, "applicationNodes")
|
||||
|
||||
totalEdges := 0
|
||||
edgeResult, err := neo4j.ExecuteQuery(ctx, s.driver,
|
||||
"MATCH ()-[r]->() RETURN count(r) AS totalEdges",
|
||||
map[string]any{},
|
||||
neo4j.EagerResultTransformer,
|
||||
)
|
||||
if err == nil && len(edgeResult.Records) > 0 {
|
||||
totalEdges = getInt(edgeResult.Records[0], "totalEdges")
|
||||
}
|
||||
|
||||
return map[string]int{
|
||||
"totalNodes": totalNodes,
|
||||
"totalEdges": totalEdges,
|
||||
"conceptNodes": conceptNodes,
|
||||
"toolNodes": toolNodes,
|
||||
"applicationNodes": applicationNodes,
|
||||
}
|
||||
}
|
||||
|
||||
func neo4jNodeToModel(n neo4j.Node) models.Node {
|
||||
props := n.Props
|
||||
node := models.Node{
|
||||
ID: getStr(props, "id"),
|
||||
Label: getStr(props, "label"),
|
||||
Type: getStr(props, "type"),
|
||||
Properties: make(map[string]interface{}),
|
||||
}
|
||||
if v, ok := props["x"]; ok && v != nil {
|
||||
node.X = getFloat64(v)
|
||||
}
|
||||
if v, ok := props["y"]; ok && v != nil {
|
||||
node.Y = getFloat64(v)
|
||||
}
|
||||
if v, ok := props["style"]; ok && v != nil {
|
||||
if s, ok := v.(map[string]interface{}); ok {
|
||||
node.Style = s
|
||||
}
|
||||
}
|
||||
for k, v := range props {
|
||||
switch k {
|
||||
case "id", "label", "type", "x", "y", "style":
|
||||
default:
|
||||
node.Properties[k] = v
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func neo4jRelToModel(r neo4j.Relationship) models.Edge {
|
||||
props := r.Props
|
||||
edge := models.Edge{
|
||||
ID: getStr(props, "id"),
|
||||
Label: getStr(props, "label"),
|
||||
Type: r.Type,
|
||||
Properties: make(map[string]interface{}),
|
||||
}
|
||||
if v, ok := props["style"]; ok && v != nil {
|
||||
if s, ok := v.(map[string]interface{}); ok {
|
||||
edge.Style = s
|
||||
}
|
||||
}
|
||||
for k, v := range props {
|
||||
switch k {
|
||||
case "id", "label", "style":
|
||||
default:
|
||||
edge.Properties[k] = v
|
||||
}
|
||||
}
|
||||
return edge
|
||||
}
|
||||
|
||||
func getStr(props map[string]any, key string) string {
|
||||
if v, ok := props[key]; ok && v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getFloat64(v any) float64 {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
return val
|
||||
case int64:
|
||||
return float64(val)
|
||||
case int:
|
||||
return float64(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getInt(record *neo4j.Record, key string) int {
|
||||
v, _ := record.Get(key)
|
||||
switch val := v.(type) {
|
||||
case int64:
|
||||
return int(val)
|
||||
case int:
|
||||
return val
|
||||
case float64:
|
||||
return int(val)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package services
|
||||
|
||||
import "knowledge-graph-backend/models"
|
||||
|
||||
type GraphService interface {
|
||||
GetGraphData() models.GraphData
|
||||
GetNodeByID(id string) (models.Node, bool)
|
||||
SearchNodes(query string) []models.Node
|
||||
GetNeighbors(nodeID string) (models.NeighborResponse, bool)
|
||||
GetStats() map[string]int
|
||||
}
|
||||
Reference in New Issue
Block a user