Python后端开发实战:FastAPI构建高性能RESTful API完整指南

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 的核心四层架构:

  1. Router层:定义接口路径、HTTP方法、请求/响应模型
  2. Schema层:Pydantic 数据校验与序列化
  3. Service层:业务逻辑处理
  4. Model层:SQLAlchemy ORM 数据库操作

八、技术进阶展望

  • 学习 Alembic 实现数据库迁移管理
  • 探索 SQLModel ------ FastAPI 作者的 ORM + Schema 合一方案
  • 研究 FastAPI + Celery + Redis 实现异步任务队列

参考文献

  1. FastAPI 官方文档
  2. SQLAlchemy 官方文档
  3. Pydantic v2 官方文档
  4. python-jose JWT 库
  5. FastAPI 最佳实践 GitHub
相关推荐
PAK向日葵2 小时前
我用 C++ 写了一个轻量级 Python 虚拟机,刚刚开源
c++·python·开源
财经资讯数据_灵砚智能3 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年5月26日
大数据·人工智能·python·信息可视化·自然语言处理·ai编程·灵砚智能
我材不敲代码4 小时前
Python基础:列表详解、增删改查及常用高阶操作
开发语言·windows·python
li星野4 小时前
FastAPI 入门:异步与同步端点的性能差异与并发测试解析
fastapi
AI玫瑰助手4 小时前
Python运算符:成员运算符(in/not in)的使用场景
开发语言·python·信息可视化
Warson_L5 小时前
python - class 入门
python
水木流年追梦5 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt
ZHANG8023ZHEN5 小时前
Diffusion 数学推理
人工智能·python·机器学习
海天一色y5 小时前
SGLang 本地部署 Qwen3-8B 大模型实战指南
python·sglang
代码帮6 小时前
面试题 - GIL全局解释器锁 :为什么Python多线程不能利用多核?GIL对I/O密集和CPU密集任务的影响?如何绕过GIL(多进程、C扩展)
python·面试