这是整合了全局异常处理 的完整 FastAPI 后端开发流程,涵盖从项目结构、安全配置、数据库、认证逻辑到统一错误响应的全部关键环节,并特别强调安全与可维护性。
🧱 一、项目结构(含全局异常)
text
编辑
/backend
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 实例 + 全局异常处理器
│ ├── config.py # 配置(SECRET_KEY 等)
│ ├── database.py # DB 会话
│ ├── exceptions.py # 自定义异常 + 全局处理器
│ ├── models/
│ │ └── user.py
│ ├── schemas/
│ │ └── auth.py, common.py
│ ├── routers/
│ │ └── auth.py
│ └── core/
│ └── security.py
├── alembic/
├── .env
├── .gitignore
└── requirements.txt
⚠️ 二、自定义异常与全局异常处理(核心)
✅ 1. schemas/common.py:统一响应格式
python
编辑
# app/schemas/common.py
from pydantic import BaseModel
from typing import Optional
class ErrorResponse(BaseModel):
detail: str
code: Optional[str] = None # 可选业务错误码,如 "USER_NOT_FOUND"
✅ 2. exceptions.py:自定义异常 + 全局处理器
python
编辑
# app/exceptions.py
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from sqlalchemy.exc import IntegrityError
from jose.exceptions import JWTError
import logging
logger = logging.getLogger("app")
# 自定义业务异常
class UserNotFoundException(Exception):
pass
class InvalidCredentialsException(Exception):
pass
class PasswordTooLongException(Exception):
pass
def register_exception_handlers(app: FastAPI):
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
logger.warning(f"HTTP {exc.status_code}: {exc.detail} | URL: {request.url}")
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
logger.error(f"Validation error: {exc.errors()} | URL: {request.url}")
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": "请求参数格式错误", "errors": exc.errors()}
)
@app.exception_handler(IntegrityError)
async def integrity_error_handler(request: Request, exc: IntegrityError):
logger.error(f"Database integrity error: {str(exc)}")
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": "用户名已存在"}
)
@app.exception_handler(JWTError)
async def jwt_error_handler(request: Request, exc: JWTError):
logger.warning(f"JWT decode error: {str(exc)} | URL: {request.url}")
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "无效的认证凭证"},
headers={"WWW-Authenticate": "Bearer"}
)
# 自定义业务异常
@app.exception_handler(UserNotFoundException)
async def user_not_found_handler(request: Request, exc: UserNotFoundException):
return JSONResponse(
status_code=status.HTTP_404_NOT_FOUND,
content={"detail": "用户不存在"}
)
@app.exception_handler(InvalidCredentialsException)
async def invalid_credentials_handler(request: Request, exc: InvalidCredentialsException):
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "用户名或密码错误"},
headers={"WWW-Authenticate": "Bearer"}
)
@app.exception_handler(PasswordTooLongException)
async def password_too_long_handler(request: Request, exc: PasswordTooLongException):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": "密码长度不能超过72字节"}
)
# 通用服务器错误(兜底)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.error(f"Unexpected error: {repr(exc)} | URL: {request.url}", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"detail": "服务器内部错误,请稍后再试"}
)
✅ 优势:
- 所有异常返回 统一 JSON 格式
- 敏感信息(如 SQL 错误)不暴露给前端
- 记录日志便于排查
- 保留标准 HTTP 状态码(401、404、500 等)
🧩 三、在 main.py 中注册异常处理器
python
编辑
# app/main.py
from fastapi import FastAPI
from app.routers import auth
from app.exceptions import register_exception_handlers
from app.database import Base, engine
# 创建表(仅开发用,生产用 Alembic)
Base.metadata.create_all(bind=engine)
app = FastAPI(title="Note App API", version="1.0.0")
# 注册全局异常处理器
register_exception_handlers(app)
# 路由
app.include_router(auth.router, prefix="/api/v1")
🔑 四、在业务逻辑中抛出自定义异常(示例)
python
编辑
# app/routers/auth.py
from fastapi import APIRouter, Depends, status
from sqlalchemy.orm import Session
from app import models, security
from app.database import get_db
from app.exceptions import InvalidCredentialsException, PasswordTooLongException
from app.schemas import auth as auth_schema
router = APIRouter()
@router.post("/login", response_model=auth_schema.Token)
def login(data: auth_schema.LoginRequest, db: Session = Depends(get_db)):
user = db.query(models.User).filter(models.User.username == data.username).first()
if not user:
raise InvalidCredentialsException() # 统一提示,防用户枚举
if len(data.password.encode('utf-8')) > 72:
raise PasswordTooLongException()
if not security.verify_password(data.password, user.password):
raise InvalidCredentialsException()
# ...生成 token...
return {"access_token": access_token, "token_type": "bearer"}
✅ 好处:业务代码干净,错误处理集中
📦 五、依赖更新(requirements.txt)
确保包含日志和异常相关包:
txt
编辑
fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
passlib[bcrypt]==1.7.4 # 注意 bcrypt < 4.0
python-jose[cryptography]==3.3.0
python-dotenv==1.0.1
alembic==1.13.2
pydantic-settings==2.6.1
🛡️ 六、安全与健壮性增强点
| 功能 | 实现方式 |
|---|---|
| 防暴力破解 | 在 login 中记录失败次数(Redis + 限流) |
| 敏感日志脱敏 | 日志中不记录密码、token |
| CORS 安全 | 限制 allow_origins=["https://your-frontend.com"] |
| HTTPS 强制 | 生产环境用 Nginx 或云服务强制 HTTPS |
| 速率限制 | 使用 slowapi 对 /login 限流 |
✅ 七、全局异常处理的优势总结
| 场景 | 传统做法 | 使用全局异常处理 |
|---|---|---|
| 参数校验失败 | 返回 422,但格式不统一 | ✅ 统一返回 {"detail": "...", "errors": [...]} |
| 数据库唯一冲突 | 报 500,暴露 SQL 错误 | ✅ 返回 400:"用户名已存在" |
| Token 无效 | 报 500 或模糊错误 | ✅ 返回 401 + WWW-Authenticate: Bearer |
| 未捕获异常 | 崩溃或暴露堆栈 | ✅ 返回 500:"服务器内部错误",日志记录详情 |
| 业务逻辑错误 | 手动写 raise HTTPException |
✅ 抛自定义异常,自动转为标准响应 |
🚀 最终效果(前端看到的响应)
✅ 登录成功:
json
编辑
{ "access_token": "xxx", "token_type": "bearer" }
❌ 密码错误:
json
编辑
{ "detail": "用户名或密码错误" }
// 状态码: 401
// Header: WWW-Authenticate: Bearer
❌ 用户名已存在(注册时):
json
编辑
{ "detail": "用户名已存在" }
// 状态码: 400
❌ 服务器崩溃:
json
编辑
{ "detail": "服务器内部错误,请稍后再试" }
// 状态码: 500
// (真实错误记录在后端日志中)
✅ 总结 Checklist
- 使用
exceptions.py集中管理所有异常 - 自定义业务异常(如
InvalidCredentialsException) - 捕获
IntegrityError、JWTError、RequestValidationError等常见错误 - 所有响应格式统一(
ErrorResponse) - 敏感错误不暴露细节,只记录日志
- 保留标准 HTTP 状态码语义