FastAPI + SQLite:从基础CRUD到安全并发的实战指南
你有没有经历过项目越跑越慢,或者因为一个小漏洞导致数据泄露的恐慌时刻?
试想一下,一个FastAPI项目,简单的内部工具,初期运行飞快。但当用户量从10个增加到100个时,响应时间却增加了近10倍,还差点因为一个未经验证的API参数导致全表数据被意外导出。其实,很多教程只教我们“跑起来”,却没教我们“跑得稳、跑得安全”。
📌 核心摘要
本文将带你超越FastAPI + SQLite的基础CRUD搭建,聚焦于安全防护(认证、授权、输入验证)与并发处理(数据库连接池、异步优化)两大实战痛点。你会获得一套可直接复用的项目骨架,并理解其背后的设计逻辑,确保你的应用在业务增长时依然稳健。
🚀 主要内容脉络
🎯 1. 快速搭建:5分钟创建一个带CRUD的FastAPI应用
⚡ 模型定义、数据库连接、基础API
🎯 2. 安全第一:给你的API加上“门禁”和“监控”
⚡ OAuth2密码流、JWT令牌、依赖注入保护路由
🎯 3. 应对高并发:别让数据库连接成为瓶颈
⚡ 连接池配置、异步会话管理、后台任务
🎯 4. 完整代码与避坑指南
⚡ 项目结构、关键配置、常见陷阱
🔧 第一部分:为什么是FastAPI + SQLite?
FastAPI就像一个高效、现代的餐厅点餐系统,你(客户端)递上菜单(JSON请求),厨房(后端逻辑)立刻开动,快速出餐(JSON响应)。而SQLite,则是这家餐厅初期最合适的“本地仓库”——无需复杂配置,一个文件搞定所有库存,对于中小型应用或原型开发来说,轻量且完全够用。
但问题来了:当顾客(并发请求)暴增,仓库管理员(数据库连接)手忙脚乱;或者有人冒充服务员(未授权访问)进入后厨。我们今天就要解决这些问题。
🛡️ 第二部分:从基础CRUD到安全堡垒
1. 搭建基础框架
首先,安装必备工具包:
pip install fastapi uvicorn sqlalchemy databases[aiosqlite] python-jose[cryptography] passlib[bcrypt]
接下来,我们定义数据模型并建立连接。SQLAlchemy的ORM让我们能用Python类操作数据库:
# models.py
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
DATABASE_URL = "sqlite:///./test.db"
# 注意:`check_same_thread=False` 仅用于SQLite简化示例,生产环境需用更安全的方式
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
Base = declarative_base()
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String, nullable=True)
# 创建表
Base.metadata.create_all(bind=engine)
然后,编写FastAPI的核心CRUD接口:
# main.py (基础版)
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import models
from .database import SessionLocal, engine
app = FastAPI()
# 依赖项:获取数据库会话
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/items/")
def create_item(name: str, description: str = None, db: Session = Depends(get_db)):
db_item = models.Item(name=name, description=description)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.get("/items/{item_id}")
def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
# ... 更新和删除接口类似
2. 构筑安全防线 🎯
警告:以下安全设置是生产应用的基石,切勿跳过。
把API直接暴露在网上,就像把家钥匙放在门垫下。我们需要认证(你是谁)和授权(你能干什么)。我们采用OAuth2密码流(业界标准)与JWT(JSON Web Tokens)组合。
第一步,处理密码。永远不要明文存储密码!
# security.py
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
第二步,创建和验证JWT令牌。
# auth.py
from jose import JWTError, jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key-change-this-in-production" # 🔴 生产环境必须用强密钥且从环境变量读取!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
credentials_exception = HTTPException(status_code=401, detail="无效凭证")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(models.User).filter(models.User.username == username).first()
if user is None:
raise credentials_exception
return user
第三步,用依赖项保护路由。现在,只有携带有效令牌的请求才能创建项目:
@app.post("/secure-items/")
def create_secure_item(
item_data: schemas.ItemCreate,
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user) # 🔒 关键的保护层
):
# 现在可以安全地创建,因为用户已通过认证
...
⚡ 第三部分:驯服并发——连接池与异步
当100个顾客同时点餐,只有1个服务员(数据库连接)会怎样?拥堵!SQLite在默认设置下对并发的支持较弱,但通过正确配置连接池,可以极大改善。
核心:使用databases库和异步SQLAlchemy。
# database_async.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# 使用aiosqlite驱动
ASYNC_DATABASE_URL = "sqlite+aiosqlite:///./test_async.db"
async_engine = create_async_engine(ASYNC_DATABASE_URL, pool_pre_ping=True, pool_size=10, max_overflow=20)
AsyncSessionLocal = sessionmaker(
bind=async_engine,
class_=AsyncSession,
expire_on_commit=False
)
async def get_async_db():
async with AsyncSessionLocal() as session:
yield session
# 在FastAPI路由中使用
@app.post("/async-items/", response_model=schemas.Item)
async def create_async_item(
item: schemas.ItemCreate,
db: AsyncSession = Depends(get_async_db)
):
db_item = models.Item(**item.dict())
db.add(db_item)
await db.commit()
await db.refresh(db_item)
return db_item
关键参数解释:
- pool_size=10:保持10个常开连接,随时待命。
- max_overflow=20:允许在繁忙时临时创建最多20个额外连接。
- pool_pre_ping=True:自动检查连接是否存活,避免使用已断开的连接。
重要:对于发送邮件、处理大文件等耗时但非数据库密集型操作,务必使用BackgroundTasks,避免阻塞主线程,影响API响应速度。
🧩 第四部分:整合与避坑指南
项目结构建议
your_project/
├── main.py # FastAPI应用创建和路由聚合
├── models.py # SQLAlchemy数据模型
├── schemas.py # Pydantic请求/响应模型(用于数据验证)
├── crud.py # 核心的数据库操作函数
├── database.py # 数据库引擎和会话工厂(同步/异步)
├── auth.py # 认证、令牌相关函数
├── security.py # 密码哈希函数
└── dependencies.py # 可重用的FastAPI依赖项
一个强化版的主文件示例
# main.py 精简示例
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from . import models, schemas, crud, auth
from .database import AsyncSessionLocal, get_async_db
app = FastAPI(title="My Secure API")
# 添加CORS中间件(根据需求配置)
app.add_middleware(CORSMiddleware, allow_origins=["*"]) # 生产环境应指定具体域名
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_async_db)):
user = await authenticate_user(form_data.username, form_data.password, db)
if not user:
raise HTTPException(status_code=400, detail="用户名或密码错误")
access_token = auth.create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me/")
async def read_users_me(current_user: models.User = Depends(auth.get_current_user)):
return current_user
# 受保护的、异步的、带后台任务的路由示例
@app.post("/items-with-notify/")
async def create_item_notify(
item: schemas.ItemCreate,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_async_db),
current_user: models.User = Depends(auth.get_current_user)
):
db_item = await crud.create_user_item(db=db, item=item, user_id=current_user.id)
# 假设有个发送通知的函数
background_tasks.add_task(send_notification, f"新项目 {item.name} 已创建!")
return db_item
必须牢记的避坑点
1️⃣ SQLite适用于开发与轻量生产。 用户量巨大或写入频繁时,考虑PostgreSQL或MySQL。
2️⃣ SECRET_KEY是生命线。 必须通过环境变量传入,且定期更换。
3️⃣ 连接池参数需按压调整。 pool_size和max_overflow不是越大越好,根据实际负载测试找到甜蜜点。
4️⃣ 善用异步(async/await)。 在I/O等待时释放CPU,但确保整个调用链(数据库驱动、HTTP客户端)都支持异步。
5️⃣ 验证所有输入。 始终使用Pydantic模型验证请求数据,这是防止注入和错误数据的第一道防线。
---写在最后---
希望这份总结能帮你避开一些坑。如果觉得有用,不妨点个 赞👍 或 收藏⭐ 标记一下,方便随时回顾。也欢迎关注我,后续为你带来更多类似的实战解析。有任何疑问或想法,我们评论区见,一起交流开发中的各种心得与问题。
浙公网安备 33010602011771号