Python后端开发实战:FastAPI构建高性能RESTful API完整指南
导语: FastAPI 是目前 Python 生态中性能最优、开发体验最佳的 Web 框架,基于 Python 类型注解自动生成文档,内置数据校验、异步支持、依赖注入,在 TechEmpower 基准测试中性能接近 Node.js。本文从项目搭建到生产级部署,完整讲解 FastAPI 开发 RESTful API 的核心技术栈。
一、项目初始化与目录结构
bash
pip install fastapi uvicorn[standard] sqlalchemy pydantic-settings python-jose[cryptography] passlib[bcrypt] python-multipart
fastapi_project/
├── app/
│ ├── __init__.py
│ ├── main.py # 应用入口
│ ├── config.py # 配置管理
│ ├── database.py # 数据库连接
│ ├── models/ # SQLAlchemy ORM模型
│ │ ├── __init__.py
│ │ └── user.py
│ ├── schemas/ # Pydantic 请求/响应模型
│ │ ├── __init__.py
│ │ └── user.py
│ ├── routers/ # 路由模块
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── auth.py
│ ├── services/ # 业务逻辑层
│ │ └── user_service.py
│ └── dependencies.py # 依赖注入
├── tests/
├── requirements.txt
└── Dockerfile
二、核心功能实现
2.1 配置管理(pydantic-settings)
python
# app/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
# 应用配置
app_name: str = "FastAPI Demo"
debug: bool = False
api_v1_prefix: str = "/api/v1"
# 数据库
database_url: str = "sqlite:///./app.db"
# JWT
secret_key: str = "your-secret-key-change-in-production"
algorithm: str = "HS256"
access_token_expire_minutes: int = 30
class Config:
env_file = ".env" # 从 .env 文件读取配置
@lru_cache()
def get_settings() -> Settings:
return Settings()
settings = get_settings()
2.2 数据库配置(SQLAlchemy)
python
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import settings
engine = create_engine(
settings.database_url,
connect_args={"check_same_thread": False} # SQLite专用
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# 数据库会话依赖
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
2.3 ORM模型
python
# app/models/user.py
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from app.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(100), unique=True, index=True, nullable=False)
hashed_password = Column(String(200), nullable=False)
is_active = Column(Boolean, default=True)
is_admin = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
2.4 Pydantic 请求/响应模型
python
# app/schemas/user.py
from pydantic import BaseModel, EmailStr, Field, validator
from datetime import datetime
from typing import Optional
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50, description="用户名")
email: EmailStr = Field(..., description="邮箱地址")
password: str = Field(..., min_length=8, description="密码(最少8位)")
@validator('username')
def username_alphanumeric(cls, v):
if not v.replace('_', '').isalnum():
raise ValueError('用户名只能包含字母、数字和下划线')
return v
class UserResponse(BaseModel):
id: int
username: str
email: str
is_active: bool
created_at: datetime
class Config:
from_attributes = True # 允许从ORM对象转换(Pydantic v2)
class UserUpdate(BaseModel):
username: Optional[str] = Field(None, min_length=3, max_length=50)
email: Optional[EmailStr] = None
三、路由与业务逻辑
python
# app/routers/users.py
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db
from app.models.user import User
from app.schemas.user import UserCreate, UserResponse, UserUpdate
from app.dependencies import get_current_user
from passlib.context import CryptContext
router = APIRouter(prefix="/users", tags=["用户管理"])
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED,
summary="创建用户")
async def create_user(user_data: UserCreate, db: Session = Depends(get_db)):
# 检查用户名/邮箱是否已存在
if db.query(User).filter(User.username == user_data.username).first():
raise HTTPException(status_code=400, detail="用户名已存在")
if db.query(User).filter(User.email == user_data.email).first():
raise HTTPException(status_code=400, detail="邮箱已被注册")
hashed_password = pwd_context.hash(user_data.password)
user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hashed_password
)
db.add(user)
db.commit()
db.refresh(user)
return user
@router.get("/", response_model=List[UserResponse], summary="获取用户列表")
async def list_users(
skip: int = Query(default=0, ge=0, description="跳过数量"),
limit: int = Query(default=20, ge=1, le=100, description="返回数量"),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
users = db.query(User).filter(User.is_active == True).offset(skip).limit(limit).all()
return users
@router.get("/{user_id}", response_model=UserResponse, summary="获取用户详情")
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user
@router.put("/{user_id}", response_model=UserResponse, summary="更新用户信息")
async def update_user(
user_id: int,
user_data: UserUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
update_data = user_data.dict(exclude_unset=True) # 只更新提供的字段
for key, value in update_data.items():
setattr(user, key, value)
db.commit()
db.refresh(user)
return user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT, summary="删除用户")
async def delete_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
user.is_active = False # 软删除
db.commit()
四、JWT认证实现
python
# app/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from app.config import settings
from app.database import get_db
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
def create_access_token(data: dict) -> str:
from datetime import datetime, timedelta
expire = datetime.utcnow() + timedelta(minutes=settings.access_token_expire_minutes)
to_encode = data.copy()
to_encode.update({"exp": expire})
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭证",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
user_id: int = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
from app.models.user import User
user = db.query(User).filter(User.id == user_id).first()
if not user or not user.is_active:
raise credentials_exception
return user
五、应用入口与中间件
python
# app/main.py
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
import time, logging
from app.config import settings
from app.database import Base, engine
from app.routers import users, auth
# 创建数据库表
Base.metadata.create_all(bind=engine)
app = FastAPI(
title=settings.app_name,
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
# CORS中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Gzip压缩
app.add_middleware(GZipMiddleware, minimum_size=1000)
# 请求耗时日志
@app.middleware("http")
async def log_requests(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
logging.info(f"{request.method} {request.url.path} - {response.status_code} - {duration:.3f}s")
return response
# 注册路由
app.include_router(auth.router, prefix=settings.api_v1_prefix)
app.include_router(users.router, prefix=settings.api_v1_prefix)
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": settings.app_name}
六、开发痛点与报错避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
422 Unprocessable Entity |
请求体不符合 Pydantic schema | 查看 detail 字段中的字段校验错误信息 |
| 异步路由中执行同步DB操作 | 阻塞事件循环 | 使用 run_in_executor 或改用异步ORM(如 encode/databases) |
| CORS 跨域报错 | 未正确配置 allow_origins | 确保前端域名在 allow_origins 列表中 |
| SQLAlchemy 懒加载报错 | Session 已关闭后访问关联对象 | 在查询时 joinedload,或返回前完成所有数据访问 |
七、全文总结
FastAPI 开发 RESTful API 的核心四层架构:
- Router层:定义接口路径、HTTP方法、请求/响应模型
- Schema层:Pydantic 数据校验与序列化
- Service层:业务逻辑处理
- Model层:SQLAlchemy ORM 数据库操作
八、技术进阶展望
- 学习
Alembic实现数据库迁移管理 - 探索
SQLModel------ FastAPI 作者的 ORM + Schema 合一方案 - 研究
FastAPI + Celery + Redis实现异步任务队列