【剪映小助手源码精讲】18_中间件系统实现

第18章 中间件系统实现

18.1 概述

中间件系统是剪映小助手的核心组件之一,负责统一处理HTTP请求和响应。通过中间件机制,我们可以在请求到达业务逻辑之前进行预处理,在响应返回客户端之前进行后处理,实现横切关注点的统一处理。

本章将详细介绍剪映小助手中的两个核心中间件:

  • PrepareMiddleware:请求预处理中间件
  • ResponseMiddleware:统一响应处理中间件

18.2 中间件架构设计

18.2.1 整体架构

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   HTTP请求      │───▶│  PrepareMiddleware │───▶│   业务逻辑层     │
│   (Request)     │    │  (环境初始化)     │    │   (Service)     │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                                               │
                                                               ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   HTTP响应      │◀───│ ResponseMiddleware│◀───│   处理结果       │
│  (Response)     │    │ (统一响应格式)    │    │   (Response)    │
└─────────────────┘    └──────────────────┘    └─────────────────┘

18.2.2 中间件注册

在FastAPI应用中注册中间件:

# main.py
from fastapi import FastAPI
from src.middlewares import PrepareMiddleware, ResponseMiddleware
from src.router import v1_router

# 创建FastAPI应用实例
app = FastAPI(
    title="剪映小助手API",
    description="基于剪映的自动化视频编辑API",
    version="1.0.0"
)

# 注册中间件
app.add_middleware(PrepareMiddleware)    # 请求预处理
app.add_middleware(ResponseMiddleware)   # 响应后处理

# 注册路由
app.include_router(v1_router, prefix="/openapi/capcut-mate")

18.3 PrepareMiddleware - 环境初始化中间件

18.3.1 功能概述

PrepareMiddleware负责在请求处理前进行环境准备工作,主要包括:

  1. 创建草稿目录
  2. 创建临时文件目录
  3. 确保必要的文件系统环境就绪

18.3.2 代码实现

# src/middlewares/prepare.py
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
import config
import os


class PrepareMiddleware(BaseHTTPMiddleware):
    """请求前的准备工作中间件
    功能:
    1. 创建临时目录
    2. 创建输出目录
    """

    async def dispatch(self, request: Request, call_next):
        # 递归创建目录,如果目录存在,就直接跳过创建
        os.makedirs(config.DRAFT_DIR, exist_ok=True)
        os.makedirs(config.TEMP_DIR, exist_ok=True)

        # 继续处理请求
        response = await call_next(request)
        return response

18.3.3 设计要点

  1. 幂等性:使用exist_ok=True参数确保目录已存在时不会抛出异常
  2. 异步处理:使用async/await模式,不阻塞事件循环
  3. 轻量级:只做必要的文件系统准备工作,不涉及复杂逻辑
  4. 错误透明:目录创建失败会直接抛出异常,由上层统一处理

18.4 ResponseMiddleware - 统一响应处理中间件

18.4.1 功能概述

ResponseMiddleware是系统中最复杂的中间件,负责统一处理所有HTTP响应:

  1. 统一响应格式:将业务响应包装成标准格式
  2. 异常统一处理:捕获并格式化各种异常情况
  3. 多语言支持:根据请求头返回中英文错误信息
  4. 参数验证错误特殊处理:对422错误进行特殊格式化处理

18.4.2 核心代码结构

# src/middlewares/response.py
from fastapi import Request
from fastapi.responses import JSONResponse
from exceptions import CustomError, CustomException
from starlette.middleware.base import BaseHTTPMiddleware
from src.utils.logger import logger
import json


class ResponseMiddleware(BaseHTTPMiddleware):
    """统一响应处理中间件"""

    async def dispatch(self, request: Request, call_next):
        # 初始化语言变量
        lang = 'zh'  # 默认语言
        
        try:
            lang = self._get_language_from_request(request)
            response = await call_next(request)

            # 处理非200状态码的响应
            if response.status_code != 200:
                return await self._handle_non_200_response(response, lang)
                
            # 处理JSON响应
            if self._is_json_response(response):
                return await self._process_json_response(response, lang)
                
            return response
            
        except CustomException as e:
            return self._handle_custom_exception(e, lang)
        except Exception as e:
            return self._handle_generic_exception(e, lang)

18.4.3 语言检测机制

def _get_language_from_request(self, request: Request) -> str:
    """从请求头获取语言偏好"""
    try:
        # 安全地解析 Accept-Language 头
        accept_lang = request.headers.get('Accept-Language', 'zh')
        if not accept_lang or not accept_lang.strip():
            return 'zh'
        
        # 先按逗号分割,取第一部分
        lang_parts = accept_lang.split(',')[0].strip()
        if not lang_parts:
            return 'zh'
        
        # 再按连字符分割,取语言代码部分
        lang_code_parts = lang_parts.split('-')
        if not lang_code_parts or not lang_code_parts[0]:
            return 'zh'
        
        lang = lang_code_parts[0].lower()
        return lang if lang in ['zh', 'en'] else 'zh'
        
    except Exception:
        # 如果解析过程中出现任何异常,返回默认语言
        return 'zh'

18.4.4 参数验证错误处理

对于422参数验证错误,进行特殊处理以提供更友好的错误信息:

async def _handle_422_error(self, body_str: str, lang: str) -> JSONResponse:
    """特殊处理422参数验证错误"""
    try:
        # 尝试解析422错误的响应体
        error_data = json.loads(body_str)
        
        # 提取验证错误的详细信息
        validation_messages = []
        if "detail" in error_data:
            for error in error_data["detail"]:
                if "loc" in error and "msg" in error:
                    # 格式化错误信息
                    field = ".".join(str(part) for part in error["loc"] if part != "body")
                    message = f"{field}: {error['msg']}"
                    validation_messages.append(message)

        # 构建统一的422错误响应(不包含data字段)
        error_message = "; ".join(validation_messages) if validation_messages else ""
        error_response = CustomError.PARAM_VALIDATION_FAILED.as_dict(detail=error_message, lang=lang)
        return JSONResponse(status_code=200, content=error_response)
        
    except json.JSONDecodeError:
        logger.warning(f"Failed to parse 422 response body: {body_str}")
        
        error_response = CustomError.PARAM_VALIDATION_FAILED.as_dict(detail=body_str, lang=lang)
        return JSONResponse(status_code=200, content=error_response)

18.4.5 JSON响应统一格式化

async def _process_json_response(self, response, lang: str):
    """处理JSON响应并统一格式"""
    body = [section async for section in response.body_iterator]
    if not body:
        return response
        
    body_str = b''.join(body).decode()
    
    try:
        data = json.loads(body_str)
        
        # 如果响应已经有统一格式,直接返回
        if 'code' in data and 'message' in data:
            return response
            
        # 创建统一格式的响应(成功响应保留data字段)
        unified_response = {
            'code': CustomError.SUCCESS.code,
            'message': CustomError.SUCCESS.as_dict(lang=lang)['message'],
            **data  # 保留原始数据
        }
        
        return JSONResponse(
            status_code=response.status_code,
            content=unified_response
        )
        
    except json.JSONDecodeError:
        logger.warning(f"JSON decode error: {body_str}")
        return response

18.4.6 异常处理机制

def _handle_custom_exception(self, e: CustomException, lang: str) -> JSONResponse:
    """处理自定义业务异常"""
    logger.warning(f"Custom exception: {e.err.code} - {e.err.cn_message}" + 
                  (f" ({e.detail})" if e.detail else ""))
    
    # 获取错误信息
    error_response = e.err.as_dict(detail=e.detail, lang=lang)
    return JSONResponse(status_code=200, content=error_response)

def _handle_generic_exception(self, e: Exception, lang: str) -> JSONResponse:
    """处理通用异常"""
    logger.warning(f"Internal server error: {str(e)}")
    
    # 获取错误信息
    error_response = CustomError.INTERNAL_SERVER_ERROR.as_dict(detail=str(e), lang=lang)
    return JSONResponse(status_code=200, content=error_response)

18.5 错误码体系设计

18.5.1 错误码分类

# exceptions.py
class CustomError(Enum):
    """错误码枚举类(支持中英文)"""
    
    # ===== 基础错误码 (1000-1999) =====
    SUCCESS = (0, "成功", "Success")
    PARAM_VALIDATION_FAILED = (1001, "参数校验失败", "Parameter validation failed")
    RESOURCE_NOT_FOUND = (1002, "资源不存在", "Resource not found")
    
    # ===== 业务错误码 (2000-2999) =====
    INVALID_DRAFT_URL = (2001, "无效的草稿URL", "Invalid draft URL")
    DRAFT_CREATE_FAILED = (2002, "草稿创建失败", "Draft creation failed")
    VIDEO_ADD_FAILED = (2006, "视频添加失败", "Video addition failed")
    
    # ===== 系统错误码 (9000-9999) =====
    INTERNAL_SERVER_ERROR = (9998, "系统内部错误", "Internal server error")
    UNKNOWN_ERROR = (9999, "未知异常", "Unknown error")

18.5.2 错误码设计原则

  1. 分层分类:按错误类型分层,便于定位问题
  2. 多语言支持:每个错误码包含中英文描述
  3. 唯一性:每个错误码在系统中唯一
  4. 可扩展性:预留足够的错误码空间

18.6 中间件使用示例

18.6.1 正常请求处理流程

# 路由层处理
@router.post("/add_videos")
def add_videos(request: AddVideosRequest) -> AddVideosResponse:
    # 调用服务层
    result = service.add_videos(
        draft_url=request.draft_url,
        video_infos=request.video_infos
    )
    return AddVideosResponse(**result)

# 中间件自动包装响应格式
{
    "code": 0,
    "message": "成功",
    "draft_id": "2025092811473036584258",
    "track_id": "track_123",
    "video_ids": ["video_1", "video_2"]
}

18.6.2 参数验证错误处理

# 请求参数验证失败
{
    "code": 1001,
    "message": "参数校验失败(draft_url: field required; video_infos: field required)"
}

18.6.3 业务异常处理

# 服务层抛出业务异常
raise CustomException(CustomError.INVALID_DRAFT_URL, "草稿不存在")

# 中间件自动格式化
{
    "code": 2001,
    "message": "无效的草稿URL(草稿不存在)"
}

18.7 性能优化与最佳实践

18.7.1 性能考虑

  1. 异步处理:所有中间件操作都使用异步模式
  2. 最小化处理:中间件只做必要的处理,不引入额外开销
  3. 错误日志:记录关键错误信息,便于问题排查
  4. 响应缓存:避免重复处理相同的响应内容

18.7.2 最佳实践

  1. 错误处理顺序:先处理已知异常,再处理未知异常
  2. 日志级别:合理使用不同的日志级别(info、warning、error)
  3. 安全考虑:不暴露内部实现细节给客户端
  4. 兼容性:保持响应格式的向后兼容性

18.7.3 调试与监控

# 开启详细日志记录
logger.setLevel(logging.DEBUG)

# 添加性能监控
import time

async def dispatch(self, request: Request, call_next):
    start_time = time.time()
    
    response = await call_next(request)
    
    process_time = time.time() - start_time
    logger.info(f"Request processed in {process_time:.3f}s")
    
    return response

18.8 总结

剪映小助手的中间件系统通过统一的设计模式,实现了请求预处理和响应格式化的标准化。这种设计带来了以下优势:

  1. 统一性:所有API响应都遵循统一的格式规范
  2. 可维护性:横切关注点集中在中间件中处理
  3. 可扩展性:易于添加新的中间件功能
  4. 多语言支持:根据客户端偏好自动选择语言
  5. 错误透明:清晰的错误码体系便于问题定位

中间件系统是构建可靠、可维护API服务的重要基础,为上层业务逻辑提供了稳定的基础设施支持。


相关资源

  • GitHub代码仓库: https://github.com/Hommy-master/capcut-mate
  • Gitee代码仓库: https://gitee.com/taohongmin-gitee/capcut-mate
  • API文档地址: https://docs.jcaigc.cn
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值