二、数据处理

SQLAlchemy数据库操作
模型定义与连接建立
py
from sqlalchemy import (
create_engine,
)
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
sessionmaker,
)
# 数据库连接字符串
# 首次连接时将自动创建test.db文件
DATABASE_URL = "sqlite:///./test.db"
# 创建引擎
engine = create_engine(DATABASE_URL)
# 定义数据库服务基类
# 需要继承 DeclarativeBase
class Base(DeclarativeBase):
pass
# 定义模型
class User(Base):
# 对应数据库中的user表
__tablename__ = "user"
# 每个类属性声明了字段的数据类型
id: Mapped[int] = mapped_column(
primary_key=True,
)
name: Mapped[str]
email: Mapped[str]
# 通过引擎创建数据库表
Base.metadata.create_all(bind=engine)
# 建立数据库连接
# sessionmaker函数生成会话工厂
# autocommit与autoflush参数设为False,意味着需手动提交事务并管理数据刷新时机
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
)
CRUD
py
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session
from database import SessionLocal, User
# 确保每个请求创建新会话,并在请求结束时关闭
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
app = FastAPI()
class UserBody(BaseModel):
name: str
email: str
@app.get("/users")
# db: Session 是参数类型注解 表示db是一个数据库会话
# Depends(get_db) 是 FastAPI 的依赖注入,自动提供数据库连接
def read_users(db: Session = Depends(get_db)):
users = db.query(User).all()
return users
# 创建新用户
@app.post("/user")
def add_new_user(
user: UserBody, db: Session = Depends(get_db)
):
new_user = User(name=user.name, email=user.email)
# 将对象添加到当前会话
db.add(new_user)
# 提交事务
db.commit()
# 从数据库重新加载对象
db.refresh(new_user)
return new_user
# 读取特定用户
@app.get("/user")
def get_user(
user_id: int, db: Session = Depends(get_db)
):
user = (
db.query(User)
.filter(User.id == user_id)
.first()
)
if user is None:
raise HTTPException(
# 用户不存在则返回404
status_code=404, detail="User not found"
)
return user
# 更新用户
@app.post("/user/{user_id}")
def update_user(
user_id: int,
user: UserBody,
db: Session = Depends(get_db),
):
db_user = (
db.query(User)
.filter(User.id == user_id)
.first()
)
if db_user is None:
raise HTTPException(
status_code=404, detail="User not found"
)
db_user.name = user.name
db_user.email = user.email
db.commit()
db.refresh(db_user)
return db_user
# 删除用户
@app.delete("/user")
def delete_user(
user_id: int, db: Session = Depends(get_db)
):
db_user = (
db.query(User)
.filter(User.id == user_id)
.first()
)
if db_user is None:
raise HTTPException(
status_code=404, detail="User not found"
)
db.delete(db_user)
db.commit()
return {"detail": "User deleted"}
# 在交互式文档操作
http://localhost:8000/docs
集成MongoDB实现NoSQL数据存储
模型定义与连接建立
py
from pymongo import MongoClient
client = MongoClient()
# 等价于client = MongoClient("mongodb://localhost:27017")
database = client.mydatabase
# 定义集合 相当于对表的引用
user_collection = database["users"]
CRUD
py
from bson import ObjectId
from fastapi import FastAPI, HTTPException
from pydantic import (
BaseModel,
EmailStr,
field_validator,
)
# 导入数据库连接变量
from database import user_collection
app = FastAPI()
class Tweet(BaseModel):
content: str
hashtags: list[str]
class User(BaseModel):
name: str
# 使用Pydantic邮箱验证器
email: EmailStr
age: int
# tweets可以是一个 Tweet 列表,也可以是 None, 默认值是 None
tweets: list[Tweet] | None = None
# 自定义了一个关于年龄的验证器
@field_validator("age")
def validate_age(cls, value):
if value < 18 or value > 100:
raise ValueError(
"Age must be between 18 and 100"
)
return value
# 返回所有用户
@app.get("/users")
def read_users() -> list[User]:
return [user for user in user_collection.find()]
# 继承自 User
class UserResponse(User):
# 新增字段 id
id: str
# 创建新用户
@app.post("/user")
def create_user(user: User) -> UserResponse:
result = user_collection.insert_one(
# MongoDB 只能存储 BSON(Binary JSON)格式 不支持 Python 特有的对象类型
# 所以使用 model_dump 将 Pydantic 模型转换为 Python 字典
# exclude_none=True:排除值为 None 的字段
user.model_dump(exclude_none=True)
)
# result有两个属性:inserted_id 和 acknowledged
user_response = UserResponse(
# **user.model_dump()将原始用户数据解包为关键字参数
id=str(result.inserted_id), **user.model_dump()
)
# 等价于
# user_response = UserResponse(
# id=str(result.inserted_id),
# name=user.name, # 从解包的字典中获取
# email=user.email, # 从解包的字典中获取
# age=user.age # 从解包的字典中获取
# )
return user_response
# 读取用户数据
@app.get("/user")
def get_user(user_id: str) -> UserResponse:
db_user = user_collection.find_one(
{
# MongoDB 中 _id 存储的是 ObjectId 对象,不是字符串
# # 将用户传入的字符串ID转换为MongoDB的ObjectId对象
"_id": ObjectId(user_id)
if ObjectId.is_valid(user_id)
else None
}
)
if db_user is None:
raise HTTPException(
status_code=404, detail="User not found"
)
# 响应给客户端时需要将 ObjectId 转换为字符串格式
db_user["id"] = str(db_user["_id"])
return db_user
# FastAPI通过Pydantic模型自动处理序列化与反序列化。当端点返回Pydantic模型时,FastAPI自动将其序列化为JSON;当端点参数接收Pydantic模型时,框架将传入JSON数据反序列化为模型对象。
# 在交互式文档操作
http://localhost:8000/docs
文件上传与下载
py
import shutil
from pathlib import Path
from fastapi import (
FastAPI,
File,
HTTPException,
UploadFile,
)
from fastapi.responses import FileResponse
app = FastAPI()
# 接受上传文件并返回文件名
@app.post("/uploadfile")
async def upload_file(
file: UploadFile = File(...),
):
with open(
# 在uploads目录以写入二进制模式打开新文件
f"uploads/{file.filename}", "wb"
) as buffer:
# 使用shutil.copyfileobj将UploadFile对象内容复制到新文件
shutil.copyfileobj(file.file, buffer)
return {"filename": file.filename}
# 实现文件下载服务
@app.get(
"/downloadfile/{filename}",
# 指定返回文件响应
response_class=FileResponse,
)
async def download_file(filename: str):
# 存在性检查
if not Path(f"uploads/{filename}").exists():
raise HTTPException(
status_code=404,
detail=f"file {filename} not found",
)
return FileResponse(
path=f"uploads/{filename}", filename=filename
)
异步数据处理
!CAUTION
后续需要对此处继续进行补充
py
# main.py
import asyncio
import time
from fastapi import FastAPI
app = FastAPI()
# 创建同步阻塞端点
@app.get("/sync")
def read_sync():
# 阻塞执行:time.sleep(2) 会阻塞整个线程2秒
# 独占资源:在此期间,该线程无法处理其他请求
# 串行处理:请求必须等待前一个请求完成
time.sleep(2)
return {
"message": "同步阻塞端点"
}
# 创建异步非阻塞端点
# asyncio库支持编写非阻塞代码:在等待I/O操作完成时暂停执行,完成后从断点恢复,全程不阻塞主线程
@app.get("/async")
async def read_async():
# 非阻塞等待:await asyncio.sleep(2) 不会阻塞线程
# 协作式并发:在等待期间,线程可以处理其他请求
# 事件循环调度:等待期间释放控制权给事件循环
await asyncio.sleep(2)
return {
"message": "异步非阻塞端点"
}
py
# timing_api_calls.py 进行调用
import asyncio
import time
from contextlib import contextmanager
from multiprocessing import Process
import uvicorn
from httpx import AsyncClient
from main import app
# 定义服务器启动函数
def run_server():
uvicorn.run(app, port=8000, log_level="error")
# 通过上下文管理器启动服务器进程
# @contextmanager将普通函数转换为上下文管理器
@contextmanager
def run_server_in_process():
# 创建一个新的进程对象并指定进程要执行的对象
p = Process(target=run_server)
# 启动新进程
p.start()
time.sleep(2) # 等待服务器启动
print("服务器已在独立进程中运行")
yield
p.terminate()
# 定义并发请求函数
async def make_requests_to_the_endpoint(
n: int, path: str
):
async with AsyncClient(
base_url="http://localhost:8000"
) as client:
tasks = (
client.get(path, timeout=float("inf"))
for _ in range(n)
)
await asyncio.gather(*tasks)
# 聚合操作至主函数并输出耗时
async def main(n: int = 10):
with run_server_in_process():
begin = time.time()
await make_requests_to_the_endpoint(n, "/sync")
end = time.time()
print(
f"完成 {n} 次同步端点请求耗时: {end - begin} 秒"
)
begin = time.time()
await make_requests_to_the_endpoint(n, "/async")
end = time.time()
print(
f"完成 {n} 次异步端点请求耗时: {end - begin} 秒"
)
if __name__ == "__main__":
asyncio.run(main(n=100))
# 完成 100 次同步端点请求耗时: 6.088550090789795 秒
# 完成 100 次异步端点请求耗时: 2.0794005393981934 秒
数据保护
核心安全实践清单:
- 数据验证与清洗 :
使用Pydantic模型验证和清洗输入数据。确保数据符合预期格式与值范围,降低注入攻击或畸形数据风险。
对输出至用户或日志的数据保持警惕,对敏感信息进行脱敏或匿名化处理,防止意外泄露。 - 访问控制 :
实施强健的访问控制机制,确保用户仅能访问其权限范围内的数据。可采用基于角色的访问控制(Role-Based Access Control, RBAC)、权限校验及完善的用户认证管理(详见第4章"认证与授权"中的RBAC方案)。 - 安全通信 :
通过HTTPS加密传输中数据,防止攻击者拦截应用收发的敏感信息。 - 数据库安全 :
确保数据库安全配置:使用加密连接、避免公开暴露数据库端口,并为数据库访问实施最小权限原则(principle of least privilege)。 - 定期更新 :
保持所有依赖项(包括FastAPI及其底层库)为最新版本,防范旧版软件中已发现的漏洞。
通用数据保护清单:
- 数据存储 :
仅在必要时存储敏感数据。若无需保存信用卡号或身份证号,则坚决不存。必须存储时,确保数据加密且访问权限严格受限。 - 数据传输 :
传输敏感数据时保持高度谨慎。使用安全API,并确保所有交互的外部服务同样遵循安全最佳实践。 - 数据保留与销毁 :
制定明确的数据保留与销毁策略。数据不再需要时,通过安全方式彻底删除,确保备份或日志中不留痕迹。 - 监控与日志 :
实施监控机制检测异常访问模式或潜在入侵。但需谨慎记录日志:避免记录敏感数据,确保日志安全存储且仅限授权人员访问。