feat: 接入fastapi
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""业务异常与全局 API 异常处理"""
|
||||
|
||||
import logging
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from openai import APIError, APITimeoutError
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from app.common.response import error_content
|
||||
from app.exceptions import AppError, DecisionError, ImportProcessError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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.error("未处理的服务器异常", exc_info=(type(exc), exc, exc.__traceback__))
|
||||
# 节点包装异常时沿原始原因查找,区分上游服务故障与内部错误
|
||||
cause = exc
|
||||
while isinstance(cause, ImportProcessError):
|
||||
nested = cause.cause or cause.__cause__
|
||||
if nested is None:
|
||||
break
|
||||
cause = nested
|
||||
if isinstance(cause, APITimeoutError) or (
|
||||
isinstance(cause, DecisionError) and cause.kind == "timeout"
|
||||
):
|
||||
return JSONResponse(
|
||||
status_code=504, content=error_content(504, "模型服务请求超时")
|
||||
)
|
||||
if isinstance(cause, APIError) or (
|
||||
isinstance(cause, DecisionError)
|
||||
and cause.kind not in {"configuration", "closed"}
|
||||
):
|
||||
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)
|
||||
Reference in New Issue
Block a user