FastAPI 终极实战:ORM 数据库、RESTful 设计、中间件与依赖注入

FastAPI 终极实战:ORM 数据库、RESTful 设计、中间件与依赖注入

这是 FastAPI 系列的最后一篇。你将学会:使用 SQLAlchemy ORM 操作数据库、设计符合 RESTful 规范的 API、理解 HTTP 状态码的语义、模块化路由、依赖注入复用逻辑、中间件统一处理请求、解决跨域问题、以及提供静态文件服务。最终完成一个完整的图书管理 API 项目。


目录

  1. [ORM 基础:SQLAlchemy 集成](#ORM 基础:SQLAlchemy 集成)
    • 1.1 核心组件(Engine、Base、Session)
    • 1.2 定义模型
    • 1.3 创建表
    • 1.4 增删改查操作
  2. [RESTful API 设计原则](#RESTful API 设计原则)
    • 2.1 核心原则:URL 描述资源,HTTP 方法描述操作
    • 2.2 示例:学生资源的增删改查
  3. [HTTP 状态码详解与高频对比](#HTTP 状态码详解与高频对比)
    • 3.1 2xx 成功
    • 3.2 3xx 重定向
    • 3.3 4xx 客户端错误
    • 3.4 5xx 服务器错误
    • 3.5 易混淆对比(400 vs 422、401 vs 403、307 vs 308、500 vs 503)
  4. 子路由(模块化)
  5. 依赖注入(Depends)
  6. 中间件(Middleware)
    • 6.1 作用与实现结构
    • 6.2 常见应用:日志、耗时统计、认证
    • 6.3 多中间件顺序
  7. 跨域(CORS)配置
  8. 静态资源服务
  9. [综合实战:图书管理 API(完整代码)](#综合实战:图书管理 API(完整代码))
  10. 总结与系列回顾

1. ORM 基础:SQLAlchemy 集成

SQLAlchemy 是 Python 最流行的 ORM(对象关系映射)库,它将数据库表映射为 Python 类,将行映射为对象,将列映射为属性。

1.1 核心组件

组件 说明
Engine 数据库连接的核心,管理连接池
Base 模型基类,所有模型继承它
Session 数据库操作会话,类似"工作单元"

1.2 定义模型

python 复制代码
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 创建引擎(以 SQLite 为例)
engine = create_engine("sqlite:///./test.db", echo=True)

# 模型基类
Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

1.3 创建表

python 复制代码
Base.metadata.create_all(bind=engine)

1.4 增删改查操作

python 复制代码
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()

# 新增单条
user = User(name="张三", age=20)
db.add(user)
db.commit()
db.refresh(user)

# 新增多条
db.add_all([User(name="李四", age=22), User(name="王五", age=25)])
db.commit()

# 查询
all_users = db.query(User).all()                     # 所有
adults = db.query(User).filter(User.age >= 18).all() # 条件过滤
paged = db.query(User).offset(0).limit(10).all()     # 分页

# 修改
user = db.query(User).filter(User.name == "张三").first()
if user:
    user.age = 21
    db.commit()

# 删除
db.query(User).filter(User.name == "李四").delete()
db.commit()

⚠️ 注意 :所有写操作必须执行 db.commit() 才会写入数据库。操作完成后建议关闭 session:db.close(),可使用 try/finally 确保释放。


2. RESTful API 设计原则

RESTful 是一种 API 设计规范,旨在提高可读性与可维护性。

核心原则 说明 示例
URL 描述资源 使用名词复数,避免动词 /users 而不是 /getUser
HTTP 方法描述操作 GET 查询、POST 新增、PUT 修改、DELETE 删除 GET /users/1 获取 id=1 的用户
状态码语义化 用状态码表达结果类型 200 OK, 201 Created, 404 Not Found

RESTful 示例

请求 含义
GET /students 获取所有学生
POST /students 新增一个学生
PUT /students/{id} 修改指定学生
DELETE /students/{id} 删除指定学生

3. HTTP 状态码详解与高频对比

3.1 2xx 成功

状态码 含义 使用场景
200 OK 请求成功 GET、PUT、DELETE 成功
201 Created 创建成功 POST 创建资源成功后返回,通常带 Location 头
204 No Content 成功但无返回体 DELETE 成功后常用

3.2 3xx 重定向

状态码 含义 特点
307 Temporary Redirect 临时重定向 保持请求方法不变
308 Permanent Redirect 永久重定向 保持请求方法不变

3.3 4xx 客户端错误

状态码 含义 典型原因
400 Bad Request 请求格式错误或缺少必要参数 缺少查询参数如 /search 缺少 keyword
401 Unauthorized 未登录 未提供 token 或 token 无效
403 Forbidden 已登录但无权限 普通用户尝试删除其他用户数据
404 Not Found 资源不存在 访问 /users/99999 但该 id 不存在
422 Unprocessable Entity 数据校验失败(格式正确但语义错误) 邮箱格式错误、年龄超出范围

3.4 5xx 服务器错误

状态码 含义 典型原因
500 Internal Server Error 服务器内部错误 代码抛出未捕获异常、数据库崩溃
503 Service Unavailable 服务不可用 过载、熔断、维护中

3.5 易混淆对比

对比 区分要点
400 vs 422 400:请求格式错误(如 JSON 解析失败);422:格式正确但字段值不符合业务规则
401 vs 403 401:未认证(未登录);403:已认证但无权限
307 vs 308 307 临时,308 永久;两者都保留请求方法(区别于 301/302)
500 vs 503 500 内部错误(代码 bug);503 服务不可用(过载、维护)

4. 子路由(模块化)

当项目变大时,将所有路由写在 main.py 会导致臃肿难维护。使用 APIRouter 拆分模块。

python 复制代码
# routers/students.py
from fastapi import APIRouter

router = APIRouter(prefix="/students", tags=["学生管理"])

@router.get("/")
async def get_students():
    return [{"name": "张三"}, {"name": "李四"}]

@router.post("/")
async def create_student():
    return {"msg": "创建成功"}

在主应用中引入:

python 复制代码
# main.py
from fastapi import FastAPI
from routers import students, teachers

app = FastAPI()
app.include_router(students.router)
app.include_router(teachers.router)

5. 依赖注入(Depends)

依赖注入用于抽离公共逻辑(如分页参数、认证、数据库会话)。FastAPI 的 Depends 会自动解析并注入。

python 复制代码
from fastapi import Depends

def pagination_params(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

@app.get("/items")
async def get_items(params: dict = Depends(pagination_params)):
    return params

依赖函数可以是普通函数或异步函数,甚至可以依赖其他依赖。


6. 中间件(Middleware)

中间件在每个请求前和响应后执行,可用于日志、耗时统计、权限校验、统一响应头等。

6.1 作用与实现结构

python 复制代码
from fastapi import Request
import time

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)          # 继续处理请求
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

6.2 常见应用

  • 耗时中间件:统计接口处理时间
  • 认证中间件:校验 token,支持白名单路径
  • 日志中间件:记录请求方法、路径、状态码

6.3 多中间件顺序

中间件按添加的顺序执行:请求时从外到内,响应时从内到外。例如:

python 复制代码
app.add_middleware(MiddlewareA)  # 先添加的外层
app.add_middleware(MiddlewareB)  # 后添加的内层

7. 跨域(CORS)配置

前后端分离时,前端域名与后端不同会产生跨域问题。FastAPI 内置 CORSMiddleware

python 复制代码
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],                 # 允许所有来源(生产环境应指定具体域名)
    allow_credentials=True,              # 允许携带 Cookie
    allow_methods=["*"],                 # 允许所有 HTTP 方法
    allow_headers=["*"],                 # 允许所有请求头
)

8. 静态资源服务

使用 StaticFiles 挂载静态目录(如图片、CSS、JS)。

python 复制代码
from fastapi.staticfiles import StaticFiles

app.mount("/static", StaticFiles(directory="static"), name="static")

访问 http://localhost:8000/static/logo.png 即可获取文件。

自定义缓存控制 :继承 StaticFiles 并重写 file_response 方法,添加 Cache-Control 头。


9. 综合实战:图书管理 API(完整代码)

下面结合 ORM、RESTful、子路由、依赖注入、中间件、CORS 等,实现完整的图书管理 API。

python 复制代码
# models.py
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./books.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

class Book(Base):
    __tablename__ = "books"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    author = Column(String)
    year = Column(Integer)

Base.metadata.create_all(bind=engine)

# crud.py (依赖数据库会话)
def get_book(db, book_id: int):
    return db.query(Book).filter(Book.id == book_id).first()

def get_books(db, skip: int = 0, limit: int = 10):
    return db.query(Book).offset(skip).limit(limit).all()

def create_book(db, title: str, author: str, year: int):
    book = Book(title=title, author=author, year=year)
    db.add(book)
    db.commit()
    db.refresh(book)
    return book

def delete_book(db, book_id: int):
    book = get_book(db, book_id)
    if book:
        db.delete(book)
        db.commit()
    return book

# dependencies.py
from fastapi import Depends
from .models import SessionLocal

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def pagination(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

# routers/books.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from .. import crud, dependencies
from ..models import Book

router = APIRouter(prefix="/books", tags=["图书管理"])

@router.get("/")
async def list_books(
    pagination: dict = Depends(dependencies.pagination),
    db: Session = Depends(dependencies.get_db)
):
    books = crud.get_books(db, skip=pagination["skip"], limit=pagination["limit"])
    return books

@router.post("/", status_code=201)
async def add_book(title: str, author: str, year: int, db: Session = Depends(dependencies.get_db)):
    return crud.create_book(db, title, author, year)

@router.get("/{book_id}")
async def get_book(book_id: int, db: Session = Depends(dependencies.get_db)):
    book = crud.get_book(db, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="图书不存在")
    return book

@router.delete("/{book_id}")
async def remove_book(book_id: int, db: Session = Depends(dependencies.get_db)):
    book = crud.delete_book(db, book_id)
    if not book:
        raise HTTPException(status_code=404, detail="图书不存在")
    return {"msg": "删除成功"}

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from routers import books

app = FastAPI(title="图书管理API")

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 静态文件
app.mount("/static", StaticFiles(directory="static"), name="static")

# 子路由
app.include_router(books.router)

# 根路径
@app.get("/")
def root():
    return {"message": "图书管理API已启动"}

启动后访问 /docs 即可测试所有接口。


10. 总结与系列回顾

本系列三篇文章涵盖了 FastAPI 从入门到实战的全路径:

篇目 核心内容
第一篇 HTTP 协议、路由、参数、异常、异步、ASGI、Uvicorn
第二篇 Pydantic 模型、文件上传、表单、响应模型、自定义校验
第三篇(本文) ORM、RESTful、状态码、子路由、依赖注入、中间件、CORS、静态文件

后续你可以

  • 将项目部署到云服务器(使用 Gunicorn + Uvicorn workers)
  • 添加身份认证(JWT)
  • 集成 Redis 做缓存
  • 编写单元测试
  • 使用 Docker 容器化

FastAPI 是现代 Python Web 开发的不二之选。希望这个系列能帮助你写出专业、高效的 API 服务。

相关推荐
shuair10 小时前
redis红锁Redlock
数据库·redis·bootstrap
小新同学^O^10 小时前
简单学习 --> 限流和熔断
数据库·python·学习
专注API从业者10 小时前
用 Open Claw + 淘宝商品接口,快速实现电商商品监控与智能选品(附完整代码)
大数据·前端·数据结构·数据库
码界筑梦坊10 小时前
143-基于Python的景点热度分析数据可视化分析系统
python·信息可视化·数据分析·毕业设计·fastapi
TDengine (老段)10 小时前
TDengine 虚拟表实现原理
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
吃好睡好便好10 小时前
用if…elseif…end语句输出成绩等级
开发语言·前端·javascript·数据库·学习·matlab·信息可视化
努力努力再努力wz10 小时前
【Redis入门系列】:Redis 内部编码机制与 String 深度解析:SDS 底层实现、三种编码与核心命令详解
c语言·开发语言·数据结构·数据库·c++·redis·缓存
罗超驿10 小时前
21.jdbc 学习笔记:从原理到实践的全流程梳理
java·数据库·mysql·面试
楠枬10 小时前
Redis 分布式锁
数据库·redis·分布式