#!/usr/bin/env python # -*- coding: utf-8 -*- """ 脚本管理API路由 提供脚本项目和文件的CRUD操作接口 """ 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.online_script.script_file_service import get_file_service from services.online_script.script_engine_service import get_script_engine from utils.api_response import success_response, error_response from utils.logger import get_logger 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: Optional[str] = None content: Optional[str] = "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\ndef boot():\n \"\"\"脚本启动函数\"\"\"\n pass" file_type: Optional[str] = "python" created_by: Optional[str] = None class FileUpdateRequest(BaseModel): content: str updated_by: Optional[str] = None class ScriptStartRequest(BaseModel): script_id: int 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): """创建新的脚本项目""" try: file_service = get_file_service() result = await file_service.create_project( project_name=request.project_name, description=request.description, created_by=request.created_by ) if result["success"]: # return result return success_response(data=result["project"], 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("/projects", summary="获取项目列表") async def get_projects(status: Optional[str] = "active"): """获取所有脚本项目""" try: file_service = get_file_service() result = await file_service.get_projects(status=status) if result["success"]: return success_response(data=result["projects"]) 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("/projects/{project_id}/files", summary="获取项目文件列表") async def get_project_files(project_id: int, include_content: bool = False): """获取指定项目的所有文件""" try: file_service = get_file_service() result = await file_service.get_project_files( project_id=project_id, include_content=include_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("/files", summary="创建脚本文件") async def create_file(request: FileCreateRequest): """创建新的脚本文件""" try: file_service = get_file_service() # 如果file_path为空,使用file_name作为默认路径 file_path = request.file_path if request.file_path else request.file_name result = await file_service.create_file( project_id=request.project_id, file_name=request.file_name, file_path=file_path, content=request.content, file_type=request.file_type, created_by=request.created_by ) if result["success"]: return success_response(data=result["file"], 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("/files/{file_id}", summary="获取文件内容") async def get_file_content(file_id: int): """获取脚本文件的内容""" try: file_service = get_file_service() result = await file_service.get_file_content(file_id=file_id) 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.put("/files/{file_id}", summary="更新文件内容") async def update_file_content(file_id: int, request: FileUpdateRequest): """更新脚本文件的内容""" try: file_service = get_file_service() result = await file_service.update_file_content( file_id=file_id, content=request.content, updated_by=request.updated_by ) if result["success"]: return success_response(data=result["file"], 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.delete("/files/{file_id}", summary="删除文件") async def delete_file(file_id: int): """删除脚本文件""" try: file_service = get_file_service() result = await file_service.delete_file(file_id=file_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("/files/search", summary="搜索文件") async def search_files( keyword: str, project_id: Optional[int] = None, file_type: Optional[str] = None, has_boot: Optional[bool] = None ): """搜索脚本文件""" try: 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 ) 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("/validate-syntax", summary="验证脚本语法") async def validate_syntax(request: dict): """验证Python脚本语法""" try: content = request.get("content", "") if not content: return error_response(message="脚本内容不能为空") 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_id=request.script_id, 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() return success_response(data=running_scripts) 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 ) 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.get("/registry/status", summary="获取注册中心状态") async def get_registry_status(): """获取脚本注册中心状态""" try: from services.online_script.script_registry_service import get_global_registry registry = get_global_registry() registrations = registry.get_all_registrations() 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" ) 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编码") except Exception as e: logger.error(f"上传脚本文件失败: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"上传脚本文件失败: {str(e)}")