mirror of
https://github.com/Hommy-master/capcut-mate.git
synced 2026-10-29 18:16:54 +08:00
优化参数报错提示。
This commit is contained in:
+2
-2
@@ -25,9 +25,9 @@ class CustomError(Enum):
|
||||
self.cn_message = cn_message
|
||||
self.en_message = en_message
|
||||
|
||||
def as_dict(self, detail: str = None, language: str = 'zh') -> dict:
|
||||
def as_dict(self, detail: str = None, lang: str = 'zh') -> dict:
|
||||
"""转换为API响应格式,支持中英文"""
|
||||
message = self.cn_message if language == 'zh' else self.en_message
|
||||
message = self.cn_message if lang == 'zh' else self.en_message
|
||||
if detail:
|
||||
message += f"({detail})"
|
||||
return {"code": self.code, "message": message}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
from src.router import v1_router
|
||||
from src.utils.logger import logger
|
||||
from src import middlewares
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import Request, status
|
||||
from exceptions import CustomError
|
||||
from src.middlewares import PrepareMiddleware, ResponseMiddleware
|
||||
|
||||
|
||||
# 0. 创建 FastAPI 应用
|
||||
@@ -15,31 +11,11 @@ app = FastAPI(title="CapCut Mate API", version="1.0")
|
||||
app.include_router(v1_router, prefix="/openapi", tags=["capcut-mate"])
|
||||
|
||||
# 2. 添加中间件
|
||||
app.middleware("http")(middlewares.prepare_middleware)
|
||||
app.add_middleware(PrepareMiddleware)
|
||||
# 注册统一响应处理中间件(注意顺序,应该在其他中间件之后注册)
|
||||
app.middleware("http")(middlewares.response_middleware)
|
||||
app.add_middleware(ResponseMiddleware)
|
||||
|
||||
# 3. 异常处理:参数校验错误
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
"""自定义参数校验错误处理器"""
|
||||
# 1. 拼接完整错误信息
|
||||
error_messages = []
|
||||
for error in exc.errors():
|
||||
field = ".".join(str(loc) for loc in error["loc"])
|
||||
error_type = error["type"]
|
||||
error_msg = error["msg"]
|
||||
# 组合字段+错误类型+详情信息
|
||||
error_messages.append(f"{field} ({error_type}): {error_msg}")
|
||||
|
||||
# 2. 构建统一响应结构
|
||||
full_message = "; ".join(error_messages) # 用分号分隔多个错误
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content=CustomError.PARAM_VALIDATION_FAILED.as_dict(full_message)
|
||||
)
|
||||
|
||||
# 4. 打印所有路由
|
||||
# 3. 打印所有路由
|
||||
for r in app.routes:
|
||||
# 1. 取 HTTP 方法列表
|
||||
methods = getattr(r, "methods", None) or [getattr(r, "method", "WS")]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 中间件实现
|
||||
from .prepare import prepare_middleware
|
||||
from .response import response_middleware
|
||||
from .prepare import PrepareMiddleware
|
||||
from .response import ResponseMiddleware
|
||||
|
||||
__all__ = ["prepare_middleware", "response_middleware"]
|
||||
__all__ = ["PrepareMiddleware", "ResponseMiddleware"]
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# 环境初始化中间件
|
||||
from fastapi import Request
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
import config
|
||||
import os
|
||||
|
||||
|
||||
# 请求前的准备工作,如:创建草稿目录
|
||||
async def prepare_middleware(request: Request, call_next):
|
||||
# 递归创建目录,如果目录存在,就直接跳过创建
|
||||
os.makedirs(config.DRAFT_DIR, exist_ok=True)
|
||||
class PrepareMiddleware(BaseHTTPMiddleware):
|
||||
"""请求前的准备工作中间件
|
||||
功能:
|
||||
1. 创建临时目录
|
||||
2. 创建输出目录
|
||||
"""
|
||||
|
||||
# 继续处理请求
|
||||
response = await call_next(request)
|
||||
return response
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
# 递归创建目录,如果目录存在,就直接跳过创建
|
||||
os.makedirs(config.DRAFT_DIR, exist_ok=True)
|
||||
|
||||
# 继续处理请求
|
||||
response = await call_next(request)
|
||||
return response
|
||||
+127
-51
@@ -1,65 +1,141 @@
|
||||
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
|
||||
|
||||
|
||||
async def response_middleware(request: Request, call_next):
|
||||
class ResponseMiddleware(BaseHTTPMiddleware):
|
||||
"""统一响应处理中间件
|
||||
功能:
|
||||
1. 统一处理业务正常响应,添加code和message字段
|
||||
2. 统一处理异常,返回标准错误格式
|
||||
"""
|
||||
try:
|
||||
# 获取语言偏好(默认中文)
|
||||
lang = request.headers.get('Accept-Language', 'zh').split(',')[0].split('-')[0]
|
||||
if lang not in ['zh', 'en']:
|
||||
lang = 'zh'
|
||||
|
||||
# 调用下一个处理函数
|
||||
response = await call_next(request)
|
||||
|
||||
# 检查是否为JSON响应
|
||||
if response.headers.get('content-type') == 'application/json':
|
||||
# 读取响应内容
|
||||
body = [section async for section in response.body_iterator]
|
||||
if body:
|
||||
body_str = b''.join(body).decode()
|
||||
try:
|
||||
# 解析JSON响应
|
||||
data = json.loads(body_str)
|
||||
|
||||
# 检查响应是否已经包含code和message
|
||||
if 'code' not in data or 'message' not in data:
|
||||
# 包装成统一响应格式(平铺业务数据)
|
||||
unified_response = {
|
||||
'code': CustomError.SUCCESS.code,
|
||||
'message': CustomError.SUCCESS.as_dict(language=lang)['message'],
|
||||
**data
|
||||
}
|
||||
|
||||
# 创建新的JSON响应
|
||||
return JSONResponse(
|
||||
status_code=response.status_code,
|
||||
content=unified_response
|
||||
)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"JSON decode error: {body_str}")
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
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
|
||||
|
||||
return response
|
||||
except CustomException as e:
|
||||
return self._handle_custom_exception(e, lang)
|
||||
except Exception as e:
|
||||
return self._handle_generic_exception(e, lang)
|
||||
|
||||
def _get_language_from_request(self, request: Request) -> str:
|
||||
"""从请求头获取语言偏好"""
|
||||
lang = request.headers.get('Accept-Language', 'zh').split(',')[0].split('-')[0]
|
||||
return lang if lang in ['zh', 'en'] else 'zh'
|
||||
|
||||
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)
|
||||
|
||||
async def _handle_non_200_response(self, response, lang: str) -> JSONResponse:
|
||||
"""处理非200状态码的响应"""
|
||||
body = b""
|
||||
async for chunk in response.body_iterator:
|
||||
body += chunk
|
||||
|
||||
except CustomException as e:
|
||||
# 处理自定义业务异常
|
||||
logger.error(f"Custom exception: {e.err.code} - {e.err.cn_message}" + (f" ({e.detail})" if e.detail else ""))
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content=e.err.as_dict(e.detail, language=lang)
|
||||
)
|
||||
except Exception as e:
|
||||
# 处理其他未捕获的异常
|
||||
logger.error(f"Internal server error: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content=CustomError.INTERNAL_SERVER_ERROR.as_dict(str(e), language=lang)
|
||||
)
|
||||
body_str = body.decode()
|
||||
|
||||
# 特殊处理422错误(参数验证错误)
|
||||
if response.status_code == 422:
|
||||
return self._handle_422_error(body_str, lang)
|
||||
|
||||
# 其它情况不应该发生,每一个错误都应该在前面被处理
|
||||
logger.error(f"Non-200 response: {response.status_code} - {body_str}")
|
||||
# 其他非200错误处理
|
||||
error_response = {
|
||||
"code": response.status_code,
|
||||
"message": f"HTTP Error {response.status_code}",
|
||||
"data": {"detail": body_str}
|
||||
}
|
||||
|
||||
return JSONResponse(status_code=200, content=error_response)
|
||||
|
||||
def _is_json_response(self, response) -> bool:
|
||||
"""检查是否为JSON响应"""
|
||||
return response.headers.get('content-type') == 'application/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': data
|
||||
}
|
||||
|
||||
return JSONResponse(
|
||||
status_code=response.status_code,
|
||||
content=unified_response
|
||||
)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"JSON decode error: {body_str}")
|
||||
return response
|
||||
|
||||
def _handle_custom_exception(self, e: CustomException, lang: str) -> JSONResponse:
|
||||
"""处理自定义异常(不包含data字段)"""
|
||||
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:
|
||||
"""处理通用异常(不包含data字段)"""
|
||||
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)
|
||||
Reference in New Issue
Block a user