"""脚本执行器 - 安全执行 Python 脚本""" import asyncio import io import logging import sys import traceback from contextlib import redirect_stdout, redirect_stderr from datetime import datetime from typing import Any, Dict from .script_sdk import ScriptSDK logger = logging.getLogger(__name__) # 执行超时时间(秒) SCRIPT_TIMEOUT = 300 # 5 分钟 # 禁止导入的模块 FORBIDDEN_MODULES = { 'os', 'subprocess', 'sys', 'builtins', '__builtins__', 'importlib', 'eval', 'exec', 'compile', 'open', 'file', 'input', 'socket', 'multiprocessing', 'threading', 'pickle', 'marshal', 'ctypes', 'code', 'codeop', 'pty', 'tty', } class ScriptExecutionResult: """脚本执行结果""" def __init__( self, success: bool, output: str = "", error: str = None, logs: list = None, execution_time_ms: int = 0 ): self.success = success self.output = output self.error = error self.logs = logs or [] self.execution_time_ms = execution_time_ms def to_dict(self) -> Dict[str, Any]: return { "success": self.success, "output": self.output, "error": self.error, "logs": self.logs, "execution_time_ms": self.execution_time_ms } def create_safe_builtins() -> Dict[str, Any]: """创建安全的内置函数集""" import builtins # 允许的内置函数 allowed = [ 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'complex', 'dict', 'divmod', 'enumerate', 'filter', 'float', 'format', 'frozenset', 'getattr', 'hasattr', 'hash', 'hex', 'id', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'map', 'max', 'min', 'next', 'object', 'oct', 'ord', 'pow', 'print', 'range', 'repr', 'reversed', 'round', 'set', 'slice', 'sorted', 'str', 'sum', 'tuple', 'type', 'zip', 'True', 'False', 'None', ] safe_builtins = {} for name in allowed: if hasattr(builtins, name): safe_builtins[name] = getattr(builtins, name) # 添加安全的 import 函数 def safe_import(name, *args, **kwargs): """安全的 import 函数,只允许特定模块""" allowed_modules = { 'json', 'datetime', 'time', 're', 'math', 'random', 'collections', 'itertools', 'functools', 'operator', 'string', 'textwrap', 'unicodedata', 'hashlib', 'base64', 'urllib.parse', } if name in FORBIDDEN_MODULES: raise ImportError(f"禁止导入模块: {name}") if name not in allowed_modules and not name.startswith('urllib.parse'): raise ImportError(f"不允许导入模块: {name},允许的模块: {', '.join(sorted(allowed_modules))}") return __builtins__['__import__'](name, *args, **kwargs) safe_builtins['__import__'] = safe_import return safe_builtins async def execute_script( task_id: int, tenant_id: str, script_content: str, trace_id: str = None ) -> ScriptExecutionResult: """ 执行 Python 脚本 Args: task_id: 任务 ID tenant_id: 租户 ID script_content: 脚本内容 trace_id: 追踪 ID Returns: ScriptExecutionResult: 执行结果 """ start_time = datetime.now() sdk = None try: # 创建 SDK 实例 sdk = ScriptSDK(tenant_id, task_id, trace_id) # 准备执行环境 script_globals = { '__builtins__': create_safe_builtins(), '__name__': '__script__', # SDK 实例 'sdk': sdk, # 快捷方法(同步包装) 'ai': lambda *args, **kwargs: asyncio.get_event_loop().run_until_complete(sdk.ai_chat(*args, **kwargs)), 'dingtalk': lambda *args, **kwargs: asyncio.get_event_loop().run_until_complete(sdk.send_dingtalk(*args, **kwargs)), 'wecom': lambda *args, **kwargs: asyncio.get_event_loop().run_until_complete(sdk.send_wecom(*args, **kwargs)), 'http_get': lambda *args, **kwargs: asyncio.get_event_loop().run_until_complete(sdk.http_get(*args, **kwargs)), 'http_post': lambda *args, **kwargs: asyncio.get_event_loop().run_until_complete(sdk.http_post(*args, **kwargs)), # 同步方法 'db': sdk.db_query, 'get_var': sdk.get_var, 'set_var': sdk.set_var, 'delete_var': sdk.delete_var, 'log': sdk.log, # 常用模块 'json': __import__('json'), 'datetime': __import__('datetime'), 're': __import__('re'), 'math': __import__('math'), 'random': __import__('random'), } # 捕获输出 stdout = io.StringIO() stderr = io.StringIO() sdk.log("脚本开始执行") # 编译并执行脚本 try: # 编译脚本 code = compile(script_content, '