# 后端 API 需求文档 ## 📋 概述 本文档描述了前端新增功能所需的后端 API 接口。这些接口用于支持多用户管理、节点数据持久化等功能。 --- ## 🔌 新增 API 接口 ### 1. 获取用户列表 #### 接口信息 - **路径**: `GET /api/users/list` - **用途**: 获取系统中所有用户的列表,用于用户切换下拉框显示 - **优先级**: ⭐⭐⭐ 高 #### 请求参数 无 #### 响应格式 ```json { "users": ["guest", "admin", "dev", "test", "user1", "user2"], "count": 6 } ``` #### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | users | string[] | 用户名列表 | | count | integer | 用户总数 | #### 实现建议 1. 扫描 `cloud/users/` 目录,返回所有子目录名称 2. 按字母顺序排序 3. 可以添加缓存机制(TTL 60秒) 4. 排除系统目录(如 `.git`, `__pycache__` 等) #### 示例代码 ```python 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` ```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): ```json { "success": true, "nodeId": "n_1704614400000_1234", "savedAt": "2026-01-07T12:34:56.789Z" } ``` **失败响应** (400): ```json { "success": false, "error": "Invalid nodeId format" } ``` #### 实现建议 ##### 存储方案 A:文件系统 ```python # 存储路径: 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 数据库 ```python # 使用 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 } ``` #### 使用场景 1. 用户在 Inspector 面板编辑节点参数 2. 点击 "💾 保存" 按钮 3. 前端调用此 API 保存节点状态 4. 可用于恢复节点配置、协作编辑等场景 #### 扩展功能(可选) - 添加用户权限验证(只能保存自己的节点) - 节点版本控制(保存历史记录) - 批量保存接口 `POST /api/nodes/batch-save` --- ## 🔄 现有 API 增强建议 ### 文件上传 API 增强 **当前**: `POST /api/files/upload?path=xxx&username=xxx` **建议增强**: 1. 支持批量上传(拖拽多个文件) 2. 返回上传文件的详细信息(路径、大小、类型) 3. 添加上传进度支持(WebSocket 或 Server-Sent Events) ```python @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 ```bash # 测试 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 ```bash # 测试 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. 路径遍历防护 ```python 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. 文件大小限制 ```python 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. 用户权限验证 ```python def check_user_permission(username: str, resource_path: str) -> bool: """验证用户只能访问自己的资源""" if not resource_path.startswith(f"users/{username}/"): raise HTTPException(403, "Permission denied") ``` --- ## 📦 数据库 Schema(可选) 如果使用数据库存储节点数据,建议的表结构: ```sql -- 节点数据表 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: 基础功能(立即实施) 1. ✅ 实现 `GET /api/users/list` 接口 2. ✅ 测试用户列表显示 ### 阶段 2: 节点持久化(后续实施) 1. ⏳ 实现 `POST /api/nodes/save` 接口 2. ⏳ 添加节点加载接口 `GET /api/nodes/{nodeId}` 3. ⏳ 添加节点列表接口 `GET /api/nodes/list` ### 阶段 3: 增强功能(可选) 1. ⏸️ 节点版本控制 2. ⏸️ 多用户协作编辑 3. ⏸️ 节点模板管理 --- ## 📝 前端调用示例 ### 获取用户列表 ```typescript // 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() }, []) ``` ### 保存节点数据 ```typescript // 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] - 文档维护者: 前端开发团队