Feat: 实现知识图谱数据库的检索方法,并对外提供接口

This commit is contained in:
2026-04-13 15:21:54 +08:00
parent 6764095ed1
commit 258e5d21f9
8 changed files with 339 additions and 277 deletions
+4 -1
View File
@@ -40,7 +40,10 @@ type Config struct {
} }
func Load() (*Config, error) { 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) return nil, fmt.Errorf("error loading .env file: %w", err)
} }
-255
View File
@@ -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": "技术支持"
}
]
}
+5 -5
View File
@@ -8,22 +8,22 @@ import (
) )
type GraphHandler struct { type GraphHandler struct {
dataService *services.DataService service services.GraphService
} }
func NewGraphHandler(dataService *services.DataService) *GraphHandler { func NewGraphHandler(service services.GraphService) *GraphHandler {
return &GraphHandler{ return &GraphHandler{
dataService: dataService, service: service,
} }
} }
func (h *GraphHandler) GetGraphData(c *gin.Context) { func (h *GraphHandler) GetGraphData(c *gin.Context) {
data := h.dataService.GetGraphData() data := h.service.GetGraphData()
c.JSON(http.StatusOK, data) c.JSON(http.StatusOK, data)
} }
func (h *GraphHandler) GetStats(c *gin.Context) { func (h *GraphHandler) GetStats(c *gin.Context) {
stats := h.dataService.GetStats() stats := h.service.GetStats()
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"stats": stats, "stats": stats,
"timestamp": gin.H{ "timestamp": gin.H{
+5 -5
View File
@@ -8,12 +8,12 @@ import (
) )
type NodeHandler struct { type NodeHandler struct {
dataService *services.DataService service services.GraphService
} }
func NewNodeHandler(dataService *services.DataService) *NodeHandler { func NewNodeHandler(service services.GraphService) *NodeHandler {
return &NodeHandler{ return &NodeHandler{
dataService: dataService, service: service,
} }
} }
@@ -27,7 +27,7 @@ func (h *NodeHandler) GetNodeByID(c *gin.Context) {
return return
} }
node, found := h.dataService.GetNodeByID(nodeID) node, found := h.service.GetNodeByID(nodeID)
if !found { if !found {
c.JSON(http.StatusNotFound, gin.H{ c.JSON(http.StatusNotFound, gin.H{
"error": "Not Found", "error": "Not Found",
@@ -49,7 +49,7 @@ func (h *NodeHandler) GetNeighbors(c *gin.Context) {
return return
} }
neighbors, found := h.dataService.GetNeighbors(nodeID) neighbors, found := h.service.GetNeighbors(nodeID)
if !found { if !found {
c.JSON(http.StatusNotFound, gin.H{ c.JSON(http.StatusNotFound, gin.H{
"error": "Not Found", "error": "Not Found",
+4 -4
View File
@@ -8,12 +8,12 @@ import (
) )
type SearchHandler struct { type SearchHandler struct {
dataService *services.DataService service services.GraphService
} }
func NewSearchHandler(dataService *services.DataService) *SearchHandler { func NewSearchHandler(service services.GraphService) *SearchHandler {
return &SearchHandler{ return &SearchHandler{
dataService: dataService, service: service,
} }
} }
@@ -27,7 +27,7 @@ func (h *SearchHandler) SearchNodes(c *gin.Context) {
return return
} }
results := h.dataService.SearchNodes(query) results := h.service.SearchNodes(query)
c.JSON(http.StatusOK, results) c.JSON(http.StatusOK, results)
} }
+9 -7
View File
@@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"log" "log"
"net/http" "net/http"
@@ -9,6 +10,7 @@ import (
"knowledge-graph-backend/config" "knowledge-graph-backend/config"
"knowledge-graph-backend/handlers" "knowledge-graph-backend/handlers"
neo4jClient "knowledge-graph-backend/neo4j"
"knowledge-graph-backend/services" "knowledge-graph-backend/services"
) )
@@ -18,14 +20,14 @@ func main() {
log.Fatalf("Failed to load config: %v", err) log.Fatalf("Failed to load config: %v", err)
} }
dataService, err := services.NewDataService(cfg.Data.FilePath) driver := neo4jClient.NewDriver(cfg.Neo4j)
if err != nil { defer driver.Close(context.Background())
log.Fatalf("Failed to initialize data service: %v", err)
}
graphHandler := handlers.NewGraphHandler(dataService) graphService := services.NewNeo4jService(driver)
searchHandler := handlers.NewSearchHandler(dataService)
nodeHandler := handlers.NewNodeHandler(dataService) graphHandler := handlers.NewGraphHandler(graphService)
searchHandler := handlers.NewSearchHandler(graphService)
nodeHandler := handlers.NewNodeHandler(graphService)
router := gin.Default() router := gin.Default()
+301
View File
@@ -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
}
+11
View File
@@ -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
}