Files
my-v0-server/app/core/exceptions.py
T
2026-09-29 07:23:42 -07:00

82 lines
2.9 KiB
Python

"""业务异常与全局 API 异常处理"""
from http import HTTPStatus
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from loguru import logger
from openai import APIError, APITimeoutError
from starlette.exceptions import HTTPException
from app.common.response import error_content
from app.exceptions import AppError, DecisionError
class BizException(AppError):
"""可安全返回给调用方的业务错误"""
def __init__(self, code: int, msg: str = "请求失败") -> None:
if not 400 <= code <= 599:
raise ValueError("业务错误码必须是 400 到 599 的 HTTP 状态码")
self.code = code
self.msg = msg
super().__init__(msg)
async def handle_biz_exception(_request: Request, exc: BizException) -> JSONResponse:
return JSONResponse(status_code=exc.code, content=error_content(exc.code, exc.msg))
async def handle_validation_exception(
_request: Request, _exc: RequestValidationError
) -> JSONResponse:
# 不返回原始输入,避免校验错误携带用户数据
return JSONResponse(status_code=422, content=error_content(422, "请求参数校验失败"))
async def handle_http_exception(_request: Request, exc: HTTPException) -> JSONResponse:
# 保留状态与协议响应头,例如 Allow、WWW-Authenticate 和 Retry-After
try:
message = HTTPStatus(exc.status_code).phrase
except ValueError:
message = "请求失败"
return JSONResponse(
status_code=exc.status_code,
content=error_content(exc.status_code, message),
headers=exc.headers,
)
async def handle_unexpected_exception(
_request: Request, exc: Exception
) -> JSONResponse:
# 详细异常仅记录在服务端,不将堆栈或内部错误内容返回给前端
logger.bind(thread_id=getattr(exc, "_agent_thread_id", "-")).opt(
exception=exc
).error("未处理的服务器异常")
exc._logged_by_api = True
if isinstance(exc, APITimeoutError) or (
isinstance(exc, DecisionError) and exc.kind == "timeout"
):
return JSONResponse(
status_code=504, content=error_content(504, "模型服务请求超时")
)
if isinstance(exc, APIError) or (
isinstance(exc, DecisionError) and exc.kind != "configuration"
):
return JSONResponse(
status_code=502, content=error_content(502, "模型服务调用失败")
)
return JSONResponse(status_code=500, content=error_content(500, "服务内部错误"))
def register_exception_handlers(app: FastAPI) -> None:
"""集中注册异常处理,接口无需重复捕获"""
app.add_exception_handler(BizException, handle_biz_exception)
app.add_exception_handler(RequestValidationError, handle_validation_exception)
app.add_exception_handler(HTTPException, handle_http_exception)
app.add_exception_handler(Exception, handle_unexpected_exception)