python系列:FASTAPI系列 19返回异常处理 & FASTAPI系列 20-异常处理器exception_handler

332 篇文章 ¥99.90 ¥299.90




一、 FASTAPI系列 19返回异常处理

前言

某些情况下,需要向客户端返回错误提示。这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。需要向客户端返回错误提示的场景主要如下:

  • 遇到这些情况时,通常要返回 4XX(400 至 499)HTTP 状态码。

  • 4XX 状态码与表示请求成功的 2XX(200 至 299) HTTP 状态码类似。只不过,4XX 状态码表示客户端发生的错误。

一、使用 HTTPException

向客户端返回 HTTP 错误响应,可以使用 HTTPException

from fastapi import FastAPI, HTTPException

app = FastAPI()

person= {
   
   
FO: Will watch for changes in these directories: ['E:\\python\\Python313\\fastapi\\ORM系统'] INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit) INFO: Started reloader process [8364] using StatReload INFO: Started server process [12812] INFO: Waiting for application startup. INFO: Application startup complete. INFO: 127.0.0.1:63944 - "GET /docs HTTP/1.1" 200 OK INFO: 127.0.0.1:63944 - "GET /openapi.json HTTP/1.1" 200 OK INFO: 127.0.0.1:63944 - "GET /student/ HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application Traceback (most recent call last): File "E:\python\Python313\fastapi\.venv\Lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 403, in run_asgi result = await app( # type: ignore[func-returns-value] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ self.scope, self.receive, self.send ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 60, in __call__ return await self.app(scope, receive, send) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\fastapi\applications.py", line 1054, in __call__ await super().__call__(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\applications.py", line 112, in __call__ await self.middleware_stack(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\middleware\errors.py", line 187, in __call__ raise exc File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\middleware\errors.py", line 165, in __call__ await self.app(scope, receive, _send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\middleware\exceptions.py", line 62, in __call__ await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app raise exc File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app await app(scope, receive, sender) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\routing.py", line 714, in __call__ await self.middleware_stack(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\routing.py", line 734, in app await route.handle(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\routing.py", line 288, in handle await self.app(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\routing.py", line 76, in app await wrap_app_handling_exceptions(app, request)(scope, receive, send) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app raise exc File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app await app(scope, receive, sender) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\starlette\routing.py", line 73, in app response = await f(request) ^^^^^^^^^^^^^^^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\fastapi\routing.py", line 301, in app raw_response = await run_endpoint_function( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...<3 lines>... ) ^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\fastapi\routing.py", line 212, in run_endpoint_function return await dependant.call(**values) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "E:\python\Python313\fastapi\ORM系统\api\students.py", line 26, in get_all_students stu1 = await Students.filter(sno__gt=2003) ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\tortoise\models.py", line 1323, in filter return cls._meta.manager.get_queryset().filter(*args, **kwargs) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\tortoise\manager.py", line 19, in get_queryset return QuerySet(self._model) File "E:\python\Python313\fastapi\.venv\Lib\site-packages\tortoise\queryset.py", line 337, in __init__ super().__init__(model) ~~~~~~~~~~~~~~~~^^^^^^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\tortoise\queryset.py", line 99, in __init__ self.capabilities: Capabilities = model._meta.db.capabilities ^^^^^^^^^^^^^^ File "E:\python\Python313\fastapi\.venv\Lib\site-packages\tortoise\models.py", line 280, in db raise ConfigurationError( f"default_connection for the model {self._model} cannot be None" ) tortoise.exceptions.ConfigurationError: default_connection for the model <class 'ORM系统.models.Students'> cannot be None
06-30
PS F:\Programmer\python\new_my_AI> cd backend PS F:\Programmer\python\new_my_AI\backend> ls 目录: F:\Programmer\python\new_my_AI\backend Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2025/11/5 17:53 models d----- 2025/11/6 9:59 routers d----- 2025/11/5 18:36 schemas -a---- 2025/11/5 17:59 325 .env -a---- 2025/11/5 17:58 888 config.py -a---- 2025/11/5 17:54 1035 database.py -a---- 2025/11/5 17:39 540 dependencies.py -a---- 2025/10/19 19:45 1838 install_deps.bat -a---- 2025/11/5 17:57 4108 jwt_handler.py -a---- 2025/11/5 17:16 1280 logger_setup.py -a---- 2025/11/5 17:45 1023 main.py -a---- 2025/10/19 18:58 283 requirements.txt PS F:\Programmer\python\new_my_AI\backend> cd models PS F:\Programmer\python\new_my_AI\backend\models> ls 目录: F:\Programmer\python\new_my_AI\backend\models Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2025/11/5 17:53 1978 chat.py -a---- 2025/11/5 17:51 733 department.py -a---- 2025/11/5 17:52 2417 post.py -a---- 2025/11/5 17:53 398 search.py -a---- 2025/11/5 17:51 1378 user.py -a---- 2025/11/5 17:50 544 __init__.py PS F:\Programmer\python\new_my_AI\backend\models> cd .. PS F:\Programmer\python\new_my_AI\backend> cd routers PS F:\Programmer\python\new_my_AI\backend\routers> ls 目录: F:\Programmer\python\new_my_AI\backend\routers Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2025/11/5 17:20 2979 api_chat.py -a---- 2025/11/5 17:43 1447 auth.py -a---- 2025/11/5 17:45 1677 chat_rooms.py -a---- 2025/11/5 17:44 2349 comments.py -a---- 2025/11/5 17:44 1097 departments.py -a---- 2025/11/5 17:47 751 posts.py -a---- 2025/11/5 17:45 1602 search.py -a---- 2025/11/6 9:59 0 __init__.py PS F:\Programmer\python\new_my_AI\backend\routers> cd .. PS F:\Programmer\python\new_my_AI\backend> cd schemas PS F:\Programmer\python\new_my_AI\backend\schemas> ls 目录: F:\Programmer\python\new_my_AI\backend\schemas Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2025/11/5 18:03 435 auth.py -a---- 2025/11/5 18:02 951 chat.py -a---- 2025/11/5 18:01 587 comment.py -a---- 2025/11/5 18:36 3777 common.py -a---- 2025/11/5 17:42 481 post.py -a---- 2025/11/5 17:41 100 user.py PS F:\Programmer\python\new_my_AI\backend\schemas> 这是我现在的项目结构 # backend/database.py from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlmodel import SQLModel from contextlib import asynccontextmanager from config import settings import asyncio # 从 models 包导入所有表模型 from models import * # 创建引擎 engine = create_async_engine( settings.DATABASE_URL, echo=settings.DEBUG, pool_pre_ping=True ) AsyncSessionLocal = async_sessionmaker( bind=engine, class_=AsyncSession, expire_on_commit=False ) @asynccontextmanager async def get_db(): async with AsyncSessionLocal() as session: try: yield session await session.commit() except Exception: await session.rollback() raise async def create_tables(): async with engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) print("✅ 所有数据库表已创建完成!") async def close_db(): await engine.dispose() # backend/dependencies.py from fastapi import Depends, HTTPException from sqlalchemy import select from database import User, get_db from jwt_handler import get_current_user_id async def get_current_active_user(user_id: str = Depends(get_current_user_id)): async with get_db() as db: result = await db.execute(select(User).where(User.id == int(user_id))) user = result.scalar_one_or_none() if not user: raise HTTPException(status_code=404, detail="用户不存在") return user # backend/jwt_handler.py from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Optional import os from dotenv import load_dotenv from fastapi import Cookie, Depends, HTTPException from jose import jwt, JWTError from passlib.hash import pbkdf2_sha256 from pydantic import BaseModel from config import settings # 使用统一配置 from logger_setup import app_logger as logger # ==================== 模型定义 ==================== class TokenData(BaseModel): user_id: Optional[str] = None # ==================== 密码工具 ==================== def get_password_hash(password: str) -> str: """ 使用 PBKDF2-SHA256 对密码进行哈希。 默认 rounds=29000,安全性高,适合用户密码存储。 """ return pbkdf2_sha256.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """ 校验明文密码是否匹配哈希值。 Passlib 会自动识别 salt 和 rounds。 """ return pbkdf2_sha256.verify(plain_password, hashed_password) # ==================== JWT 工具函数 ==================== def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: """ 创建 JWT 访问令牌 :param data: 要编码的数据,建议包含 {"sub": "user_id"} :param expires_delta: 可选的过期时间偏移量 :return: 编码后的 JWT 字符串 """ to_encode = data.copy() expire = datetime.now(timezone.utc) + ( expires_delta if expires_delta else timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) ) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) logger.info(f"✅ JWT 已生成,有效期至: {expire}") return encoded_jwt def decode_access_token(token: str) -> Optional[dict]: """ 【同步】解码 JWT 并返回 payload(可用于中间件、日志等非 await 上下文) :param token: JWT 字符串 :return: 解码后的 payload 或 None(无效/过期) """ try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) logger.debug(f"🔍 JWT 成功解码: {payload}") return payload except JWTError as e: logger.warning(f"❌ JWT 解码失败 (JWTError): {e}") return None except Exception as e: logger.error(f"❌ 意外错误解码 JWT: {e}") return None # ==================== FastAPI 依赖项 ==================== async def get_current_user_id(access_token: str = Cookie(None)) -> str: """ FastAPI 依赖:从 HttpOnly Cookie 提取用户 ID 若未登录或 token 无效,抛出 401 错误 :return: 用户 ID(字符串) """ if not access_token: logger.warning("🚫 请求缺少 access_token") raise HTTPException(status_code=401, detail="未登录") payload = decode_access_token(access_token) if not payload: logger.warning("⚠️ 无法解析 JWT payload") raise HTTPException(status_code=401, detail="凭证无效") user_id: str = payload.get("sub") if not user_id: logger.warning("⚠️ JWT 中缺少 'sub' 字段") raise HTTPException(status_code=401, detail="凭证不完整") logger.info(f"👤 当前用户已认证: user_id={user_id}") return user_id async def get_current_active_user(user_id: str = Depends(get_current_user_id)): """ 扩展依赖:根据 user_id 查询数据库中的活跃用户对象 (可在此处添加封禁检查、角色权限判断等) """ from database import get_user_by_id user = await get_user_by_id(int(user_id)) if not user: logger.warning(f"❌ 用户不存在: user_id={user_id}") raise HTTPException(status_code=404, detail="用户不存在") if user.role == "banned": raise HTTPException(status_code=403, detail="该账户已被封禁") return user # backend/logger_setup.py import logging import os from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler from pathlib import Path from config import BASE_DIR LOG_DIR = BASE_DIR.parent / "logs" LOG_DIR.mkdir(exist_ok=True) def setup_logger(): logger = logging.getLogger("chat_app") logger.setLevel(logging.INFO) # 防止重复添加 handler if logger.handlers: return logger # 格式 formatter = logging.Formatter( '%(asctime)s | %(levelname)s | %(name)s | %(funcName)s() | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 1. 按时间切割的日志文件(每天一个) file_handler = TimedRotatingFileHandler( LOG_DIR / "app.log", when="midnight", interval=1, backupCount=7, # 保留最近7天 encoding='utf-8' ) file_handler.setFormatter(formatter) file_handler.setLevel(logging.INFO) # 2. 控制台输出 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) console_handler.setLevel(logging.INFO) # 添加到 logger logger.addHandler(file_handler) logger.addHandler(console_handler) return logger app_logger = setup_logger() # backend/main.py from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from config import FRONTEND_DIR from logger_setup import app_logger as logger from routers import auth, posts, comments, departments, search, chat_rooms app = FastAPI(title="AI社交平台", version="1.0") @app.middleware("http") async def log_requests(request, call_next): logger.info(f"➡️ {request.method} {request.url.path} | IP: {request.client.host}") response = await call_next(request) logger.info(f"⬅️ Status: {response.status_code}") return response app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR / "static")), name="static") app.include_router(auth.router) app.include_router(posts.router) app.include_router(comments.router) app.include_router(departments.router) app.include_router(search.router) app.include_router(chat_rooms.router) if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True) 整理一下,把缺失的部分补全
最新发布
11-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坦笑&&life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值