Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258e5d21f9 | ||
|
|
6764095ed1 | ||
|
|
37df78e5d2 |
@@ -0,0 +1,91 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Neo4jConfig struct {
|
||||||
|
URI string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataConfig struct {
|
||||||
|
FilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CORSConfig struct {
|
||||||
|
AllowOrigins []string
|
||||||
|
AllowMethods []string
|
||||||
|
AllowHeaders []string
|
||||||
|
ExposeHeaders []string
|
||||||
|
AllowCredentials bool
|
||||||
|
MaxAge time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig
|
||||||
|
Neo4j Neo4jConfig
|
||||||
|
Data DataConfig
|
||||||
|
CORS CORSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Server: ServerConfig{
|
||||||
|
Port: getEnv("PORT", "3001"),
|
||||||
|
},
|
||||||
|
Neo4j: Neo4jConfig{
|
||||||
|
URI: os.Getenv("NEO4J_URI"),
|
||||||
|
Username: os.Getenv("NEO4J_USERNAME"),
|
||||||
|
Password: os.Getenv("NEO4J_PASSWORD"),
|
||||||
|
},
|
||||||
|
Data: DataConfig{
|
||||||
|
FilePath: getEnv("DATA_FILE", "data.json"),
|
||||||
|
},
|
||||||
|
CORS: CORSConfig{
|
||||||
|
AllowOrigins: []string{"http://localhost:5173", "http://localhost:3000"},
|
||||||
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
|
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "Accept"},
|
||||||
|
ExposeHeaders: []string{"Content-Length", "Content-Type"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 12 * time.Hour,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CORSConfig) ToGinConfig() cors.Config {
|
||||||
|
return cors.Config{
|
||||||
|
AllowOrigins: c.AllowOrigins,
|
||||||
|
AllowMethods: c.AllowMethods,
|
||||||
|
AllowHeaders: c.AllowHeaders,
|
||||||
|
ExposeHeaders: c.ExposeHeaders,
|
||||||
|
AllowCredentials: c.AllowCredentials,
|
||||||
|
MaxAge: c.MaxAge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, defaultValue string) string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
@@ -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 {
|
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{
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-24
@@ -1,42 +1,37 @@
|
|||||||
package neo4j
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func main() {
|
||||||
PORT = "3001"
|
cfg, err := config.Load()
|
||||||
DATA_FILE = "data.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func test() {
|
|
||||||
|
|
||||||
dataService, err := services.NewDataService(DATA_FILE)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize data service: %v", err)
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
graphHandler := handlers.NewGraphHandler(dataService)
|
driver := neo4jClient.NewDriver(cfg.Neo4j)
|
||||||
searchHandler := handlers.NewSearchHandler(dataService)
|
defer driver.Close(context.Background())
|
||||||
nodeHandler := handlers.NewNodeHandler(dataService)
|
|
||||||
|
graphService := services.NewNeo4jService(driver)
|
||||||
|
|
||||||
|
graphHandler := handlers.NewGraphHandler(graphService)
|
||||||
|
searchHandler := handlers.NewSearchHandler(graphService)
|
||||||
|
nodeHandler := handlers.NewNodeHandler(graphService)
|
||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.Use(cors.New(cors.Config{
|
router.Use(cors.New(cfg.CORS.ToGinConfig()))
|
||||||
AllowOrigins: []string{"http://localhost:5173", "http://localhost:3000"},
|
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
||||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "Accept"},
|
|
||||||
ExposeHeaders: []string{"Content-Length", "Content-Type"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
MaxAge: 12 * 3600,
|
|
||||||
}))
|
|
||||||
|
|
||||||
router.GET("/health", func(c *gin.Context) {
|
router.GET("/health", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -57,8 +52,8 @@ func test() {
|
|||||||
api.GET("/nodes/:id/neighbors", nodeHandler.GetNeighbors)
|
api.GET("/nodes/:id/neighbors", nodeHandler.GetNeighbors)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Knowledge Graph API Server running on http://localhost:" + PORT)
|
log.Println("Knowledge Graph API Server running on http://localhost:" + cfg.Server.Port)
|
||||||
log.Println("Data file loaded from: " + DATA_FILE)
|
log.Println("Data file loaded from: " + cfg.Data.FilePath)
|
||||||
log.Println("Available endpoints:")
|
log.Println("Available endpoints:")
|
||||||
log.Println(" GET /health - Health check")
|
log.Println(" GET /health - Health check")
|
||||||
log.Println(" GET /api/graph - Get all graph data")
|
log.Println(" GET /api/graph - Get all graph data")
|
||||||
@@ -67,7 +62,7 @@ func test() {
|
|||||||
log.Println(" GET /api/nodes/:id - Get node by ID")
|
log.Println(" GET /api/nodes/:id - Get node by ID")
|
||||||
log.Println(" GET /api/nodes/:id/neighbors - Get node neighbors")
|
log.Println(" GET /api/nodes/:id/neighbors - Get node neighbors")
|
||||||
|
|
||||||
if err := router.Run(":" + PORT); err != nil {
|
if err := router.Run(":" + cfg.Server.Port); err != nil {
|
||||||
log.Fatalf("Failed to start server: %v", err)
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-37
@@ -2,47 +2,25 @@ package neo4j
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
|
||||||
|
"knowledge-graph-backend/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func NewDriver(cfg config.Neo4jConfig) neo4j.DriverWithContext {
|
||||||
NEO4J_URI string
|
ctx := context.Background()
|
||||||
NEO4J_USERNAME string
|
driver, err := neo4j.NewDriverWithContext(
|
||||||
NEO4J_PASSWORD string
|
cfg.URI,
|
||||||
)
|
neo4j.BasicAuth(cfg.Username, cfg.Password, ""))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
err = driver.VerifyConnectivity(ctx)
|
||||||
err := godotenv.Load()
|
if err != nil {
|
||||||
if err != nil {
|
panic(err)
|
||||||
log.Fatal("Error loading .env file")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
NEO4J_URI = os.Getenv("NEO4J_URI")
|
return driver
|
||||||
NEO4J_USERNAME = os.Getenv("NEO4J_USERNAME")
|
|
||||||
NEO4J_PASSWORD = os.Getenv("NEO4J_PASSWORD")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doConnect() {
|
|
||||||
ctx := context.Background()
|
|
||||||
// dbUri := "<database-uri>"
|
|
||||||
// dbUser := "<username>"
|
|
||||||
// dbPassword := "<password>"
|
|
||||||
driver, err := neo4j.NewDriverWithContext(
|
|
||||||
NEO4J_URI,
|
|
||||||
neo4j.BasicAuth(NEO4J_USERNAME, NEO4J_PASSWORD, ""))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer driver.Close(ctx)
|
|
||||||
|
|
||||||
err = driver.VerifyConnectivity(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println("Connection established.")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
package neo4j
|
package neo4j
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
func TestDoConnect(t *testing.T){
|
"knowledge-graph-backend/config"
|
||||||
doConnect()
|
)
|
||||||
}
|
|
||||||
|
func TestNewDriver(t *testing.T) {
|
||||||
|
cnf, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[cnf]: %v", err)
|
||||||
|
}
|
||||||
|
cfg := config.Neo4jConfig{
|
||||||
|
URI: cnf.Neo4j.URI,
|
||||||
|
Username: cnf.Neo4j.Username,
|
||||||
|
Password: cnf.Neo4j.Password,
|
||||||
|
}
|
||||||
|
driver := NewDriver(cfg)
|
||||||
|
defer driver.Close(context.Background())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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