/** * 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;