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 }