476 lines
11 KiB
Markdown
476 lines
11 KiB
Markdown
# 后端 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]
|
||
- 文档维护者: 前端开发团队
|