第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负责在请求处理前进行环境准备工作,主要包括:
- 创建草稿目录
- 创建临时文件目录
- 确保必要的文件系统环境就绪
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 设计要点
- 幂等性:使用
exist_ok=True参数确保目录已存在时不会抛出异常 - 异步处理:使用
async/await模式,不阻塞事件循环 - 轻量级:只做必要的文件系统准备工作,不涉及复杂逻辑
- 错误透明:目录创建失败会直接抛出异常,由上层统一处理
18.4 ResponseMiddleware - 统一响应处理中间件
18.4.1 功能概述
ResponseMiddleware是系统中最复杂的中间件,负责统一处理所有HTTP响应:
- 统一响应格式:将业务响应包装成标准格式
- 异常统一处理:捕获并格式化各种异常情况
- 多语言支持:根据请求头返回中英文错误信息
- 参数验证错误特殊处理:对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 错误码设计原则
- 分层分类:按错误类型分层,便于定位问题
- 多语言支持:每个错误码包含中英文描述
- 唯一性:每个错误码在系统中唯一
- 可扩展性:预留足够的错误码空间
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 性能考虑
- 异步处理:所有中间件操作都使用异步模式
- 最小化处理:中间件只做必要的处理,不引入额外开销
- 错误日志:记录关键错误信息,便于问题排查
- 响应缓存:避免重复处理相同的响应内容
18.7.2 最佳实践
- 错误处理顺序:先处理已知异常,再处理未知异常
- 日志级别:合理使用不同的日志级别(info、warning、error)
- 安全考虑:不暴露内部实现细节给客户端
- 兼容性:保持响应格式的向后兼容性
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 总结
剪映小助手的中间件系统通过统一的设计模式,实现了请求预处理和响应格式化的标准化。这种设计带来了以下优势:
- 统一性:所有API响应都遵循统一的格式规范
- 可维护性:横切关注点集中在中间件中处理
- 可扩展性:易于添加新的中间件功能
- 多语言支持:根据客户端偏好自动选择语言
- 错误透明:清晰的错误码体系便于问题定位
中间件系统是构建可靠、可维护API服务的重要基础,为上层业务逻辑提供了稳定的基础设施支持。
相关资源
- GitHub代码仓库: https://github.com/Hommy-master/capcut-mate
- Gitee代码仓库: https://gitee.com/taohongmin-gitee/capcut-mate
- API文档地址: https://docs.jcaigc.cn
1055

被折叠的 条评论
为什么被折叠?



