【剪映小助手源码精讲】36_错误处理机制

29 阅读17分钟

第36章:错误处理机制

36.1 概述

错误处理机制是构建健壮API服务的核心组成部分。剪映小助手采用了一套完整的错误处理体系,涵盖统一错误响应格式、HTTP状态码映射、异常处理中间件等多个层面,确保系统在面对各种异常情况时能够提供一致、清晰的错误信息。

本章将深入剖析错误处理机制的实现细节,包括如何设计统一的错误响应格式、如何建立错误码与HTTP状态码的映射关系、如何通过中间件实现全局异常捕获,以及如何提供友好的错误信息展示。

36.2 统一错误响应格式设计

36.2.1 错误响应结构定义

剪映小助手定义了标准化的错误响应格式,确保所有API接口在发生错误时返回一致的数据结构:

# src/schemas/base.py
from pydantic import BaseModel
from typing import Optional, Any, Dict

class ErrorResponse(BaseModel):
    """统一错误响应格式"""
    code: int
    message: str
    details: Optional[Any] = None
    timestamp: Optional[str] = None
    path: Optional[str] = None
    method: Optional[str] = None

class StandardResponse(BaseModel):
    """标准响应格式"""
    success: bool
    data: Optional[Any] = None
    error: Optional[ErrorResponse] = None
    timestamp: str
    request_id: Optional[str] = None

36.2.2 错误响应构建器

系统提供了错误响应构建器,简化错误响应的创建过程:

# src/utils/response_builder.py
from datetime import datetime
import uuid
from src.schemas.base import ErrorResponse, StandardResponse

class ResponseBuilder:
    """响应构建器"""
    
    @staticmethod
    def build_error_response(
        code: int,
        message: str,
        details: Any = None,
        path: str = None,
        method: str = None
    ) -> StandardResponse:
        """构建错误响应"""
        error = ErrorResponse(
            code=code,
            message=message,
            details=details,
            timestamp=datetime.now().isoformat(),
            path=path,
            method=method
        )
        
        return StandardResponse(
            success=False,
            error=error,
            timestamp=datetime.now().isoformat(),
            request_id=str(uuid.uuid4())
        )
    
    @staticmethod
    def build_success_response(data: Any = None) -> StandardResponse:
        """构建成功响应"""
        return StandardResponse(
            success=True,
            data=data,
            timestamp=datetime.now().isoformat(),
            request_id=str(uuid.uuid4())
        )

36.3 HTTP状态码映射机制

36.3.1 错误码与HTTP状态码对应关系

系统建立了完整的错误码与HTTP状态码映射关系,确保错误响应符合RESTful设计原则:

# src/utils/http_status_mapper.py
from enum import IntEnum

class HTTPStatus(IntEnum):
    """HTTP状态码定义"""
    OK = 200
    CREATED = 201
    ACCEPTED = 202
    BAD_REQUEST = 400
    UNAUTHORIZED = 401
    FORBIDDEN = 403
    NOT_FOUND = 404
    METHOD_NOT_ALLOWED = 405
    CONFLICT = 409
    UNPROCESSABLE_ENTITY = 422
    TOO_MANY_REQUESTS = 429
    INTERNAL_SERVER_ERROR = 500
    BAD_GATEWAY = 502
    SERVICE_UNAVAILABLE = 503

# 错误码到HTTP状态码的映射
ERROR_CODE_TO_HTTP_STATUS = {
    # 基础错误码 (1000-1999)
    1000: HTTPStatus.BAD_REQUEST,           # 参数错误
    1001: HTTPStatus.BAD_REQUEST,           # 缺少必填参数
    1002: HTTPStatus.UNPROCESSABLE_ENTITY,   # 参数格式错误
    1003: HTTPStatus.BAD_REQUEST,           # 参数范围错误
    
    # 业务错误码 (2000-2999)
    2000: HTTPStatus.NOT_FOUND,            # 草稿不存在
    2001: HTTPStatus.CONFLICT,             # 草稿已存在
    2002: HTTPStatus.NOT_FOUND,            # 素材不存在
    2003: HTTPStatus.BAD_REQUEST,         # 素材格式不支持
    2004: HTTPStatus.UNPROCESSABLE_ENTITY, # 素材处理失败
    
    # 系统错误码 (9000-9999)
    9000: HTTPStatus.INTERNAL_SERVER_ERROR, # 服务器内部错误
    9001: HTTPStatus.BAD_GATEWAY,          # 外部服务调用失败
    9002: HTTPStatus.SERVICE_UNAVAILABLE,   # 服务暂不可用
    9003: HTTPStatus.TOO_MANY_REQUESTS,     # 请求过于频繁
}

def get_http_status_by_error_code(error_code: int) -> int:
    """根据错误码获取对应的HTTP状态码"""
    return ERROR_CODE_TO_HTTP_STATUS.get(error_code, HTTPStatus.INTERNAL_SERVER_ERROR)

36.3.2 状态码映射服务

系统提供了状态码映射服务,自动处理错误码到HTTP状态码的转换:

# src/service/status_code_service.py
from src.utils.http_status_mapper import get_http_status_by_error_code
from src.utils.logger import logger

class StatusCodeService:
    """状态码映射服务"""
    
    @staticmethod
    def map_error_to_http_status(error_code: int, error_message: str = None) -> int:
        """将错误码映射为HTTP状态码"""
        http_status = get_http_status_by_error_code(error_code)
        
        logger.info(
            f"错误码映射: error_code={error_code}, "
            f"http_status={http_status}, message={error_message}"
        )
        
        return http_status
    
    @staticmethod
    def get_status_description(status_code: int) -> str:
        """获取状态码描述"""
        descriptions = {
            200: "成功",
            201: "已创建",
            400: "请求错误",
            401: "未授权",
            403: "禁止访问",
            404: "未找到",
            422: "无法处理的实体",
            500: "服务器内部错误",
            502: "网关错误",
            503: "服务不可用"
        }
        return descriptions.get(status_code, "未知状态")

36.4 异常处理中间件实现

36.4.1 全局异常处理中间件

系统通过中间件实现全局异常捕获和处理,确保所有未处理的异常都能被妥善处置:

# src/middlewares/error_handler.py
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from src.utils.response_builder import ResponseBuilder
from src.service.status_code_service import StatusCodeService
from src.utils.logger import logger
import traceback

class ErrorHandlerMiddleware(BaseHTTPMiddleware):
    """全局错误处理中间件"""
    
    async def dispatch(self, request: Request, call_next):
        """处理请求并捕获异常"""
        try:
            # 继续处理请求
            response = await call_next(request)
            return response
            
        except HTTPException as http_exc:
            # 处理HTTP异常
            logger.warning(
                f"HTTP异常: status={http_exc.status_code}, "
                f"detail={http_exc.detail}, path={request.url.path}"
            )
            
            error_response = ResponseBuilder.build_error_response(
                code=http_exc.status_code * 10,  # 转换为自定义错误码
                message=str(http_exc.detail),
                path=str(request.url.path),
                method=request.method
            )
            
            return JSONResponse(
                status_code=http_exc.status_code,
                content=error_response.dict()
            )
            
        except Exception as exc:
            # 处理未预期的异常
            logger.error(
                f"未预期的异常: {str(exc)}, path={request.url.path}, "
                f"method={request.method}", exc_info=True
            )
            
            # 记录详细的错误信息
            error_details = {
                "error_type": type(exc).__name__,
                "error_message": str(exc),
                "traceback": traceback.format_exc()
            }
            
            error_response = ResponseBuilder.build_error_response(
                code=9000,  # 服务器内部错误
                message="服务器内部错误",
                details=error_details,
                path=str(request.url.path),
                method=request.method
            )
            
            return JSONResponse(
                status_code=500,
                content=error_response.dict()
            )

36.4.2 自定义异常处理器

系统为不同类型的自定义异常提供专门的处理器:

# src/middlewares/custom_exception_handler.py
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from src.exceptions import CustomException, CustomError
from src.utils.response_builder import ResponseBuilder
from src.service.status_code_service import StatusCodeService
from src.utils.logger import logger

async def custom_exception_handler(request: Request, exc: CustomException) -> JSONResponse:
    """自定义异常处理器"""
    
    # 获取HTTP状态码
    http_status = StatusCodeService.map_error_to_http_status(
        exc.error_code.value, exc.message
    )
    
    logger.warning(
        f"自定义异常处理: error_code={exc.error_code.value}, "
        f"message={exc.message}, path={request.url.path}"
    )
    
    # 构建错误响应
    error_response = ResponseBuilder.build_error_response(
        code=exc.error_code.value,
        message=exc.message,
        details=exc.details,
        path=str(request.url.path),
        method=request.method
    )
    
    return JSONResponse(
        status_code=http_status,
        content=error_response.dict()
    )

async def validation_exception_handler(request: Request, exc: Exception) -> JSONResponse:
    """参数验证异常处理器"""
    
    logger.warning(
        f"参数验证异常: {str(exc)}, path={request.url.path}, "
        f"method={request.method}"
    )
    
    # 构建错误响应
    error_response = ResponseBuilder.build_error_response(
        code=1000,  # 参数错误
        message="参数验证失败",
        details={"validation_errors": str(exc)},
        path=str(request.url.path),
        method=request.method
    )
    
    return JSONResponse(
        status_code=400,
        content=error_response.dict()
    )

36.4.3 异常处理器注册

在FastAPI应用中注册异常处理器:

# src/main.py
from fastapi import FastAPI
from src.middlewares.error_handler import ErrorHandlerMiddleware
from src.middlewares.custom_exception_handler import (
    custom_exception_handler,
    validation_exception_handler
)
from src.exceptions import CustomException
from pydantic import ValidationError

app = FastAPI(
    title="剪映小助手API",
    description="视频编辑自动化API服务"
)

# 添加全局错误处理中间件
app.add_middleware(ErrorHandlerMiddleware)

# 注册自定义异常处理器
app.add_exception_handler(CustomException, custom_exception_handler)
app.add_exception_handler(ValidationError, validation_exception_handler)

36.5 错误处理策略

36.5.1 参数验证错误处理

对于参数验证错误,系统提供了详细的错误信息:

# src/validators/param_validator.py
from typing import Any, Dict, List
from src.utils.logger import logger
from src.exceptions import CustomException, CustomError

class ParameterValidator:
    """参数验证器"""
    
    @staticmethod
    def validate_required_params(params: Dict[str, Any], required_fields: List[str]) -> None:
        """验证必填参数"""
        missing_fields = []
        
        for field in required_fields:
            if field not in params or params[field] is None:
                missing_fields.append(field)
        
        if missing_fields:
            error_message = f"缺少必填参数: {', '.join(missing_fields)}"
            logger.warning(f"参数验证失败: {error_message}")
            
            raise CustomException(
                error_code=CustomError.MISSING_REQUIRED_PARAMETER,
                message=error_message,
                details={"missing_fields": missing_fields}
            )
    
    @staticmethod
    def validate_param_format(param_name: str, param_value: Any, expected_type: type) -> None:
        """验证参数格式"""
        if not isinstance(param_value, expected_type):
            error_message = f"参数 '{param_name}' 格式错误,期望类型: {expected_type.__name__}"
            logger.warning(f"参数格式验证失败: {error_message}")
            
            raise CustomException(
                error_code=CustomError.PARAMETER_FORMAT_ERROR,
                message=error_message,
                details={
                    "param_name": param_name,
                    "param_value": param_value,
                    "expected_type": expected_type.__name__
                }
            )

36.5.2 资源不存在错误处理

对于资源不存在的场景,系统提供了友好的错误处理:

# src/service/resource_service.py
from src.utils.logger import logger
from src.exceptions import CustomException, CustomError
import os

class ResourceService:
    """资源服务"""
    
    @staticmethod
    def validate_draft_exists(draft_path: str) -> None:
        """验证草稿文件是否存在"""
        if not os.path.exists(draft_path):
            error_message = f"草稿文件不存在: {draft_path}"
            logger.warning(f"资源验证失败: {error_message}")
            
            raise CustomException(
                error_code=CustomError.DRAFT_NOT_FOUND,
                message=error_message,
                details={"draft_path": draft_path}
            )
    
    @staticmethod
    def validate_material_exists(material_url: str) -> None:
        """验证素材是否存在"""
        # 这里应该实现素材存在性检查逻辑
        # 简化示例
        if not material_url or not material_url.startswith("http"):
            error_message = f"素材URL无效: {material_url}"
            logger.warning(f"素材验证失败: {error_message}")
            
            raise CustomException(
                error_code=CustomError.MATERIAL_NOT_FOUND,
                message=error_message,
                details={"material_url": material_url}
            )

36.5.3 系统错误处理

对于系统级错误,系统提供了详细的错误信息记录:

# src/service/system_service.py
import traceback
from src.utils.logger import logger
from src.exceptions import CustomException, CustomError

class SystemService:
    """系统服务"""
    
    @staticmethod
    def handle_system_error(error: Exception, context: dict = None) -> None:
        """处理系统错误"""
        error_details = {
            "error_type": type(error).__name__,
            "error_message": str(error),
            "traceback": traceback.format_exc(),
            "context": context or {}
        }
        
        logger.error(
            f"系统错误处理: {error_details}",
            exc_info=True
        )
        
        raise CustomException(
            error_code=CustomError.INTERNAL_SERVER_ERROR,
            message="服务器内部错误,请稍后重试",
            details=error_details
        )

36.6 错误信息国际化支持

36.6.1 多语言错误消息管理

系统支持错误消息的国际化,根据用户的语言偏好返回相应的错误信息:

# src/utils/i18n_error_manager.py
from typing import Dict

class I18nErrorManager:
    """国际化错误管理器"""
    
    def __init__(self):
        self._messages = {
            "zh": {
                1000: "参数错误",
                1001: "缺少必填参数",
                1002: "参数格式错误",
                1003: "参数范围错误",
                2000: "草稿不存在",
                2001: "草稿已存在",
                2002: "素材不存在",
                2003: "素材格式不支持",
                2004: "素材处理失败",
                9000: "服务器内部错误",
                9001: "外部服务调用失败",
                9002: "服务暂不可用",
                9003: "请求过于频繁"
            },
            "en": {
                1000: "Parameter error",
                1001: "Missing required parameter",
                1002: "Parameter format error",
                1003: "Parameter range error",
                2000: "Draft not found",
                2001: "Draft already exists",
                2002: "Material not found",
                2003: "Material format not supported",
                2004: "Material processing failed",
                9000: "Internal server error",
                9001: "External service call failed",
                9002: "Service temporarily unavailable",
                9003: "Too many requests"
            },
            "ja": {
                1000: "パラメータエラー",
                1001: "必須パラメータが不足しています",
                1002: "パラメータフォーマットエラー",
                1003: "パラメータ範囲エラー",
                2000: "下書きが見つかりません",
                2001: "下書きは既に存在します",
                2002: "素材が見つかりません",
                2003: "素材フォーマットがサポートされていません",
                2004: "素材処理に失敗しました",
                9000: "サーバー内部エラー",
                9001: "外部サービス呼び出しに失敗しました",
                9002: "サービスは一時的に利用できません",
                9003: "リクエストが多すぎます"
            }
        }
    
    def get_error_message(self, error_code: int, lang: str = "zh") -> str:
        """获取指定语言的错误消息"""
        return self._messages.get(lang, {}).get(error_code, "Unknown error")
    
    def get_localized_error_response(self, error_code: int, lang: str = "zh", details: dict = None) -> dict:
        """获取本地化的错误响应"""
        message = self.get_error_message(error_code, lang)
        
        return {
            "code": error_code,
            "message": message,
            "details": details or {},
            "language": lang
        }

36.6.2 语言检测与选择

系统自动检测用户的语言偏好,选择合适的错误消息语言:

# src/utils/language_detector.py
from typing import Optional
from src.utils.i18n_error_manager import I18nErrorManager

def detect_user_language(request_headers: dict) -> str:
    """从请求头检测用户语言"""
    accept_language = request_headers.get("Accept-Language", "zh-CN")
    
    # 简单的语言检测逻辑
    if "zh" in accept_language:
        return "zh"
    elif "en" in accept_language:
        return "en"
    elif "ja" in accept_language:
        return "ja"
    else:
        return "zh"  # 默认中文

def get_localized_error_message(error_code: int, request_headers: dict) -> str:
    """获取本地化的错误消息"""
    lang = detect_user_language(request_headers)
    i18n_manager = I18nErrorManager()
    return i18n_manager.get_error_message(error_code, lang)

36.7 错误日志与监控

36.7.1 结构化错误日志

系统记录结构化的错误日志,便于后续分析和监控:

# src/utils/error_logger.py
from datetime import datetime
from typing import Dict, Any
from src.utils.logger import logger

class ErrorLogger:
    """错误日志记录器"""
    
    @staticmethod
    def log_api_error(
        error_code: int,
        error_message: str,
        request_info: Dict[str, Any],
        user_info: Dict[str, Any] = None
    ) -> None:
        """记录API错误日志"""
        error_log = {
            "timestamp": datetime.now().isoformat(),
            "level": "ERROR",
            "type": "api_error",
            "error_code": error_code,
            "error_message": error_message,
            "request": {
                "method": request_info.get("method"),
                "path": request_info.get("path"),
                "query_params": request_info.get("query_params"),
                "headers": request_info.get("headers"),
                "body": request_info.get("body")
            },
            "user": user_info or {},
            "environment": "production"
        }
        
        logger.error(f"API错误: {error_log}")
    
    @staticmethod
    def log_system_error(
        error_type: str,
        error_message: str,
        stack_trace: str,
        context: Dict[str, Any] = None
    ) -> None:
        """记录系统错误日志"""
        error_log = {
            "timestamp": datetime.now().isoformat(),
            "level": "ERROR",
            "type": "system_error",
            "error_type": error_type,
            "error_message": error_message,
            "stack_trace": stack_trace,
            "context": context or {},
            "environment": "production"
        }
        
        logger.error(f"系统错误: {error_log}")

36.7.2 错误统计与告警

系统提供错误统计和告警功能,及时发现和处理问题:

# src/service/error_monitor_service.py
from datetime import datetime, timedelta
from typing import Dict, List
from collections import defaultdict
from src.utils.error_logger import ErrorLogger

class ErrorMonitorService:
    """错误监控服务"""
    
    def __init__(self):
        self.error_stats = defaultdict(int)
        self.error_timeline = []
        self.alert_thresholds = {
            "error_rate": 0.1,      # 错误率阈值
            "error_count": 100,     # 错误数量阈值
            "time_window": 300     # 时间窗口(秒)
        }
    
    def record_error(self, error_code: int, error_message: str, severity: str = "normal") -> None:
        """记录错误信息"""
        current_time = datetime.now()
        
        # 更新错误统计
        self.error_stats[error_code] += 1
        
        # 记录错误时间线
        self.error_timeline.append({
            "timestamp": current_time,
            "error_code": error_code,
            "error_message": error_message,
            "severity": severity
        })
        
        # 清理过期的错误记录
        self._cleanup_old_errors()
        
        # 检查是否需要告警
        self._check_alert_conditions()
    
    def _cleanup_old_errors(self) -> None:
        """清理过期的错误记录"""
        cutoff_time = datetime.now() - timedelta(seconds=self.alert_thresholds["time_window"])
        self.error_timeline = [
            error for error in self.error_timeline
            if error["timestamp"] > cutoff_time
        ]
    
    def _check_alert_conditions(self) -> None:
        """检查告警条件"""
        if len(self.error_timeline) > self.alert_thresholds["error_count"]:
            self._send_alert("错误数量超过阈值")
        
        error_rate = len(self.error_timeline) / max(self.alert_thresholds["time_window"], 1)
        if error_rate > self.alert_thresholds["error_rate"]:
            self._send_alert("错误率超过阈值")
    
    def _send_alert(self, alert_message: str) -> None:
        """发送告警"""
        # 这里应该实现实际的告警发送逻辑
        # 可以集成邮件、短信、钉钉等告警方式
        print(f"告警: {alert_message}")
        print(f"当前错误统计: {dict(self.error_stats)}")
    
    def get_error_statistics(self) -> Dict:
        """获取错误统计信息"""
        return {
            "error_stats": dict(self.error_stats),
            "recent_errors": len(self.error_timeline),
            "most_common_errors": self._get_most_common_errors(),
            "error_trend": self._calculate_error_trend()
        }
    
    def _get_most_common_errors(self) -> List[Dict]:
        """获取最常见的错误"""
        sorted_errors = sorted(
            self.error_stats.items(),
            key=lambda x: x[1],
            reverse=True
        )
        return [{"error_code": code, "count": count} for code, count in sorted_errors[:5]]
    
    def _calculate_error_trend(self) -> str:
        """计算错误趋势"""
        if len(self.error_timeline) > 50:
            return "high"
        elif len(self.error_timeline) > 20:
            return "medium"
        else:
            return "low"

36.8 错误处理最佳实践

36.8.1 异常链传递

在处理复杂业务逻辑时,系统采用异常链传递机制,保留完整的错误上下文:

# src/service/draft_service.py
from src.utils.logger import logger
from src.exceptions import CustomException, CustomError

class DraftService:
    """草稿服务"""
    
    def process_draft(self, draft_url: str) -> dict:
        """处理草稿"""
        try:
            # 步骤1:验证草稿URL
            self._validate_draft_url(draft_url)
            
            # 步骤2:下载草稿文件
            draft_content = self._download_draft(draft_url)
            
            # 步骤3:解析草稿内容
            parsed_draft = self._parse_draft_content(draft_content)
            
            return parsed_draft
            
        except CustomException as ce:
            # 保持原始异常信息,添加上下文
            logger.error(f"草稿处理失败: {ce.message}, draft_url={draft_url}")
            raise CustomException(
                error_code=ce.error_code,
                message=f"草稿处理失败: {ce.message}",
                details={**ce.details, "draft_url": draft_url}
            ) from ce
            
        except Exception as e:
            # 包装未知异常
            logger.error(f"草稿处理时发生未预期错误: {str(e)}, draft_url={draft_url}")
            raise CustomException(
                error_code=CustomError.INTERNAL_SERVER_ERROR,
                message="草稿处理失败",
                details={"original_error": str(e), "draft_url": draft_url}
            ) from e

36.8.2 批量操作错误处理

在处理批量操作时,系统采用部分成功/失败的错误处理策略:

# src/service/batch_service.py
from typing import List, Dict
from src.utils.logger import logger
from src.exceptions import CustomException

class BatchService:
    """批量操作服务"""
    
    def batch_process_drafts(self, draft_urls: List[str]) -> Dict:
        """批量处理草稿"""
        results = {
            "total": len(draft_urls),
            "successful": 0,
            "failed": 0,
            "success_results": [],
            "error_results": []
        }
        
        for draft_url in draft_urls:
            try:
                result = self.process_single_draft(draft_url)
                results["success_results"].append({
                    "draft_url": draft_url,
                    "result": result
                })
                results["successful"] += 1
                
            except CustomException as ce:
                results["error_results"].append({
                    "draft_url": draft_url,
                    "error_code": ce.error_code.value,
                    "error_message": ce.message,
                    "details": ce.details
                })
                results["failed"] += 1
                logger.warning(f"批量处理中单个草稿失败: {ce.message}")
                
            except Exception as e:
                results["error_results"].append({
                    "draft_url": draft_url,
                    "error_code": 9000,
                    "error_message": str(e),
                    "details": {}
                })
                results["failed"] += 1
                logger.error(f"批量处理中发生未预期错误: {str(e)}")
        
        return results

36.8.3 错误恢复机制

系统实现了错误恢复机制,在某些情况下可以自动恢复:

# src/utils/error_recovery.py
import time
from typing import Callable, Any
from src.utils.logger import logger

class ErrorRecovery:
    """错误恢复机制"""
    
    @staticmethod
    def with_retry(
        func: Callable,
        max_retries: int = 3,
        retry_delay: float = 1.0,
        backoff_factor: float = 2.0,
        exceptions: tuple = (Exception,)
    ) -> Any:
        """带重试的错误恢复机制"""
        
        for attempt in range(max_retries + 1):
            try:
                return func()
                
            except exceptions as e:
                if attempt == max_retries:
                    logger.error(f"重试失败,已达到最大重试次数: {max_retries}")
                    raise
                
                # 计算延迟时间
                delay = retry_delay * (backoff_factor ** attempt)
                
                logger.warning(
                    f"操作失败,准备重试: attempt={attempt + 1}, "
                    f"max_retries={max_retries}, delay={delay}s, error={str(e)}"
                )
                
                time.sleep(delay)
        
        return None
    
    @staticmethod
    def with_circuit_breaker(
        func: Callable,
        failure_threshold: int = 5,
        recovery_timeout: float = 60.0,
        expected_exception: type = Exception
    ) -> Any:
        """熔断器模式错误恢复"""
        # 这里应该实现熔断器逻辑
        # 简化示例
        try:
            return func()
        except expected_exception as e:
            logger.error(f"熔断器触发: {str(e)}")
            raise

36.9 错误码文档生成

36.9.1 自动化错误码文档

系统自动生成错误码文档,确保文档与代码保持一致:

# scripts/generate_error_docs.py
from src.exceptions import CustomError
from src.utils.http_status_mapper import ERROR_CODE_TO_HTTP_STATUS
from src.utils.i18n_error_manager import I18nErrorManager

def generate_error_code_documentation():
    """生成错误码文档"""
    i18n_manager = I18nErrorManager()
    
    documentation = "# 错误码说明文档\n\n"
    documentation += "本文档自动生成,描述了系统中定义的所有错误码及其含义。\n\n"
    
    # 按错误码分类生成文档
    error_categories = {
        "基础错误 (1000-1999)": [],
        "业务错误 (2000-2999)": [],
        "系统错误 (9000-9999)": []
    }
    
    for error_item in CustomError:
        error_code = error_item.value
        
        # 确定错误码分类
        if 1000 <= error_code < 2000:
            category = "基础错误 (1000-1999)"
        elif 2000 <= error_code < 3000:
            category = "业务错误 (2000-2999)"
        else:
            category = "系统错误 (9000-9999)"
        
        # 获取HTTP状态码
        http_status = ERROR_CODE_TO_HTTP_STATUS.get(error_code, 500)
        
        # 获取多语言错误消息
        zh_message = i18n_manager.get_error_message(error_code, "zh")
        en_message = i18n_manager.get_error_message(error_code, "en")
        
        error_info = {
            "code": error_code,
            "name": error_item.name,
            "http_status": http_status,
            "zh_message": zh_message,
            "en_message": en_message,
            "description": f"错误码 {error_code} 的描述信息"
        }
        
        error_categories[category].append(error_info)
    
    # 生成文档内容
    for category, errors in error_categories.items():
        if errors:
            documentation += f"## {category}\n\n"
            documentation += "| 错误码 | 名称 | HTTP状态码 | 中文消息 | 英文消息 |\n"
            documentation += "|--------|------|------------|----------|----------|\n"
            
            for error in errors:
                documentation += f"| {error['code']} | {error['name']} | {error['http_status']} | {error['zh_message']} | {error['en_message']} |\n"
            
            documentation += "\n"
    
    return documentation

if __name__ == "__main__":
    docs = generate_error_code_documentation()
    with open("error_codes.md", "w", encoding="utf-8") as f:
        f.write(docs)
    print("错误码文档已生成: error_codes.md")

36.10 错误处理测试

36.10.1 错误场景测试用例

系统包含完整的错误场景测试用例,确保错误处理逻辑的正确性:

# tests/test_error_handling.py
import pytest
from fastapi.testclient import TestClient
from src.main import app
from src.exceptions import CustomException, CustomError

client = TestClient(app)

class TestErrorHandling:
    """错误处理测试类"""
    
    def test_parameter_validation_error(self):
        """测试参数验证错误处理"""
        response = client.post("/v1/create_draft", json={})
        
        assert response.status_code == 400
        response_data = response.json()
        assert response_data["success"] is False
        assert response_data["error"]["code"] == 1001  # 缺少必填参数
        assert "message" in response_data["error"]
    
    def test_resource_not_found_error(self):
        """测试资源不存在错误处理"""
        invalid_draft_url = "https://example.com/nonexistent.draft"
        response = client.post("/v1/get_draft", json={"draft_url": invalid_draft_url})
        
        assert response.status_code == 404
        response_data = response.json()
        assert response_data["success"] is False
        assert response_data["error"]["code"] == 2000  # 草稿不存在
    
    def test_internal_server_error(self):
        """测试服务器内部错误处理"""
        # 这里应该触发一个内部错误
        # 简化示例
        response = client.post("/v1/gen_video", json={"draft_url": "error_trigger"})
        
        assert response.status_code == 500
        response_data = response.json()
        assert response_data["success"] is False
        assert response_data["error"]["code"] == 9000  # 服务器内部错误
    
    def test_custom_exception_handler(self):
        """测试自定义异常处理器"""
        # 测试自定义异常的抛出和处理
        with pytest.raises(CustomException) as exc_info:
            raise CustomException(
                error_code=CustomError.INVALID_PARAMETER,
                message="测试自定义异常"
            )
        
        assert exc_info.value.error_code == CustomError.INVALID_PARAMETER
        assert "测试自定义异常" in str(exc_info.value)
    
    def test_error_response_format(self):
        """测试错误响应格式"""
        response = client.post("/v1/create_draft", json={})
        response_data = response.json()
        
        # 验证响应格式
        assert "success" in response_data
        assert "timestamp" in response_data
        assert "request_id" in response_data
        assert "error" in response_data
        
        # 验证错误信息格式
        error_data = response_data["error"]
        assert "code" in error_data
        assert "message" in error_data
        assert "details" in error_data
        assert "timestamp" in error_data
        assert "path" in error_data
        assert "method" in error_data

36.11 本章小结

本章深入剖析了剪映小助手的错误处理机制,从统一错误响应格式设计到HTTP状态码映射,从异常处理中间件实现到具体的错误处理策略,构建了一套完整、健壮的错误处理体系。

通过标准化的错误响应格式,系统确保了所有API接口在发生错误时都能提供一致、清晰的错误信息。HTTP状态码映射机制使得错误响应符合RESTful设计原则,便于客户端理解和处理。全局异常处理中间件实现了统一的异常捕获和处理,避免了异常信息的泄露。

国际化支持使得错误信息能够根据用户的语言偏好进行本地化展示,提升了用户体验。完善的错误日志和监控机制帮助开发团队及时发现和解决问题,确保系统的稳定运行。

这套错误处理机制不仅提高了系统的健壮性和可维护性,也为开发者提供了友好的调试体验,是构建专业API服务的重要组成部分。


附录

代码仓库地址:

  • GitHub: https://github.com/Hommy-master/capcut-mate
  • Gitee: https://gitee.com/taohongmin-gitee/capcut-mate

接口文档地址:

  • API文档地址: https://docs.jcaigc.cn