11 KiB
11 KiB
后端 API 需求文档
📋 概述
本文档描述了前端新增功能所需的后端 API 接口。这些接口用于支持多用户管理、节点数据持久化等功能。
🔌 新增 API 接口
1. 获取用户列表
接口信息
- 路径:
GET /api/users/list - 用途: 获取系统中所有用户的列表,用于用户切换下拉框显示
- 优先级: ⭐⭐⭐ 高
请求参数
无
响应格式
{
"users": ["guest", "admin", "dev", "test", "user1", "user2"],
"count": 6
}
响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| users | string[] | 用户名列表 |
| count | integer | 用户总数 |
实现建议
- 扫描
cloud/users/目录,返回所有子目录名称 - 按字母顺序排序
- 可以添加缓存机制(TTL 60秒)
- 排除系统目录(如
.git,__pycache__等)
示例代码
from fastapi import APIRouter
from pathlib import Path
from typing import List
router = APIRouter()
@router.get("/api/users/list")
async def list_users():
"""获取所有用户列表"""
users_dir = Path("cloud/users")
if not users_dir.exists():
users_dir.mkdir(parents=True, exist_ok=True)
return {"users": [], "count": 0}
users = []
for item in users_dir.iterdir():
if item.is_dir() and not item.name.startswith('.'):
users.append(item.name)
users.sort()
return {"users": users, "count": len(users)}
错误处理
- 如果
cloud/users/不存在,自动创建并返回空列表 - 权限问题返回
403 Forbidden
2. 保存节点数据
接口信息
- 路径:
POST /api/nodes/save - 用途: 保存单个节点的配置数据到服务器持久化存储
- 优先级: ⭐⭐ 中
请求参数
Content-Type: application/json
{
"nodeId": "n_1704614400000_1234",
"nodeData": {
"label": "我的CSV加载器",
"file_path": "users/guest/data/sales.csv",
"delimiter": ",",
"meta": { ... },
"preview": { ... }
}
}
请求字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nodeId | string | 是 | 节点唯一标识符 |
| nodeData | object | 是 | 节点完整数据对象 |
| nodeData.label | string | 否 | 节点自定义标题 |
| nodeData.* | any | 否 | 其他节点参数 |
响应格式
成功响应 (200):
{
"success": true,
"nodeId": "n_1704614400000_1234",
"savedAt": "2026-01-07T12:34:56.789Z"
}
失败响应 (400):
{
"success": false,
"error": "Invalid nodeId format"
}
实现建议
存储方案 A:文件系统
# 存储路径: cloud/nodes/{nodeId}.json
import json
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from pathlib import Path
from datetime import datetime
class NodeSaveRequest(BaseModel):
nodeId: str
nodeData: dict
@router.post("/api/nodes/save")
async def save_node(request: NodeSaveRequest):
"""保存节点数据到文件系统"""
nodes_dir = Path("cloud/nodes")
nodes_dir.mkdir(parents=True, exist_ok=True)
# 验证 nodeId 格式
if not request.nodeId.startswith('n_'):
raise HTTPException(status_code=400, detail="Invalid nodeId format")
# 添加元数据
save_data = {
"nodeId": request.nodeId,
"nodeData": request.nodeData,
"savedAt": datetime.utcnow().isoformat() + "Z"
}
# 保存到文件
file_path = nodes_dir / f"{request.nodeId}.json"
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=2, ensure_ascii=False)
return {
"success": True,
"nodeId": request.nodeId,
"savedAt": save_data["savedAt"]
}
存储方案 B:SQLite 数据库
# 使用 SQLite 存储节点数据
import sqlite3
import json
def init_db():
conn = sqlite3.connect('cloud/tracestudio.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS nodes (
node_id TEXT PRIMARY KEY,
node_data TEXT NOT NULL,
saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
@router.post("/api/nodes/save")
async def save_node(request: NodeSaveRequest):
"""保存节点数据到数据库"""
conn = sqlite3.connect('cloud/tracestudio.db')
c = conn.cursor()
c.execute('''
INSERT OR REPLACE INTO nodes (node_id, node_data, saved_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
''', (request.nodeId, json.dumps(request.nodeData)))
conn.commit()
# 获取保存时间
c.execute('SELECT saved_at FROM nodes WHERE node_id = ?', (request.nodeId,))
saved_at = c.fetchone()[0]
conn.close()
return {
"success": True,
"nodeId": request.nodeId,
"savedAt": saved_at
}
使用场景
- 用户在 Inspector 面板编辑节点参数
- 点击 "💾 保存" 按钮
- 前端调用此 API 保存节点状态
- 可用于恢复节点配置、协作编辑等场景
扩展功能(可选)
- 添加用户权限验证(只能保存自己的节点)
- 节点版本控制(保存历史记录)
- 批量保存接口
POST /api/nodes/batch-save
🔄 现有 API 增强建议
文件上传 API 增强
当前: POST /api/files/upload?path=xxx&username=xxx
建议增强:
- 支持批量上传(拖拽多个文件)
- 返回上传文件的详细信息(路径、大小、类型)
- 添加上传进度支持(WebSocket 或 Server-Sent Events)
@router.post("/api/files/upload")
async def upload_files(
files: List[UploadFile], # 支持多文件
path: str = "",
username: str = "guest"
):
"""批量上传文件"""
results = []
for file in files:
# ... 上传逻辑 ...
results.append({
"filename": file.filename,
"path": f"{path}/{file.filename}",
"size": file.size,
"status": "success"
})
return {
"uploaded": len(results),
"files": results
}
📊 优先级排序
| 优先级 | 接口 | 原因 |
|---|---|---|
| ⭐⭐⭐ 高 | GET /api/users/list | 用户切换下拉框依赖此接口,无此接口会显示写死的4个用户 |
| ⭐⭐ 中 | POST /api/nodes/save | 节点持久化功能,不实现不影响基本使用 |
| ⭐ 低 | 文件上传增强 | 已有基础功能,增强为优化体验 |
🧪 测试用例
测试用户列表 API
# 测试 1: 获取用户列表
curl http://localhost:8000/api/users/list
# 预期响应:
{
"users": ["admin", "dev", "guest", "test"],
"count": 4
}
# 测试 2: 空用户目录
rm -rf cloud/users/*
curl http://localhost:8000/api/users/list
# 预期响应:
{
"users": [],
"count": 0
}
测试节点保存 API
# 测试 1: 保存节点
curl -X POST http://localhost:8000/api/nodes/save \
-H "Content-Type: application/json" \
-d '{
"nodeId": "n_1704614400000_1234",
"nodeData": {
"label": "测试节点",
"file_path": "users/guest/data/test.csv"
}
}'
# 预期响应:
{
"success": true,
"nodeId": "n_1704614400000_1234",
"savedAt": "2026-01-07T12:34:56.789Z"
}
# 测试 2: 无效节点ID
curl -X POST http://localhost:8000/api/nodes/save \
-H "Content-Type: application/json" \
-d '{
"nodeId": "invalid_id",
"nodeData": {}
}'
# 预期响应: 400 Bad Request
{
"success": false,
"error": "Invalid nodeId format"
}
🔐 安全注意事项
1. 路径遍历防护
def validate_path(path: str) -> bool:
"""防止 ../ 路径遍历攻击"""
safe_path = Path(path).resolve()
base_path = Path("cloud/").resolve()
return str(safe_path).startswith(str(base_path))
2. 文件大小限制
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
MAX_NODE_DATA_SIZE = 10 * 1024 * 1024 # 10MB
# 在上传和保存时检查大小
if len(json.dumps(request.nodeData)) > MAX_NODE_DATA_SIZE:
raise HTTPException(413, "Node data too large")
3. 用户权限验证
def check_user_permission(username: str, resource_path: str) -> bool:
"""验证用户只能访问自己的资源"""
if not resource_path.startswith(f"users/{username}/"):
raise HTTPException(403, "Permission denied")
📦 数据库 Schema(可选)
如果使用数据库存储节点数据,建议的表结构:
-- 节点数据表
CREATE TABLE nodes (
node_id TEXT PRIMARY KEY,
node_type TEXT NOT NULL,
node_data TEXT NOT NULL, -- JSON 格式
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
version INTEGER DEFAULT 1
);
-- 创建索引
CREATE INDEX idx_nodes_created_by ON nodes(created_by);
CREATE INDEX idx_nodes_created_at ON nodes(created_at);
-- 节点历史表(可选,用于版本控制)
CREATE TABLE node_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL,
node_data TEXT NOT NULL,
version INTEGER NOT NULL,
saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (node_id) REFERENCES nodes(node_id)
);
🚀 实施步骤
阶段 1: 基础功能(立即实施)
- ✅ 实现
GET /api/users/list接口 - ✅ 测试用户列表显示
阶段 2: 节点持久化(后续实施)
- ⏳ 实现
POST /api/nodes/save接口 - ⏳ 添加节点加载接口
GET /api/nodes/{nodeId} - ⏳ 添加节点列表接口
GET /api/nodes/list
阶段 3: 增强功能(可选)
- ⏸️ 节点版本控制
- ⏸️ 多用户协作编辑
- ⏸️ 节点模板管理
📝 前端调用示例
获取用户列表
// web/src/components/HeaderBar.tsx
React.useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch('/api/users/list')
if (response.ok) {
const data = await response.json()
setUserList(data.users)
}
} catch (error) {
console.warn('获取用户列表失败,使用默认列表')
}
}
fetchUsers()
}, [])
保存节点数据
// web/src/components/Inspector.tsx
const handleSaveNode = async () => {
try {
const response = await fetch('/api/nodes/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
nodeId: node.id,
nodeData: node.data
})
})
if (response.ok) {
const result = await response.json()
alert(`✅ 节点已保存: ${result.savedAt}`)
} else {
const error = await response.text()
alert(`❌ 保存失败: ${error}`)
}
} catch (error) {
alert(`❌ 网络错误: ${error}`)
}
}
🔄 版本历史
| 版本 | 日期 | 更新内容 |
|---|---|---|
| v1.0 | 2026-01-07 | 初始版本,定义用户列表和节点保存API |
📧 联系方式
如有疑问或建议,请通过以下方式联系:
- GitHub Issues: [TraceStudio Issues]
- 文档维护者: 前端开发团队