graph.js
3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* Graph API — 知识图谱 REST 接口
*
* GET /api/graph/nodes?type=Person&limit=100 列出节点
* GET /api/graph/nodes/:id 查询单个节点(含边)
* POST /api/graph/nodes 创建/更新节点
* GET /api/graph/search?q=关键词 全文搜索
* POST /api/graph/edges 创建关系边
* GET /api/graph/path?from=id&to=id 查询关系路径
*/
const express = require('express');
const { getGraphStore } = require('../db/GraphStore');
const router = express.Router();
function store() {
return getGraphStore();
}
// GET /api/graph/nodes?type=Person&limit=100&offset=0&sort=name&order=asc
router.get('/nodes', (req, res) => {
const { type, limit = 100, offset = 0, sort, order = 'asc' } = req.query;
const g = store();
try {
const lim = Number(limit);
const off = Number(offset);
let nodes, total;
if (type) {
if (sort) {
nodes = g.listSorted(type, sort, order, lim, off);
} else {
nodes = g.listByType(type, lim, off);
}
total = g.countByType(type);
} else {
nodes = g.search('', lim, off);
total = g.countSearch('');
}
res.json({ nodes, total, limit: lim, offset: off });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// GET /api/graph/nodes/:id
router.get('/nodes/:id', (req, res) => {
const g = store();
try {
const node = g.getNode(req.params.id);
if (!node) return res.status(404).json({ error: 'Node not found' });
const edges = g.getEdges(req.params.id);
res.json({ node, edges });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// POST /api/graph/nodes { type, ...data }
router.post('/nodes', (req, res) => {
const { type, ...data } = req.body;
if (!type) return res.status(400).json({ error: 'type is required' });
try {
const node = store().createNode(type, data);
res.json({ node });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// GET /api/graph/search?q=xxx&type=Person&limit=50&offset=0
router.get('/search', (req, res) => {
const { q = '', type, limit = 50, offset = 0 } = req.query;
if (!q) return res.status(400).json({ error: 'q is required' });
try {
const g = store();
const lim = Number(limit);
const off = Number(offset);
let results = g.search(q, lim + (type ? lim : 0), off); // 多取一些再过滤
if (type) results = results.filter(n => n._type === type);
const total = g.countSearch(q);
res.json({ results, total, limit: lim, offset: off });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// POST /api/graph/edges { fromId, toId, relation, ...meta }
router.post('/edges', (req, res) => {
const { fromId, toId, relation, ...meta } = req.body;
if (!fromId || !toId || !relation) {
return res.status(400).json({ error: 'fromId, toId, relation are required' });
}
try {
const edge = store().createEdge(fromId, toId, relation, meta);
res.json({ edge });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// GET /api/graph/path?from=id&to=id&maxDepth=6
router.get('/path', (req, res) => {
const { from, to, maxDepth = 6 } = req.query;
if (!from || !to) return res.status(400).json({ error: 'from and to are required' });
try {
const path = store().getRelationshipPath(from, to, Number(maxDepth));
res.json({ path, found: path !== null, steps: path?.length ?? null });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;