# /// # plugins.py # 描述:插件管理API路由 # 作者:AI Generated # 创建日期:2026-04-05 # /// from fastapi import APIRouter, HTTPException, Depends from typing import List, Optional, Dict, Any from pydantic import BaseModel from plugins.base import PluginRegistry, PluginType from utils import require_api_key from database_service import get_db_service router = APIRouter(prefix="/plugins", tags=["插件管理"]) class PluginConfigRequest(BaseModel): name: str config: Dict[str, Any] def _sync_plugin_from_db(plugin_name: str): db_service = get_db_service() if not db_service: return plugin_config = db_service.get_plugin_config(plugin_name) plugin = PluginRegistry.get(plugin_name) if not plugin: return if plugin_config: import json plugin.initialize(json.loads(plugin_config.config_json) if plugin_config.config_json else {}) if plugin_config.enabled: plugin.enable() else: plugin.disable() @router.get("/", summary="获取所有插件") async def get_plugins(_=Depends(require_api_key)) -> Dict[str, Any]: import json plugins = PluginRegistry.get_all() result = [] for p in plugins: info = p.get_info() db_service = get_db_service() if db_service: pc = db_service.get_plugin_config(p.plugin_name) if pc: info["config"] = json.loads(pc.config_json) if pc.config_json else {} info["enabled"] = pc.enabled result.append(info) return { "success": True, "plugins": result, "count": len(result) } @router.get("/hotloaded", summary="获取热加载插件列表") async def get_hotloaded_plugins(_=Depends(require_api_key)) -> Dict[str, Any]: try: from plugins.plugin_loader import hot_plugin_loader plugins = hot_plugin_loader.get_loaded_plugins() return { "success": True, "plugins": plugins, "count": len(plugins), "plugin_dir": hot_plugin_loader.plugin_dir } except Exception as e: return {"success": False, "error": str(e)} @router.post("/hotreload", summary="手动触发插件热重载") async def trigger_hot_reload(_=Depends(require_api_key)) -> Dict[str, Any]: try: from plugins.plugin_loader import hot_plugin_loader hot_plugin_loader.scan_and_load_plugins() plugins = hot_plugin_loader.get_loaded_plugins() return { "success": True, "message": "Hot reload triggered", "plugins": plugins } except Exception as e: return {"success": False, "error": str(e)} @router.get("/type/{plugin_type}", summary="按类型获取插件") async def get_plugins_by_type(plugin_type: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: ptype = PluginType(plugin_type) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid plugin type: {plugin_type}") plugins = PluginRegistry.get_by_type(ptype) return { "success": True, "plugins": [p.get_info() for p in plugins], "count": len(plugins) } @router.get("/custom/list", summary="列出自定义插件文件") async def list_custom_plugins(_=Depends(require_api_key)) -> Dict[str, Any]: try: import os custom_dir = "/app/plugins/custom" if not os.path.exists(custom_dir): return {"success": True, "plugins": [], "count": 0} files = [] for f in os.listdir(custom_dir): if f.endswith('.py') and not f.startswith('_'): filepath = os.path.join(custom_dir, f) stat = os.stat(filepath) files.append({ "name": f, "size": stat.st_size, "modified": stat.st_mtime, "path": filepath }) return { "success": True, "plugins": files, "count": len(files), "directory": custom_dir } except Exception as e: return {"success": False, "error": str(e)} @router.post("/custom/create", summary="创建自定义插件文件") async def create_custom_plugin(name: str, code: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: import os custom_dir = "/app/plugins/custom" os.makedirs(custom_dir, exist_ok=True) safe_name = name.replace("..", "").replace("/", "").replace("\\", "") if not safe_name.endswith(".py"): safe_name += ".py" filepath = os.path.join(custom_dir, safe_name) with open(filepath, 'w') as f: f.write(code) from plugins.plugin_loader import hot_plugin_loader hot_plugin_loader.scan_and_load_plugins() return { "success": True, "message": f"Plugin {safe_name} created", "path": filepath } except Exception as e: return {"success": False, "error": str(e)} @router.delete("/custom/{plugin_name}", summary="删除自定义插件") async def delete_custom_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: import os custom_dir = "/app/plugins/custom" safe_name = plugin_name.replace("..", "").replace("/", "").replace("\\", "") if not safe_name.endswith(".py"): safe_name += ".py" filepath = os.path.join(custom_dir, safe_name) if os.path.exists(filepath): os.remove(filepath) from plugins.plugin_loader import hot_plugin_loader hot_plugin_loader.scan_and_load_plugins() return {"success": True, "message": f"Plugin {safe_name} deleted"} else: return {"success": False, "error": "Plugin file not found"} except Exception as e: return {"success": False, "error": str(e)} @router.get("/custom/{plugin_name}/download", summary="下载自定义插件源码") async def download_custom_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: import os custom_dir = "/app/plugins/custom" safe_name = plugin_name.replace("..", "").replace("/", "").replace("\\", "") if not safe_name.endswith(".py"): safe_name += ".py" filepath = os.path.join(custom_dir, safe_name) if os.path.exists(filepath): with open(filepath, 'r') as f: code = f.read() return { "success": True, "name": safe_name, "code": code } else: return {"success": False, "error": "Plugin file not found"} except Exception as e: return {"success": False, "error": str(e)} @router.get("/{plugin_name}", summary="获取插件详情") async def get_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: import json plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") info = plugin.get_info() db_service = get_db_service() if db_service: pc = db_service.get_plugin_config(plugin_name) if pc: info["config"] = json.loads(pc.config_json) if pc.config_json else {} info["enabled"] = pc.enabled return {"success": True, "plugin": info} @router.post("/{plugin_name}/enable", summary="启用插件") async def enable_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") PluginRegistry.enable(plugin_name) db_service = get_db_service() if db_service: pc = db_service.get_plugin_config(plugin_name) if pc: db_service.update_plugin_enabled(plugin_name, True) else: db_service.save_plugin_config(plugin_name, plugin.config or {}, True) return {"success": True, "message": f"Plugin '{plugin_name}' enabled"} @router.post("/{plugin_name}/disable", summary="禁用插件") async def disable_plugin(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") PluginRegistry.disable(plugin_name) db_service = get_db_service() if db_service: pc = db_service.get_plugin_config(plugin_name) if pc: db_service.update_plugin_enabled(plugin_name, False) else: db_service.save_plugin_config(plugin_name, plugin.config or {}, False) return {"success": True, "message": f"Plugin '{plugin_name}' disabled"} @router.put("/{plugin_name}/config", summary="更新插件配置") async def update_plugin_config(plugin_name: str, request: PluginConfigRequest, _=Depends(require_api_key)) -> Dict[str, Any]: plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") success = plugin.initialize(request.config) if not success: raise HTTPException(status_code=400, detail=f"Failed to initialize plugin '{plugin_name}'") db_service = get_db_service() if db_service: pc = db_service.get_plugin_config(plugin_name) enabled = pc.enabled if pc else False db_service.save_plugin_config(plugin_name, request.config, enabled) return {"success": True, "message": f"Plugin '{plugin_name}' config updated"} @router.post("/{plugin_name}/execute", summary="执行插件") async def execute_plugin(plugin_name: str, request: Optional[Dict[str, Any]] = None, _=Depends(require_api_key)) -> Dict[str, Any]: plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") if not plugin.enabled: return {"success": False, "error": f"Plugin '{plugin_name}' is disabled"} params = request or {} result = plugin.execute(params) return {"success": True, "result": result} @router.get("/{plugin_name}/logs", summary="获取插件日志") async def get_plugin_logs( plugin_name: str, lines: int = 100, level: Optional[str] = None, _=Depends(require_api_key) ) -> Dict[str, Any]: try: import logging import os plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") log_dir = "/app/data/logs" if not os.path.exists(log_dir): os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, f"{plugin_name}.log") app_log = os.path.join(log_dir, "app.log") all_logs = [] if os.path.exists(log_file): with open(log_file, 'r', encoding='utf-8') as f: all_logs.extend(f.readlines()) if os.path.exists(app_log): with open(app_log, 'r', encoding='utf-8') as f: for line in f: if plugin_name in line or f"[{plugin_name}]" in line: all_logs.append(line) filtered_logs = [] for line in all_logs: if level: if level.upper() in line.upper(): filtered_logs.append(line.strip()) else: filtered_logs.append(line.strip()) filtered_logs = filtered_logs[-lines:] if len(filtered_logs) > lines else filtered_logs return { "success": True, "plugin_name": plugin_name, "log_file": log_file, "count": len(filtered_logs), "logs": filtered_logs } except Exception as e: return {"success": False, "error": str(e)} @router.get("/{plugin_name}/schema", summary="获取插件配置表单Schema") async def get_plugin_schema(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") schema = None if hasattr(plugin, 'get_config_schema'): schema = plugin.get_config_schema() elif hasattr(plugin, 'config_schema'): schema = plugin.config_schema if schema: if callable(schema): schema = schema() if hasattr(schema, 'to_form_schema'): return { "success": True, "schema": schema.to_form_schema(), "has_form": True } return { "success": True, "schema": schema, "has_form": True } return { "success": True, "schema": None, "has_form": False, "message": "此插件暂无表单配置" } except Exception as e: return {"success": False, "error": str(e)} @router.post("/{plugin_name}/logs/clear", summary="清空插件日志") async def clear_plugin_logs(plugin_name: str, _=Depends(require_api_key)) -> Dict[str, Any]: try: import os plugin = PluginRegistry.get(plugin_name) if not plugin: raise HTTPException(status_code=404, detail=f"Plugin '{plugin_name}' not found") log_file = f"/app/data/logs/{plugin_name}.log" if os.path.exists(log_file): with open(log_file, 'w') as f: f.write("") return { "success": True, "message": f"Plugin '{plugin_name}' logs cleared" } except Exception as e: return {"success": False, "error": str(e)}