SQL Server RAG 笔记2:图数据库服务层与前端可视化构建
SQL Server RAG 笔记2:图数据库服务层与前端可视化构建
摘要
此篇是 SQL Server 图数据库实战的第二篇,继续构建知识图谱前端,将介绍基于 FastAPI 的服务层架构设计,以及 Vue3 + D3.js 前端图可视化实现。内容涵盖 API 路由设计、数据传输模型、前端组件化架构、D3.js 力导向图布局等核心知识点。
服务层架构设计
整体架构
┌─────────────────────────────────────────────────────┐
│ 前端 (Vue3) │
│ GraphVisualization / NodeList / EdgeList │
├─────────────────────────────────────────────────────┤
│ API 服务层 │
│ REST API (FastAPI) + 路由分发 │
├─────────────────────────────────────────────────────┤
│ DAO 数据访问层 │
│ NodeDAO / EdgeDAO / GraphDAO │
├─────────────────────────────────────────────────────┤
│ 数据库 (SQL Server) │
│ 原生图表 (AS NODE/EDGE) │
└─────────────────────────────────────────────────────┘
FastAPI 应用入口
因为接口会比较多,所以用路由的方式分开管理。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from api import nodes_router, edges_router, graph_router
app = FastAPI(
title="SQLServer GraphDB API",
description="基于SQLServer的图数据库API系统",
version="1.0.0"
)
# CORS 中间件配置,允许前端跨域访问
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境应限制具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 路由模块注册
app.include_router(nodes_router)
app.include_router(edges_router)
app.include_router(graph_router)
API 路由设计
节点路由 (nodes.py)
from fastapi import APIRouter, HTTPException, Query
from typing import List, Optional
from models import NodeCreate, NodeUpdate, NodeResponse
from dao import NodeDAO
router = APIRouter(prefix="/api/nodes", tags=["nodes"])
@router.post("/", response_model=NodeResponse, status_code=201)
def create_node(node: NodeCreate):
"""创建新节点"""
node_id = NodeDAO.create_node(node)
if node_id:
result = NodeDAO.get_node_by_id(node_id)
if result:
return result
raise HTTPException(status_code=400, detail="Failed to create node")
@router.get("/{node_id}", response_model=NodeResponse)
def get_node(node_id: int):
"""根据ID获取节点"""
node = NodeDAO.get_node_by_id(node_id)
if not node:
raise HTTPException(status_code=404, detail="Node not found")
return node
@router.get("/", response_model=List[NodeResponse])
def get_nodes(
node_type: Optional[str] = Query(None, description="按节点类型筛选"),
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0)
):
"""分页获取节点列表"""
return NodeDAO.get_all_nodes(node_type=node_type, limit=limit, offset=offset)
@router.put("/{node_id}", response_model=NodeResponse)
def update_node(node_id: int, node_update: NodeUpdate):
"""更新节点信息"""
result = NodeDAO.update_node(node_id, node_update)
if not result:
raise HTTPException(status_code=404, detail="Node not found or update failed")
return result
@router.delete("/{node_id}", status_code=204)
def delete_node(node_id: int):
"""软删除节点"""
success = NodeDAO.delete_node(node_id)
if not success:
raise HTTPException(status_code=404, detail="Node not found or already deleted")
return None
@router.get("/search/", response_model=List[NodeResponse])
def search_nodes(keyword: str = Query(..., min_length=1), limit: int = Query(50, ge=1, le=200)):
"""关键词搜索节点"""
return NodeDAO.search_nodes(keyword, limit)
知识点笔记:
response_model:自动进行数据序列化和验证Query参数:支持参数校验和文档生成- HTTP 状态码:201 (创建成功)、204 (删除成功)、404 (未找到)
- 软删除:通过
IsDeleted标志位实现,支持数据恢复
边路由和图路由可以在项目里对应目录查看,这里不一一列出,实现的套路跟上面节点路由基本一致。
前端架构设计
Vue3 组件化架构
src/
├── App.vue # 根组件,布局管理
├── main.js # 应用入口
├── services/
│ └── api.js # API 调用封装
└── components/
├── GraphVisualization.vue # 图可视化核心组件
├── NodeList.vue # 节点列表管理
└── EdgeList.vue # 边列表管理
知识点笔记:
Composition API(<script setup>):更灵活的逻辑复用- 组件单向数据流:父组件通过 Props 传递数据
- 事件向上传递:子组件通过 Emit 向父组件通信
API 服务封装
import axios from 'axios'
const api = axios.create({
baseURL: '/api', // 代理到后端服务
timeout: 10000
})
// 响应拦截器,统一处理错误
api.interceptors.response.use(
response => response.data,
error => {
console.error('API Error:', error)
return Promise.reject(error)
}
)
export default {
// 节点操作
getNodes(nodeType = null, limit = 100, offset = 0) {
const params = new URLSearchParams({ limit, offset })
if (nodeType) params.append('node_type', nodeType)
return api.get(`/nodes/?${params}`)
},
createNode(nodeData) {
return api.post('/nodes/', nodeData)
},
// 图查询操作
getFullGraph(limit = 1000) {
return api.get(`/graph/full?limit=${limit}`)
},
getNeighbors(nodeId) {
return api.get(`/graph/neighbors/${nodeId}`)
},
traverseGraph(startNodeId, maxDepth = 3) {
return api.post('/graph/traverse', {
start_node_id: startNodeId,
max_depth: maxDepth
})
},
findPath(startNodeId, endNodeId) {
return api.get(`/graph/path/${startNodeId}/${endNodeId}`)
},
getStatistics() {
return api.get('/graph/statistics')
}
}
知识点笔记:
axios.create:创建自定义配置的实例- 响应拦截器:在统一位置处理错误和数据转换
URLSearchParams:构建查询参数,避免手动拼接字符串- Promise 返回:支持 async/await 异步调用
图可视化核心实现
D3.js 力导向图布局
d3.js 是整个前端可视化的核心库,负责将 SQL Server 图数据库中的节点和关系数据转换为直观、可交互的力导向图,让用户能够:
- 可视化查看图结构
- 与节点进行交互
- 缩放和平移探索图数据
再往下的代码可能有点枯燥了。建议保存此篇文章的URL,后续可以提供给AI,让AI以此篇URL的方式来画前端的知识图谱。
// GraphVisualization.vue
import * as d3 from 'd3'
const NODE_RADIUS = 20
const initGraph = () => {
// 清除旧画布
d3.select('#graph-container').selectAll('*').remove()
const width = container.value.clientWidth
const height = container.value.clientHeight
const svg = d3.select('#graph-container')
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', `0 0 ${width} ${height}`)
// 缩放行为
const zoom = d3.zoom()
.scaleExtent([0.1, 4])
.on('zoom', (event) => {
g.attr('transform', event.transform)
})
svg.call(zoom)
// 节点数据处理
const nodes = props.nodes.map(d => ({
...d,
node_id: d.node_id,
x: Math.random() * width,
y: Math.random() * height
}))
const nodeIds = new Set(nodes.map(n => n.node_id))
// 边数据处理,过滤无效引用
const edges = props.edges
.map(d => ({
...d,
source: d.source_node_id || d.source,
target: d.target_node_id || d.target
}))
.filter(d => d.source !== undefined &&
d.target !== undefined &&
nodeIds.has(d.source) &&
nodeIds.has(d.target))
// 力导向模拟
simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(edges)
.id(d => d.node_id)
.distance(120)) // 连线距离
.force('charge', d3.forceManyBody().strength(-150)) // 节点排斥力
.force('center', d3.forceCenter(width / 2, height / 2)) // 中心引力
.force('collide', d3.forceCollide().radius(NODE_RADIUS + 30)) // 碰撞检测
}
知识点笔记:
forceSimulation:D3 力导向图核心,启动物理模拟forceLink:节点间的弹簧力,拉近相连节点forceManyBody:节点间排斥力,防止重叠forceCenter:中心引力,将节点拉向画布中心forceCollide:碰撞检测,防止节点重叠
节点与边的渲染
// 渲染连线
const link = g.append('g')
.selectAll('line')
.data(edges)
.join('line')
.attr('stroke', '#999')
.attr('stroke-width', 1.5)
.attr('opacity', 0.6)
.attr('marker-end', 'url(#arrowhead)') // 箭头标记
// 渲染节点
const node = g.append('g')
.selectAll('g')
.data(nodes)
.join('g')
.attr('cursor', 'pointer')
.call(d3.drag() // 拖拽交互
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded))
// 节点圆形
node.append('circle')
.attr('r', NODE_RADIUS)
.attr('fill', d => getNodeColor(d.node_type))
.attr('stroke', '#fff')
.attr('stroke-width', 2)
// 节点图标
node.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '0.35em')
.attr('font-size', '16px')
.text(d => getNodeIcon(d.node_type))
// 节点标签
node.append('text')
.attr('dy', NODE_RADIUS + 15)
.attr('text-anchor', 'middle')
.attr('font-size', '12px')
.text(d => d.name)
// 连线标签
const linkLabels = g.append('g')
.selectAll('text')
.data(edges)
.join('text')
.attr('dy', -5)
.attr('text-anchor', 'middle')
.attr('font-size', '10px')
.attr('fill', '#666')
.text(d => d.edge_type)
知识点笔记:
join():D3 数据绑定语法,新旧数据对比后智能更新marker-end:SVG 箭头标记,标识边的方向getNodeColor:根据节点类型返回不同颜色- 拖拽交互:通过
drag()行为启用节点拖拽
力模拟动态更新
// 模拟 tick 事件,更新图形位置
simulation.on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
node.attr('transform', d => `translate(${d.x},${d.y})`)
linkLabels
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2)
})
知识点笔记:
simulation.on('tick'):每帧更新时调用,驱动动画- 位置插值:D3 自动计算节点间的平滑过渡
- 箭头位置:实时更新连线的起点和终点坐标
交互功能实现
节点拖拽
const dragStarted = (event, d) => {
if (!event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
}
const dragged = (event, d) => {
d.fx = event.x
d.fy = event.y
}
const dragEnded = (event, d) => {
if (!event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
知识点笔记:
alphaTarget:调整模拟热力,重启或停止模拟fx/fy:固定节点位置,脱离模拟影响- 拖拽时提高
alphaTarget,让其他节点响应调整
总结
前后两篇,我们使用的技术框架总体如下:
| 层级 | 技术 | 作用 |
|---|---|---|
| 前端框架 | Vue3 + Composition API | 组件化开发 |
| UI 组件 | Element Plus | 快速构建管理界面 |
| 图可视化 | D3.js | 力导向图布局渲染 |
| HTTP 客户端 | axios | API 调用封装 |
| 后端框架 | FastAPI | 高性能 REST API |
| 数据验证 | Pydantic | 请求/响应模型校验 |
| 数据库 | SQL Server 原生图 | 图数据存储 |
通过SQLServer也是可以搭建图数据库的,但也需留意在Neo4J里很多东西跟SQLServer里的还是有区别的,这个在实际项目中是需要考虑的因素。但如果项目中不打算额外采购Neo4J,并且在已经有SQLServer2027+版本的部署的话,是可以考虑的。
本篇代码内容比较长,但也只展示了一部分,完整的项目代码可以在以下网址下载:
https://github.com/microsoftbi/SQLServerRAG
最终运行起来之后,可以看到读取出的知识图谱如下:

posted on 2026-05-05 00:19 哥本哈士奇(aspnetx) 阅读(0) 评论(0) 收藏 举报
浙公网安备 33010602011771号