#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 统一异常处理模块 定义项目专用的异常类型和标准化的重试机制 """ import time import logging from typing import Optional, Callable, Any, Union from functools import wraps import os logger = logging.getLogger(__name__) class TravelContentCreatorError(Exception): """项目基础异常类""" def __init__(self, message: str, error_code: Optional[str] = None, details: Optional[dict] = None): super().__init__(message) self.message = message self.error_code = error_code self.details = details or {} def __str__(self): if self.error_code: return f"[{self.error_code}] {self.message}" return self.message class ConfigurationError(TravelContentCreatorError): """配置相关异常""" pass class AIModelError(TravelContentCreatorError): """AI模型调用异常""" pass class ContentGenerationError(TravelContentCreatorError): """内容生成异常""" pass class PosterGenerationError(TravelContentCreatorError): """海报生成异常""" pass class ResourceError(TravelContentCreatorError): """资源文件异常""" pass class ValidationError(TravelContentCreatorError): """数据验证异常""" pass class TimeoutError(TravelContentCreatorError): """超时异常""" pass class RetryableError(TravelContentCreatorError): """可重试异常""" pass class NonRetryableError(TravelContentCreatorError): """不可重试异常""" pass def retry_on_failure( max_retries: int = 3, delay: float = 1.0, backoff: float = 2.0, exceptions: tuple = (Exception,), non_retryable_exceptions: tuple = (NonRetryableError,) ): """ 重试装饰器 Args: max_retries: 最大重试次数 delay: 初始延迟时间(秒) backoff: 退避系数 exceptions: 需要重试的异常类型 non_retryable_exceptions: 不可重试的异常类型 """ def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs) -> Any: last_exception = None current_delay = delay for attempt in range(max_retries + 1): try: return func(*args, **kwargs) except non_retryable_exceptions as e: logger.error(f"不可重试异常: {e}") raise except exceptions as e: last_exception = e if attempt < max_retries: logger.warning(f"函数 {func.__name__} 第 {attempt + 1} 次调用失败: {e}") logger.info(f"将在 {current_delay:.1f} 秒后重试...") time.sleep(current_delay) current_delay *= backoff else: logger.error(f"函数 {func.__name__} 重试 {max_retries} 次后仍然失败") break # 如果所有重试都失败了,抛出最后一个异常 if last_exception: raise last_exception return wrapper return decorator def timeout_handler(timeout_seconds: int): """ 超时处理装饰器 注意: 这个装饰器使用 signal 模块,因此在非Unix-like系统(如Windows)上可能无法正常工作。 Args: timeout_seconds: 超时时间(秒) """ def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs) -> Any: # 在Windows上,signal模块功能有限,以下代码可能无法按预期工作 if os.name == 'nt': logger.warning("timeout_handler在Windows上功能受限,可能无法生效") return func(*args, **kwargs) import signal def timeout_signal_handler(signum, frame): raise TimeoutError(f"函数 {func.__name__} 执行超时 ({timeout_seconds}秒)") # 设置超时信号 old_handler = signal.signal(signal.SIGALRM, timeout_signal_handler) signal.alarm(timeout_seconds) try: result = func(*args, **kwargs) return result finally: # 取消超时信号 signal.alarm(0) signal.signal(signal.SIGALRM, old_handler) return wrapper return decorator def handle_exceptions( default_return: Any = None, log_level: str = "ERROR", reraise: bool = True ): """ 通用异常处理装饰器 Args: default_return: 异常时的默认返回值 log_level: 日志级别 reraise: 是否重新抛出异常 """ def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs) -> Any: try: return func(*args, **kwargs) except Exception as e: # 记录异常 log_func = getattr(logger, log_level.lower()) log_func(f"函数 {func.__name__} 发生异常: {e}", exc_info=True) if reraise: raise else: return default_return return wrapper return decorator class ErrorContext: """错误上下文管理器""" def __init__(self, operation: str, error_type: type = TravelContentCreatorError): self.operation = operation self.error_type = error_type self.start_time = None def __enter__(self): self.start_time = time.time() logger.info(f"开始执行操作: {self.operation}") return self def __exit__(self, exc_type, exc_val, exc_tb): duration = time.time() - self.start_time if exc_type is None: logger.info(f"操作完成: {self.operation} (耗时: {duration:.2f}秒)") else: logger.error(f"操作失败: {self.operation} (耗时: {duration:.2f}秒)") # 如果是预期的异常类型,包装后重新抛出 if not isinstance(exc_val, TravelContentCreatorError): wrapped_error = self.error_type( f"操作 '{self.operation}' 失败: {str(exc_val)}", details={'original_error': str(exc_val), 'duration': duration} ) raise wrapped_error from exc_val return False def validate_required_fields(data: dict, required_fields: list, error_prefix: str = ""): """ 验证必需字段 Args: data: 要验证的数据字典 required_fields: 必需字段列表 error_prefix: 错误消息前缀 Raises: ValidationError: 当必需字段缺失时 """ missing_fields = [] for field in required_fields: if field not in data or data[field] is None: missing_fields.append(field) if missing_fields: error_msg = f"{error_prefix}缺少必需字段: {', '.join(missing_fields)}" raise ValidationError(error_msg, error_code="MISSING_REQUIRED_FIELDS") def safe_execute(func: Callable, *args, **kwargs) -> tuple: """ 安全执行函数,返回 (success, result_or_error) Args: func: 要执行的函数 *args: 函数参数 **kwargs: 函数关键字参数 Returns: tuple: (是否成功, 结果或异常对象) """ try: result = func(*args, **kwargs) return True, result except Exception as e: logger.exception(f"函数 {func.__name__} 执行失败") return False, e # 常用的异常实例 COMMON_ERRORS = { 'config_missing': ConfigurationError("配置文件缺失或格式错误"), 'api_key_missing': ConfigurationError("API密钥未配置"), 'model_unavailable': AIModelError("AI模型不可用"), 'network_error': RetryableError("网络连接错误"), 'resource_not_found': ResourceError("资源文件未找到"), 'invalid_format': ValidationError("数据格式无效"), } def get_error_by_code(error_code: str) -> TravelContentCreatorError: """根据错误代码获取预定义的异常对象""" return COMMON_ERRORS.get(error_code, TravelContentCreatorError(f"未知错误: {error_code}"))