283 lines
8.3 KiB
Python
283 lines
8.3 KiB
Python
|
|
#!/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}"))
|