2025-04-30 16:57:46 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
"""
|
2025-09-12 16:15:13 +08:00
|
|
|
|
脚本管理API路由
|
|
|
|
|
提供脚本项目和文件的CRUD操作接口
|
2025-04-30 16:57:46 +08:00
|
|
|
|
"""
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
from services.script_file_service import get_file_service
|
|
|
|
|
from services.script_engine_service import get_script_engine
|
|
|
|
|
from utils.api_response import success_response, error_response
|
2025-04-30 16:57:46 +08:00
|
|
|
|
from utils.logger import get_logger
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
logger = get_logger("routes.script_api")
|
|
|
|
|
router = APIRouter(prefix="/api/script", tags=["在线脚本模块"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 请求模型定义
|
|
|
|
|
class ProjectCreateRequest(BaseModel):
|
|
|
|
|
project_name: str
|
|
|
|
|
description: Optional[str] = ""
|
|
|
|
|
created_by: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FileCreateRequest(BaseModel):
|
|
|
|
|
project_id: int
|
|
|
|
|
file_name: str
|
|
|
|
|
file_path: str
|
|
|
|
|
content: Optional[str] = ""
|
|
|
|
|
file_type: Optional[str] = "python"
|
|
|
|
|
created_by: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FileUpdateRequest(BaseModel):
|
|
|
|
|
content: str
|
|
|
|
|
updated_by: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScriptStartRequest(BaseModel):
|
|
|
|
|
script_path: str
|
|
|
|
|
start_params: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FunctionCallRequest(BaseModel):
|
|
|
|
|
script_id: str
|
|
|
|
|
function_name: str
|
|
|
|
|
function_args: Any = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/projects", summary="创建脚本项目")
|
|
|
|
|
async def create_project(request: ProjectCreateRequest):
|
|
|
|
|
"""创建新的脚本项目"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.create_project(
|
|
|
|
|
project_name=request.project_name,
|
|
|
|
|
description=request.description,
|
|
|
|
|
created_by=request.created_by
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return result
|
|
|
|
|
# return success_response(data=result["project"], message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"创建项目失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"创建项目失败: {str(e)}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
@router.get("/projects", summary="获取项目列表")
|
|
|
|
|
async def get_projects(status: Optional[str] = "active"):
|
|
|
|
|
"""获取所有脚本项目"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.get_projects(status=status)
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result["projects"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"获取项目列表失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取项目列表失败: {str(e)}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
@router.get("/projects/{project_id}/files", summary="获取项目文件列表")
|
|
|
|
|
async def get_project_files(project_id: int, include_content: bool = False):
|
|
|
|
|
"""获取指定项目的所有文件"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.get_project_files(
|
|
|
|
|
project_id=project_id,
|
|
|
|
|
include_content=include_content
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result)
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"获取项目文件列表失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取项目文件列表失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/files", summary="创建脚本文件")
|
|
|
|
|
async def create_file(request: FileCreateRequest):
|
|
|
|
|
"""创建新的脚本文件"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.create_file(
|
|
|
|
|
project_id=request.project_id,
|
|
|
|
|
file_name=request.file_name,
|
|
|
|
|
file_path=request.file_path,
|
|
|
|
|
content=request.content,
|
|
|
|
|
file_type=request.file_type,
|
|
|
|
|
created_by=request.created_by
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result["file"], message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"创建文件失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"创建文件失败: {str(e)}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
@router.get("/files/{file_id}", summary="获取文件内容")
|
|
|
|
|
async def get_file_content(file_id: int):
|
|
|
|
|
"""获取脚本文件的内容"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.get_file_content(file_id=file_id)
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result)
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"获取文件内容失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取文件内容失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/files/{file_id}", summary="更新文件内容")
|
|
|
|
|
async def update_file_content(file_id: int, request: FileUpdateRequest):
|
|
|
|
|
"""更新脚本文件的内容"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.update_file_content(
|
|
|
|
|
file_id=file_id,
|
|
|
|
|
content=request.content,
|
|
|
|
|
updated_by=request.updated_by
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result["file"], message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"更新文件内容失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"更新文件内容失败: {str(e)}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
@router.delete("/files/{file_id}", summary="删除文件")
|
|
|
|
|
async def delete_file(file_id: int):
|
|
|
|
|
"""删除脚本文件"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.delete_file(file_id=file_id)
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"删除文件失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"删除文件失败: {str(e)}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
@router.get("/files/search", summary="搜索文件")
|
|
|
|
|
async def search_files(
|
|
|
|
|
keyword: str,
|
|
|
|
|
project_id: Optional[int] = None,
|
|
|
|
|
file_type: Optional[str] = None,
|
|
|
|
|
has_boot: Optional[bool] = None
|
2025-04-30 16:57:46 +08:00
|
|
|
|
):
|
2025-09-12 16:15:13 +08:00
|
|
|
|
"""搜索脚本文件"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.search_files(
|
|
|
|
|
keyword=keyword,
|
|
|
|
|
project_id=project_id,
|
|
|
|
|
file_type=file_type,
|
|
|
|
|
has_boot=has_boot
|
|
|
|
|
)
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result)
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"搜索文件失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"搜索文件失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/validate-syntax", summary="验证脚本语法")
|
|
|
|
|
async def validate_syntax(request: dict):
|
|
|
|
|
"""验证Python脚本语法"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
content = request.get("content", "")
|
|
|
|
|
if not content:
|
|
|
|
|
return error_response(message="脚本内容不能为空")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.validate_script_syntax(content)
|
|
|
|
|
|
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result)
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"语法验证失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"语法验证失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/scripts/start", summary="启动脚本服务")
|
|
|
|
|
async def start_script_service(request: ScriptStartRequest):
|
|
|
|
|
"""启动脚本服务"""
|
|
|
|
|
try:
|
|
|
|
|
script_engine = get_script_engine()
|
|
|
|
|
result = await script_engine.start_script_service(
|
|
|
|
|
script_path=request.script_path,
|
|
|
|
|
start_params=request.start_params
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result, message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"启动脚本服务失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"启动脚本服务失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/scripts/{script_id}/stop", summary="停止脚本服务")
|
|
|
|
|
async def stop_script_service(script_id: str):
|
|
|
|
|
"""停止脚本服务"""
|
|
|
|
|
try:
|
|
|
|
|
script_engine = get_script_engine()
|
|
|
|
|
result = await script_engine.stop_script_service(script_id=script_id)
|
|
|
|
|
|
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"停止脚本服务失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"停止脚本服务失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/scripts/running", summary="获取运行中的脚本")
|
|
|
|
|
async def get_running_scripts():
|
|
|
|
|
"""获取所有运行中的脚本"""
|
|
|
|
|
try:
|
|
|
|
|
script_engine = get_script_engine()
|
|
|
|
|
running_scripts = script_engine.get_running_scripts()
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
return success_response(data=running_scripts)
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取运行中的脚本失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取运行中的脚本失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/scripts/execute-function", summary="执行脚本函数")
|
|
|
|
|
async def execute_function(request: FunctionCallRequest):
|
|
|
|
|
"""执行脚本中的指定函数"""
|
|
|
|
|
try:
|
|
|
|
|
script_engine = get_script_engine()
|
|
|
|
|
result = await script_engine.execute_script_function(
|
|
|
|
|
script_id=request.script_id,
|
|
|
|
|
function_name=request.function_name,
|
|
|
|
|
function_args=request.function_args
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result)
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"执行脚本函数失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"执行脚本函数失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/registry/status", summary="获取注册中心状态")
|
|
|
|
|
async def get_registry_status():
|
|
|
|
|
"""获取脚本注册中心状态"""
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
from services.script_registry_service import get_global_registry
|
|
|
|
|
registry = get_global_registry()
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
registrations = registry.get_all_registrations()
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-12 16:15:13 +08:00
|
|
|
|
return success_response(data=registrations)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取注册中心状态失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"获取注册中心状态失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/upload", summary="上传脚本文件")
|
|
|
|
|
async def upload_script_file(
|
|
|
|
|
project_id: int,
|
|
|
|
|
file: UploadFile = File(...)
|
|
|
|
|
):
|
|
|
|
|
"""上传脚本文件"""
|
|
|
|
|
try:
|
|
|
|
|
# 读取文件内容
|
|
|
|
|
content = await file.read()
|
|
|
|
|
content_str = content.decode('utf-8')
|
|
|
|
|
|
|
|
|
|
file_service = get_file_service()
|
|
|
|
|
result = await file_service.create_file(
|
|
|
|
|
project_id=project_id,
|
|
|
|
|
file_name=file.filename,
|
|
|
|
|
file_path=file.filename,
|
|
|
|
|
content=content_str,
|
|
|
|
|
file_type="python"
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
2025-09-12 16:15:13 +08:00
|
|
|
|
|
|
|
|
|
if result["success"]:
|
|
|
|
|
return success_response(data=result["file"], message=result["message"])
|
|
|
|
|
else:
|
|
|
|
|
return error_response(message=result["error"])
|
|
|
|
|
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
return error_response(message="文件编码错误,请确保文件为UTF-8编码")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
except Exception as e:
|
2025-09-12 16:15:13 +08:00
|
|
|
|
logger.error(f"上传脚本文件失败: {e}", exc_info=True)
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"上传脚本文件失败: {str(e)}")
|