惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Y
Y Combinator Blog
月光博客
月光博客
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
博客园 - 司徒正美
V
Visual Studio Blog
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
The Cloudflare Blog

博客园 - VipSoft

SpringBoot 心跳日志不记录 access.log Qdrant Linux 安装(非Docker) LangChain — RAG 知识库(实操) LangChain — RAG 构建知识库(理论) LangChain — RAG 构建知识库(实操) Python PyCharm 运行,取不到 .env 文件中的值 Qdrant 安装(Windows) LangChain — RAG 构建知识库 Python 项目简单部署(Linux) MinerU - 将非结构化文档(PDF、图片、Office 文件等)转换为机器可读的 Markdown 和 JSON LangChain 入门 服务端部署-FastAPI LangChain 入门 LangSmith LangChain 入门 实战 - 食谱推荐 LangChain 入门 Memory 会话记忆 LangChain 入门 Tools 工具 LangChain 入门 Tools 工具 LangChain 入门 Prompts 提示词 LangChain 入门 Message 消息 LangChain 入门 Model 的初始化和调用 LangChain 入门 Agent 的基本运行机制 AI 0基础学习,名词解析 LangChain 和 LangGraph AI大模型知识体系 Dify — Workflow - 数据可视化 Dify — 连接MySQL配置 Dify — Chatflow - 数据库智能查询 Dify — Chatflow - 文档知识库 Dify — Agent 智能体 高安全券码、注册码生成 Dify — 文本生成应用
FastAPI 全局 HTTP 异常处理器 + 统一响应封装
VipSoft · 2026-09-10 · via 博客园 - VipSoft

这是通过全局 HTTP 异常处理器 + 统一响应封装实现的。

应用启动时注册处理器

main.py

configure_api_responses(app)

无效路径触发 404 异常

请求不存在的路径时,FastAPI 底层的 Starlette 路由会抛出 HTTPException,其中:

status_code = 404
detail = "Not Found"

注册的处理器接住这个异常:

@app.exception_handler(HTTPException)
async def http_error(request: Request, exc: HTTPException):
    return error_response(
        exc.status_code, {"detail": exc.detail}, exc.headers
    )

这里导入的是 starlette.exceptions.HTTPException,所以也能捕获框架产生的路由异常。

将异常转换成统一 JSON

[responses.py:22]的 error_response()

  • 通过 ERROR_CODES404 映射为业务码 4004
  • 将字符串 detail 作为 message
  • {"detail": "Not Found"} 放入 data
  • ApiResponse 序列化,返回 JSONResponse

因此响应体是:

{
  "code": 4004,
  "message": "Not Found",
  "data": {"detail": "Not Found"}
}

注意:目前实际返回的 HTTP 状态码是 200,因为 error_response() 中写的是 status_code=200404 被转换成了响应体里的业务码 4004

示例代码

main.py

def create_app(
    settings: Settings | None = None,
    extractor=None,
    rag_answer_service=None,
) -> FastAPI:
    settings = settings or Settings()
    settings.ensure_directories()
    settings.configure_logging(component="api")
    repository = create_document_repository(settings)
    if extractor is None:
        from vipsoft_agent.infrastructure.llm.qwen import QwenRequirementExtractor

        extractor = QwenRequirementExtractor(settings)

     
    app = FastAPI(
        title="VipSoftAgent",
        version="0.1.0",
        description="VipSoftAgent Service",
        lifespan=lifespan,
        redirect_slashes=False,
        docs_url=None,
        redoc_url=None
    )
    configure_api_responses(app) # 配置异常处理器
    .....
    return app


def run() -> None:
    import uvicorn

    uvicorn.run("vipsoft_agent.main:create_app", factory=True, host="0.0.0.0", port=8000)

responses.py


def configure_api_responses(app: FastAPI) -> None:
    @app.exception_handler(HTTPException)
    async def http_error(request: Request, exc: HTTPException):
        return error_response(exc.status_code, {"detail": exc.detail}, exc.headers)

    @app.exception_handler(RequestValidationError)
    async def validation_error(request: Request, exc: RequestValidationError):
        return error_response(422, {"detail": exc.errors()})

    # Keep unexpected errors inside CORS so allowed callers can read the envelope.
    @app.middleware("http")
    async def unexpected_error(request: Request, call_next):
        try:
            return await call_next(request)
        except Exception:
            logger.exception("Unhandled API error method=%s path=%s",
                             request.method, request.url.path)
            return error_response(500, {"detail": "Internal server error"})

    def openapi():
        if app.openapi_schema is None:
            schema = get_openapi(
                title=app.title, version=app.version,
                description=app.description, routes=app.routes,
            )
            for path in schema["paths"].values():
                for operation in path.values():
                    if isinstance(operation, dict) and "responses" in operation:
                        operation["responses"].pop("422", None)
            app.openapi_schema = schema
        return app.openapi_schema

    app.openapi = openapi

def error_response(status: int, data: dict, headers=None) -> JSONResponse:
    detail = data.get("detail")
    if isinstance(detail, str):
        message = detail
    elif isinstance(detail, list) and detail and isinstance(detail[0], dict):
        message = str(detail[0].get("msg", "Request failed"))
    else:
        message = HTTPStatus(status).phrase
    return JSONResponse(
        status_code=200,
        content=ApiResponse(
            code=ERROR_CODES.get(status, status * 10),
            message=message,
            data=jsonable_encoder(data),
        ).model_dump(mode="json"),
        headers=headers,
    )