优化node_management.py
This commit is contained in:
parent
d348a656c0
commit
5a758b3a8f
@ -13,6 +13,7 @@ from jingrow.utils.fs import atomic_write_json
|
|||||||
from jingrow.utils.jingrow_api import get_record_id, create_record, update_record, get_record_list
|
from jingrow.utils.jingrow_api import get_record_id, create_record, update_record, get_record_list
|
||||||
from jingrow.utils.auth import get_jingrow_cloud_url, get_jingrow_cloud_api_headers
|
from jingrow.utils.auth import get_jingrow_cloud_url, get_jingrow_cloud_api_headers
|
||||||
from jingrow.config import Config
|
from jingrow.config import Config
|
||||||
|
from jingrow.utils.path import get_root_path, get_apps_path
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -33,8 +34,7 @@ async def export_node_definition(payload: Dict[str, Any]):
|
|||||||
|
|
||||||
export_data = {"metadata": metadata, **(schema or {})}
|
export_data = {"metadata": metadata, **(schema or {})}
|
||||||
|
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1] # 修正路径层级
|
|
||||||
new_root = jingrow_root / "ai" / "nodes"
|
new_root = jingrow_root / "ai" / "nodes"
|
||||||
target = new_root / node_type / f"{node_type}.json"
|
target = new_root / node_type / f"{node_type}.json"
|
||||||
atomic_write_json(target, export_data)
|
atomic_write_json(target, export_data)
|
||||||
@ -49,8 +49,7 @@ async def import_local_node_definitions():
|
|||||||
扫描本地节点定义目录,按 metadata 去重后导入到 Local Ai Node。
|
扫描本地节点定义目录,按 metadata 去重后导入到 Local Ai Node。
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1] # 修正路径层级
|
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
if not nodes_root.exists():
|
if not nodes_root.exists():
|
||||||
return {"success": True, "matched": 0, "imported": 0, "skipped_existing": 0}
|
return {"success": True, "matched": 0, "imported": 0, "skipped_existing": 0}
|
||||||
@ -128,8 +127,7 @@ async def get_all_node_metadata():
|
|||||||
获取所有节点的元数据,用于流程编排界面
|
获取所有节点的元数据,用于流程编排界面
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1] # 修正路径层级
|
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
|
|
||||||
if not nodes_root.exists():
|
if not nodes_root.exists():
|
||||||
@ -177,8 +175,7 @@ async def get_node_schema(node_type: str):
|
|||||||
获取指定节点类型的Schema配置
|
获取指定节点类型的Schema配置
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1]
|
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
json_file = nodes_root / node_type / f"{node_type}.json"
|
json_file = nodes_root / node_type / f"{node_type}.json"
|
||||||
|
|
||||||
@ -303,151 +300,148 @@ async def check_node_exists(node_type: str):
|
|||||||
|
|
||||||
@router.get("/jingrow/installed-node-types")
|
@router.get("/jingrow/installed-node-types")
|
||||||
async def get_installed_node_types():
|
async def get_installed_node_types():
|
||||||
"""
|
"""
|
||||||
获取已安装的节点类型列表
|
获取已安装的节点类型列表
|
||||||
通过扫描节点目录获取,这是最高效的方式(只需要列出目录名,不需要读取文件)
|
通过扫描节点目录获取,这是最高效的方式(只需要列出目录名,不需要读取文件)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 确定节点目录路径:apps/jingrow/jingrow/ai/nodes
|
# 确定节点目录路径:apps/jingrow/jingrow/ai/nodes
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1] # jingrow
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
|
||||||
|
node_types = []
|
||||||
node_types = []
|
|
||||||
|
# 如果目录不存在,返回空列表
|
||||||
# 如果目录不存在,返回空列表
|
if not nodes_root.exists():
|
||||||
if not nodes_root.exists():
|
return {"success": True, "node_types": []}
|
||||||
return {"success": True, "node_types": []}
|
|
||||||
|
# 扫描目录,只获取目录名(最高效的方式)
|
||||||
# 扫描目录,只获取目录名(最高效的方式)
|
for item in nodes_root.iterdir():
|
||||||
for item in nodes_root.iterdir():
|
if item.is_dir() and not item.name.startswith('.') and item.name != '__pycache__':
|
||||||
if item.is_dir() and not item.name.startswith('.') and item.name != '__pycache__':
|
# 验证是否包含节点定义文件(可选,但更可靠)
|
||||||
# 验证是否包含节点定义文件(可选,但更可靠)
|
json_file = item / f"{item.name}.json"
|
||||||
json_file = item / f"{item.name}.json"
|
if json_file.exists():
|
||||||
if json_file.exists():
|
node_types.append(item.name)
|
||||||
node_types.append(item.name)
|
|
||||||
|
return {"success": True, "node_types": sorted(node_types)}
|
||||||
return {"success": True, "node_types": sorted(node_types)}
|
except Exception as e:
|
||||||
except Exception as e:
|
logger.error(f"获取已安装节点类型失败: {str(e)}")
|
||||||
logger.error(f"获取已安装节点类型失败: {str(e)}")
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
return {"success": True, "node_types": []}
|
||||||
return {"success": True, "node_types": []}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jingrow/install-node-from-url")
|
@router.post("/jingrow/install-node-from-url")
|
||||||
async def install_node_from_url(url: str = Form(...)):
|
async def install_node_from_url(url: str = Form(...)):
|
||||||
"""从URL安装节点"""
|
"""从URL安装节点"""
|
||||||
try:
|
try:
|
||||||
# 下载文件
|
# 下载文件
|
||||||
current = Path(__file__).resolve()
|
root = get_root_path()
|
||||||
root = current.parents[4]
|
tmp_dir = root / "tmp"
|
||||||
tmp_dir = root / "tmp"
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
# 创建临时文件
|
||||||
# 创建临时文件
|
temp_filename = f"node_download_{uuid.uuid4().hex[:8]}{Path(url).suffix}"
|
||||||
temp_filename = f"node_download_{uuid.uuid4().hex[:8]}{Path(url).suffix}"
|
temp_file_path = tmp_dir / temp_filename
|
||||||
temp_file_path = tmp_dir / temp_filename
|
|
||||||
|
# 下载文件
|
||||||
# 下载文件
|
response = requests.get(url, stream=True, timeout=300)
|
||||||
response = requests.get(url, stream=True, timeout=300)
|
response.raise_for_status()
|
||||||
response.raise_for_status()
|
|
||||||
|
with open(temp_file_path, 'wb') as f:
|
||||||
with open(temp_file_path, 'wb') as f:
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
for chunk in response.iter_content(chunk_size=8192):
|
f.write(chunk)
|
||||||
f.write(chunk)
|
|
||||||
|
# 安装节点
|
||||||
# 安装节点
|
result = _install_node_from_file(str(temp_file_path))
|
||||||
result = _install_node_from_file(str(temp_file_path))
|
|
||||||
|
# 清理临时文件
|
||||||
# 清理临时文件
|
if temp_file_path.exists():
|
||||||
if temp_file_path.exists():
|
os.remove(temp_file_path)
|
||||||
os.remove(temp_file_path)
|
|
||||||
|
return result
|
||||||
return result
|
|
||||||
|
except Exception as e:
|
||||||
except Exception as e:
|
logger.error(f"从URL安装节点失败: {str(e)}")
|
||||||
logger.error(f"从URL安装节点失败: {str(e)}")
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
raise HTTPException(status_code=500, detail=f"安装节点失败: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"安装节点失败: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jingrow/install-node-from-git")
|
@router.post("/jingrow/install-node-from-git")
|
||||||
async def install_node_from_git(repo_url: str = Form(...)):
|
async def install_node_from_git(repo_url: str = Form(...)):
|
||||||
"""从git仓库克隆并安装节点"""
|
"""从git仓库克隆并安装节点"""
|
||||||
try:
|
try:
|
||||||
current = Path(__file__).resolve()
|
root = get_root_path()
|
||||||
root = current.parents[4]
|
tmp_dir = root / "tmp"
|
||||||
tmp_dir = root / "tmp"
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
# 创建临时目录用于克隆
|
||||||
# 创建临时目录用于克隆
|
clone_dir = tmp_dir / f"node_git_clone_{uuid.uuid4().hex[:8]}"
|
||||||
clone_dir = tmp_dir / f"node_git_clone_{uuid.uuid4().hex[:8]}"
|
|
||||||
|
try:
|
||||||
try:
|
# 使用 git clone 克隆仓库
|
||||||
# 使用 git clone 克隆仓库
|
result = subprocess.run(
|
||||||
result = subprocess.run(
|
['git', 'clone', repo_url, str(clone_dir)],
|
||||||
['git', 'clone', repo_url, str(clone_dir)],
|
capture_output=True,
|
||||||
capture_output=True,
|
text=True,
|
||||||
text=True,
|
timeout=300
|
||||||
timeout=300
|
)
|
||||||
)
|
|
||||||
|
if result.returncode != 0:
|
||||||
if result.returncode != 0:
|
raise HTTPException(status_code=400, detail=f"Git 克隆失败: {result.stderr}")
|
||||||
raise HTTPException(status_code=400, detail=f"Git 克隆失败: {result.stderr}")
|
|
||||||
|
# 查找节点目录(节点包应该包含一个或多个节点目录)
|
||||||
# 查找节点目录(节点包应该包含一个或多个节点目录)
|
node_dirs = []
|
||||||
node_dirs = []
|
for item in clone_dir.iterdir():
|
||||||
for item in clone_dir.iterdir():
|
if item.is_dir() and not item.name.startswith('.') and item.name != '__pycache__':
|
||||||
if item.is_dir() and not item.name.startswith('.') and item.name != '__pycache__':
|
json_file = item / f"{item.name}.json"
|
||||||
json_file = item / f"{item.name}.json"
|
if json_file.exists():
|
||||||
if json_file.exists():
|
node_dirs.append(item)
|
||||||
node_dirs.append(item)
|
|
||||||
|
if not node_dirs:
|
||||||
if not node_dirs:
|
raise HTTPException(status_code=400, detail="仓库中没有找到节点定义文件")
|
||||||
raise HTTPException(status_code=400, detail="仓库中没有找到节点定义文件")
|
|
||||||
|
# 安装所有找到的节点
|
||||||
# 安装所有找到的节点
|
installed_nodes = []
|
||||||
installed_nodes = []
|
errors = []
|
||||||
errors = []
|
|
||||||
|
for node_dir in node_dirs:
|
||||||
for node_dir in node_dirs:
|
try:
|
||||||
try:
|
result = _install_single_node_directory(str(node_dir))
|
||||||
result = _install_single_node_directory(str(node_dir))
|
if result.get('success'):
|
||||||
if result.get('success'):
|
installed_nodes.append(node_dir.name)
|
||||||
installed_nodes.append(node_dir.name)
|
else:
|
||||||
else:
|
errors.append(f"{node_dir.name}: {result.get('error')}")
|
||||||
errors.append(f"{node_dir.name}: {result.get('error')}")
|
except Exception as e:
|
||||||
except Exception as e:
|
errors.append(f"{node_dir.name}: {str(e)}")
|
||||||
errors.append(f"{node_dir.name}: {str(e)}")
|
|
||||||
|
# 清理临时目录
|
||||||
# 清理临时目录
|
shutil.rmtree(clone_dir, ignore_errors=True)
|
||||||
shutil.rmtree(clone_dir, ignore_errors=True)
|
|
||||||
|
if errors:
|
||||||
if errors:
|
return {
|
||||||
return {
|
'success': len(installed_nodes) > 0,
|
||||||
'success': len(installed_nodes) > 0,
|
'installed': installed_nodes,
|
||||||
'installed': installed_nodes,
|
'errors': errors,
|
||||||
'errors': errors,
|
'message': f"成功安装 {len(installed_nodes)} 个节点,失败 {len(errors)} 个"
|
||||||
'message': f"成功安装 {len(installed_nodes)} 个节点,失败 {len(errors)} 个"
|
}
|
||||||
}
|
else:
|
||||||
else:
|
return {
|
||||||
return {
|
'success': True,
|
||||||
'success': True,
|
'installed': installed_nodes,
|
||||||
'installed': installed_nodes,
|
'message': f"成功安装 {len(installed_nodes)} 个节点"
|
||||||
'message': f"成功安装 {len(installed_nodes)} 个节点"
|
}
|
||||||
}
|
|
||||||
|
finally:
|
||||||
finally:
|
# 确保清理临时目录
|
||||||
# 确保清理临时目录
|
if clone_dir.exists():
|
||||||
if clone_dir.exists():
|
shutil.rmtree(clone_dir, ignore_errors=True)
|
||||||
shutil.rmtree(clone_dir, ignore_errors=True)
|
|
||||||
|
except HTTPException:
|
||||||
except HTTPException:
|
raise
|
||||||
raise
|
except Exception as e:
|
||||||
except Exception as e:
|
logger.error(f"从Git安装节点失败: {str(e)}")
|
||||||
logger.error(f"从Git安装节点失败: {str(e)}")
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
raise HTTPException(status_code=500, detail=f"安装节点失败: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"安装节点失败: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
def _install_node_from_file(file_path: str) -> Dict[str, Any]:
|
def _install_node_from_file(file_path: str) -> Dict[str, Any]:
|
||||||
@ -511,165 +505,161 @@ def _install_node_from_file(file_path: str) -> Dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def _install_single_node_directory(node_dir: str) -> Dict[str, Any]:
|
def _install_single_node_directory(node_dir: str) -> Dict[str, Any]:
|
||||||
"""安装单个节点目录到 ai/nodes 并导入数据库"""
|
"""安装单个节点目录到 ai/nodes 并导入数据库"""
|
||||||
try:
|
try:
|
||||||
node_dir_path = Path(node_dir)
|
node_dir_path = Path(node_dir)
|
||||||
node_name = node_dir_path.name
|
node_name = node_dir_path.name
|
||||||
|
|
||||||
# 读取节点定义文件
|
# 读取节点定义文件
|
||||||
json_file = node_dir_path / f"{node_name}.json"
|
json_file = node_dir_path / f"{node_name}.json"
|
||||||
if not json_file.exists():
|
if not json_file.exists():
|
||||||
return {'success': False, 'error': f'找不到节点定义文件: {json_file.name}'}
|
return {'success': False, 'error': f'找不到节点定义文件: {json_file.name}'}
|
||||||
|
|
||||||
with open(json_file, 'r', encoding='utf-8') as f:
|
with open(json_file, 'r', encoding='utf-8') as f:
|
||||||
node_data = json.load(f)
|
node_data = json.load(f)
|
||||||
|
|
||||||
if not isinstance(node_data, dict):
|
if not isinstance(node_data, dict):
|
||||||
return {'success': False, 'error': '节点定义文件格式错误'}
|
return {'success': False, 'error': '节点定义文件格式错误'}
|
||||||
|
|
||||||
metadata = node_data.get("metadata") or {}
|
metadata = node_data.get("metadata") or {}
|
||||||
node_type = metadata.get("type")
|
node_type = metadata.get("type")
|
||||||
if not node_type:
|
if not node_type:
|
||||||
return {'success': False, 'error': '节点定义中缺少 metadata.type'}
|
return {'success': False, 'error': '节点定义中缺少 metadata.type'}
|
||||||
|
|
||||||
# 确定目标目录:apps/jingrow/jingrow/ai/nodes
|
# 确定目标目录:apps/jingrow/jingrow/ai/nodes
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
# node_management.py 位于 jingrow/api/
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
# parents[0] = jingrow/api, parents[1] = jingrow
|
nodes_root.mkdir(parents=True, exist_ok=True)
|
||||||
jingrow_root = current_file.parents[1] # jingrow
|
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
target_node_dir = nodes_root / node_type
|
||||||
nodes_root.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
# 如果目标目录已存在,先删除
|
||||||
target_node_dir = nodes_root / node_type
|
if target_node_dir.exists():
|
||||||
|
shutil.rmtree(target_node_dir)
|
||||||
# 如果目标目录已存在,先删除
|
|
||||||
if target_node_dir.exists():
|
# 复制整个节点目录
|
||||||
shutil.rmtree(target_node_dir)
|
shutil.copytree(node_dir_path, target_node_dir)
|
||||||
|
|
||||||
# 复制整个节点目录
|
# 导入到数据库
|
||||||
shutil.copytree(node_dir_path, target_node_dir)
|
# 检查是否已存在
|
||||||
|
exists_res = get_record_id(
|
||||||
# 导入到数据库
|
pagetype="Local Ai Node",
|
||||||
# 检查是否已存在
|
field="node_type",
|
||||||
exists_res = get_record_id(
|
value=node_type,
|
||||||
pagetype="Local Ai Node",
|
)
|
||||||
field="node_type",
|
|
||||||
value=node_type,
|
# 生成 schema(移除 metadata)
|
||||||
)
|
schema = dict(node_data)
|
||||||
|
schema.pop("metadata", None)
|
||||||
# 生成 schema(移除 metadata)
|
|
||||||
schema = dict(node_data)
|
payload = {
|
||||||
schema.pop("metadata", None)
|
"node_type": node_type,
|
||||||
|
"node_label": metadata.get("label") or node_type,
|
||||||
payload = {
|
"node_icon": metadata.get("icon") or "fa-cube",
|
||||||
"node_type": node_type,
|
"node_color": metadata.get("color") or "#6b7280",
|
||||||
"node_label": metadata.get("label") or node_type,
|
"node_group": metadata.get("group") or "",
|
||||||
"node_icon": metadata.get("icon") or "fa-cube",
|
"node_component": metadata.get("component_type") or "GenericNode",
|
||||||
"node_color": metadata.get("color") or "#6b7280",
|
"node_description": metadata.get("description") or "",
|
||||||
"node_group": metadata.get("group") or "",
|
"status": "Published",
|
||||||
"node_component": metadata.get("component_type") or "GenericNode",
|
"node_schema": schema,
|
||||||
"node_description": metadata.get("description") or "",
|
}
|
||||||
"status": "Published",
|
|
||||||
"node_schema": schema,
|
if exists_res.get("success"):
|
||||||
}
|
# 更新现有记录,使用 node_type 作为 name
|
||||||
|
res = update_record("Local Ai Node", node_type, payload)
|
||||||
if exists_res.get("success"):
|
else:
|
||||||
# 更新现有记录,使用 node_type 作为 name
|
# 创建新记录
|
||||||
res = update_record("Local Ai Node", node_type, payload)
|
res = create_record("Local Ai Node", payload)
|
||||||
else:
|
|
||||||
# 创建新记录
|
if res.get("success"):
|
||||||
res = create_record("Local Ai Node", payload)
|
return {'success': True, 'node_type': node_type, 'message': f'节点 {node_type} 安装成功'}
|
||||||
|
else:
|
||||||
if res.get("success"):
|
return {'success': False, 'error': res.get('error', '导入数据库失败')}
|
||||||
return {'success': True, 'node_type': node_type, 'message': f'节点 {node_type} 安装成功'}
|
|
||||||
else:
|
except Exception as e:
|
||||||
return {'success': False, 'error': res.get('error', '导入数据库失败')}
|
logger.error(f"安装节点目录失败: {str(e)}")
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"安装节点目录失败: {str(e)}")
|
|
||||||
return {'success': False, 'error': str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jingrow/node/package/{node_type}")
|
@router.post("/jingrow/node/package/{node_type}")
|
||||||
async def package_node(node_type: str):
|
async def package_node(node_type: str):
|
||||||
"""
|
"""
|
||||||
打包节点文件夹为zip文件,返回文件路径用于后续上传
|
打包节点文件夹为zip文件,返回文件路径用于后续上传
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
current_file = Path(__file__).resolve()
|
jingrow_root = get_apps_path() / "jingrow"
|
||||||
jingrow_root = current_file.parents[1]
|
nodes_root = jingrow_root / "ai" / "nodes"
|
||||||
nodes_root = jingrow_root / "ai" / "nodes"
|
node_dir = nodes_root / node_type
|
||||||
node_dir = nodes_root / node_type
|
|
||||||
|
if not node_dir.exists():
|
||||||
if not node_dir.exists():
|
raise HTTPException(status_code=404, detail=f"节点目录不存在: {node_type}")
|
||||||
raise HTTPException(status_code=404, detail=f"节点目录不存在: {node_type}")
|
|
||||||
|
# 创建临时打包目录
|
||||||
# 创建临时打包目录
|
root = get_root_path()
|
||||||
root = current_file.parents[4]
|
tmp_dir = root / "tmp"
|
||||||
tmp_dir = root / "tmp"
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
# 创建临时目录用于打包,文件夹名称保持为节点类型(不加时间戳)
|
||||||
# 创建临时目录用于打包,文件夹名称保持为节点类型(不加时间戳)
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
temp_package_dir = tmp_dir / node_type
|
||||||
temp_package_dir = tmp_dir / node_type
|
if temp_package_dir.exists():
|
||||||
if temp_package_dir.exists():
|
# 如果临时目录已存在,先删除
|
||||||
# 如果临时目录已存在,先删除
|
shutil.rmtree(temp_package_dir)
|
||||||
shutil.rmtree(temp_package_dir)
|
temp_package_dir.mkdir(parents=True, exist_ok=True)
|
||||||
temp_package_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
try:
|
||||||
try:
|
# 复制节点目录内容(排除不必要的文件)
|
||||||
# 复制节点目录内容(排除不必要的文件)
|
for item in node_dir.iterdir():
|
||||||
for item in node_dir.iterdir():
|
if item.name in ['__pycache__', '.git', '.DS_Store', '.pytest_cache']:
|
||||||
if item.name in ['__pycache__', '.git', '.DS_Store', '.pytest_cache']:
|
continue
|
||||||
continue
|
dst = temp_package_dir / item.name
|
||||||
dst = temp_package_dir / item.name
|
if item.is_dir():
|
||||||
if item.is_dir():
|
shutil.copytree(item, dst, dirs_exist_ok=True)
|
||||||
shutil.copytree(item, dst, dirs_exist_ok=True)
|
else:
|
||||||
else:
|
shutil.copy2(item, dst)
|
||||||
shutil.copy2(item, dst)
|
|
||||||
|
# 打包为 ZIP,zip文件名格式:{node_type}-{timestamp}.zip,但内部文件夹名称保持为节点类型
|
||||||
# 打包为 ZIP,zip文件名格式:{node_type}-{timestamp}.zip,但内部文件夹名称保持为节点类型
|
zip_filename = f"{node_type}-{timestamp}.zip"
|
||||||
zip_filename = f"{node_type}-{timestamp}.zip"
|
zip_base_name = tmp_dir / f"{node_type}-{timestamp}"
|
||||||
zip_base_name = tmp_dir / f"{node_type}-{timestamp}"
|
# base_dir 使用 node_type,这样 zip 内部文件夹名称就是 node_type
|
||||||
# base_dir 使用 node_type,这样 zip 内部文件夹名称就是 node_type
|
shutil.make_archive(str(zip_base_name), 'zip', root_dir=str(tmp_dir), base_dir=node_type)
|
||||||
shutil.make_archive(str(zip_base_name), 'zip', root_dir=str(tmp_dir), base_dir=node_type)
|
|
||||||
|
zip_path = tmp_dir / f"{zip_filename}"
|
||||||
zip_path = tmp_dir / f"{zip_filename}"
|
|
||||||
|
if not zip_path.exists():
|
||||||
if not zip_path.exists():
|
raise HTTPException(status_code=500, detail="ZIP文件创建失败")
|
||||||
raise HTTPException(status_code=500, detail="ZIP文件创建失败")
|
|
||||||
|
# 读取文件内容
|
||||||
# 读取文件内容
|
with open(zip_path, 'rb') as f:
|
||||||
with open(zip_path, 'rb') as f:
|
zip_content = f.read()
|
||||||
zip_content = f.read()
|
|
||||||
|
# 清理临时文件
|
||||||
# 清理临时文件
|
if zip_path.exists():
|
||||||
if zip_path.exists():
|
os.remove(zip_path)
|
||||||
os.remove(zip_path)
|
|
||||||
|
# 返回文件内容,前端可以直接使用
|
||||||
# 返回文件内容,前端可以直接使用
|
return Response(
|
||||||
return Response(
|
content=zip_content,
|
||||||
content=zip_content,
|
media_type="application/zip",
|
||||||
media_type="application/zip",
|
headers={
|
||||||
headers={
|
"Content-Disposition": f"attachment; filename={zip_filename}",
|
||||||
"Content-Disposition": f"attachment; filename={zip_filename}",
|
"Content-Type": "application/zip"
|
||||||
"Content-Type": "application/zip"
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
finally:
|
||||||
finally:
|
# 清理临时目录
|
||||||
# 清理临时目录
|
if temp_package_dir.exists():
|
||||||
if temp_package_dir.exists():
|
shutil.rmtree(temp_package_dir, ignore_errors=True)
|
||||||
shutil.rmtree(temp_package_dir, ignore_errors=True)
|
|
||||||
|
except HTTPException:
|
||||||
except HTTPException:
|
raise
|
||||||
raise
|
except Exception as e:
|
||||||
except Exception as e:
|
logger.error(f"打包节点失败: {str(e)}")
|
||||||
logger.error(f"打包节点失败: {str(e)}")
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
raise HTTPException(status_code=500, detail=f"打包节点失败: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"打包节点失败: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/jingrow/node/publish")
|
@router.post("/jingrow/node/publish")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user