fastapi 博客系统模型分析
博客系统模型分析
依赖版本要求
本项目使用的主要依赖版本:
# requirements.txt
sqlalchemy>=2.0.0
pydantic>=2.0.0
fastapi>=0.100.0
SQLAlchemy Base 与 Pydantic BaseModel 说明
SQLAlchemy Base
-
用途:
- SQLAlchemy ORM的基类,用于定义数据库模型
- 负责数据库表的映射和关系定义
- 处理数据库层面的操作(CRUD)
-
特点:
- 继承自
declarative_base()
- 定义数据库表结构和关系
- 支持数据库迁移
- 处理数据库事务
- 管理数据库连接
- 继承自
-
示例:
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class UserModel(Base):
__tablename__ = "users"
# 数据库字段定义
Pydantic BaseModel
-
用途:
- 数据验证和序列化
- API请求和响应模型定义
- 数据模式文档生成
-
特点:
- 运行时数据验证
- 类型注解支持
- 自动生成JSON Schema
- 支持复杂的数据转换
- 提供错误处理机制
-
示例:
from pydantic import BaseModel
class UserCreate(BaseModel):
username: str
email: str
# API请求数据验证
两者的区别和联系
-
职责不同:
SQLAlchemy Base
: 负责数据库层面的对象关系映射Pydantic BaseModel
: 负责API层面的数据验证和序列化
-
使用场景:
SQLAlchemy Base
: 数据库操作、表结构定义Pydantic BaseModel
: API接口定义、请求响应验证
-
数据流转:
# API请求数据 -> Pydantic模型验证 -> SQLAlchemy模型存储 -> 数据库
# 数据库数据 -> SQLAlchemy模型查询 -> Pydantic模型序列化 -> API响应
- 配合使用:
# Pydantic模型
class UserCreate(BaseModel):
username: str
email: str
# SQLAlchemy模型
class UserModel(Base):
__tablename__ = "users"
username: Mapped[str]
email: Mapped[str]
# 在API中使用
@router.post("/users/")
async def create_user(user: UserCreate, db: Session):
db_user = UserModel(**user.dict()) # Pydantic -> SQLAlchemy
db.add(db_user)
db.commit()
return user # SQLAlchemy -> Pydantic
核心模型
1. 用户模型 (UserModel)
users
├── id (主键)
├── username (唯一)
├── email (唯一)
├── hashed_password (密码哈希)
├── is_active (是否激活)
├── is_admin (是否管理员)
└── created_at (创建时间)
模型实现:
class UserModel(Base):
"""用户表SQLAlchemy模型"""
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# 关联关系
posts = relationship("PostModel", back_populates="user")
images = relationship("ImageModel", back_populates="user")
comments = relationship("CommentModel", back_populates="author")
class UserCreate(BaseModel):
"""用户创建Pydantic模型"""
username: str = Field(..., description="用户名")
email: EmailStr = Field(..., description="邮箱地址")
password: constr(min_length=6, max_length=6, pattern=r'^\d{6}$') = Field(
...,
description="密码必须是6位数字"
)
class UserRead(BaseModel):
"""用户读取Pydantic模型"""
id: int
username: str
email: EmailStr
is_active: bool
is_admin: bool
created_at: datetime
class Config:
from_attributes = True
关联关系:
- 与文章模型(PostModel)的一对多关系 (user.posts)
- 与图片模型(ImageModel)的一对多关系 (user.images)
- 与评论模型(CommentModel)的一对多关系 (user.comments)
2. 文章模型 (PostModel)
posts
├── id (主键)
├── title (标题)
├── raw_content (原始内容)
├── html_content (HTML内容)
├── slug (URL别名,唯一)
├── author_id (作者ID,外键 -> users.id)
├── category_id (分类ID,外键 -> categories.id)
├── created_at (创建时间)
└── updated_at (更新时间)
模型实现:
class PostModel(Base):
"""文章表SQLAlchemy模型"""
__tablename__ = "posts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
raw_content: Mapped[str] = mapped_column(Text, nullable=False)
html_content: Mapped[str] = mapped_column(Text, nullable=True)
slug: Mapped[str] = mapped_column(String(200), unique=True, nullable=True)
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=True)
category_id: Mapped[int] = mapped_column(Integer, ForeignKey("categories.id"), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("UserModel", back_populates="posts")
category = relationship("CategoryModel", back_populates="posts")
tags = relationship("TagModel", secondary="post_tags", back_populates="posts")
images = relationship("ImageModel", secondary="post_images", back_populates="posts")
comments = relationship("CommentModel", back_populates="post")
class PostCreate(BaseModel):
"""文章创建Pydantic模型"""
title: str
raw_content: str
html_content: str | None = None
slug: str | None = None
category_id: int | None = None
class PostRead(BaseModel):
"""文章读取Pydantic模型"""
id: int
title: str
raw_content: str
html_content: str | None = None
slug: str | None = None
author_id: int
category_id: int | None = None
created_at: datetime
updated_at: datetime
user: UserRead | None = None
category: CategoryRead | None = None
class Config:
from_attributes = True
关联关系:
- 与用户模型(UserModel)的多对一关系 (post.user)
- 与分类模型(CategoryModel)的多对一关系 (post.category)
- 通过post_tags与标签模型(TagModel)的多对多关系
- 通过post_images与图片模型(ImageModel)的多对多关系
- 与评论模型(CommentModel)的一对多关系 (post.comments)
3. 分类模型 (CategoryModel)
categories
├── id (主键)
├── name (分类名,唯一)
└── description (描述)
模型实现:
class CategoryModel(Base):
"""分类表SQLAlchemy模型"""
__tablename__ = "categories"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
description: Mapped[str] = mapped_column(String(200), nullable=True)
# 关联关系
posts = relationship("PostModel", back_populates="category")
class CategoryCreate(BaseModel):
"""分类创建Pydantic模型"""
name: str
description: str | None = None
class CategoryRead(BaseModel):
"""分类读取Pydantic模型"""
id: int
name: str
description: str | None = None
class Config:
from_attributes = True
关联关系:
- 与文章模型(PostModel)的一对多关系 (category.posts)
4. 标签模型 (TagModel)
tags
├── id (主键)
└── name (标签名,唯一)
模型实现:
class TagModel(Base):
"""标签表SQLAlchemy模型"""
__tablename__ = "tags"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
# 关联关系
posts = relationship("PostModel", secondary="post_tags", back_populates="tags")
class TagCreate(BaseModel):
"""标签创建Pydantic模型"""
name: str
class TagRead(BaseModel):
"""标签读取Pydantic模型"""
id: int
name: str
class Config:
from_attributes = True
关联关系:
- 通过post_tags与文章模型(PostModel)的多对多关系
5. 图片模型 (ImageModel)
images
├── id (主键)
├── filename (文件名)
├── original_name (原始文件名)
├── user_id (用户ID,外键 -> users.id)
└── upload_time (上传时间)
模型实现:
class ImageModel(Base):
"""图片表SQLAlchemy模型"""
__tablename__ = "images"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
filename: Mapped[str] = mapped_column(String(255), nullable=False)
original_name: Mapped[str] = mapped_column(String(255), nullable=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=True)
upload_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# 关联关系
user = relationship("UserModel", back_populates="images")
posts = relationship("PostModel", secondary="post_images", back_populates="images")
class ImageCreate(BaseModel):
"""图片创建Pydantic模型"""
filename: str
original_name: str | None = None
user_id: int | None = None
class ImageRead(BaseModel):
"""图片读取Pydantic模型"""
id: int
filename: str
original_name: str | None = None
user_id: int | None = None
upload_time: datetime
class Config:
from_attributes = True
关联关系:
- 与用户模型(UserModel)的多对一关系 (image.user)
- 通过post_images与文章模型(PostModel)的多对多关系
6. 评论模型 (CommentModel)
comments
├── id (主键)
├── post_id (文章ID,外键 -> posts.id)
├── author_id (作者ID,外键 -> users.id)
├── content (评论内容)
└── created_at (创建时间)
模型实现:
class CommentModel(Base):
"""评论表SQLAlchemy模型"""
__tablename__ = "comments"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
post_id: Mapped[int] = mapped_column(Integer, ForeignKey("posts.id"))
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"))
content: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# 关联关系
post = relationship("PostModel", back_populates="comments")
author = relationship("UserModel", back_populates="comments")
class CommentCreate(BaseModel):
"""评论创建Pydantic模型"""
post_id: int
content: str
class CommentRead(BaseModel):
"""评论读取Pydantic模型"""
id: int
post_id: int
author_id: int
content: str
created_at: datetime
class Config:
from_attributes = True
关联关系:
- 与文章模型(PostModel)的多对一关系 (comment.post)
- 与用户模型(UserModel)的多对一关系 (comment.author)
7. 网站配置模型 (WebConfigModel)
web_config
├── id (主键)
├── key (配置键,唯一)
├── value (配置值)
├── description (配置描述)
├── created_at (创建时间)
└── updated_at (更新时间)
模型实现:
class WebConfigModel(Base):
"""网站配置表SQLAlchemy模型"""
__tablename__ = "web_config"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
key: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="配置键")
value: Mapped[str] = mapped_column(Text, nullable=True, comment="配置值")
description: Mapped[str] = mapped_column(String(200), nullable=True, comment="配置描述")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(
DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
comment="更新时间"
)
class WebConfigCreate(BaseModel):
"""网站配置创建Pydantic模型"""
key: str
value: str | None = None
description: str | None = None
class WebConfigRead(BaseModel):
"""网站配置读取Pydantic模型"""
id: int
key: str
value: str | None = None
description: str | None = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
无其他模型关联关系
关联表
1. 文章标签关联表 (PostTagModel)
post_tags
├── post_id (主键,外键 -> posts.id)
└── tag_id (主键,外键 -> tags.id)
模型实现:
class PostTagModel(Base):
"""文章标签关联表SQLAlchemy模型"""
__tablename__ = "post_tags"
post_id: Mapped[int] = mapped_column(Integer, ForeignKey("posts.id", ondelete="CASCADE"), primary_key=True)
tag_id: Mapped[int] = mapped_column(Integer, ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True)
连接文章模型(PostModel)和标签模型(TagModel)的多对多关系
2. 文章图片关联表 (PostImageModel)
post_images
├── post_id (主键,外键 -> posts.id)
└── image_id (主键,外键 -> images.id)
模型实现:
class PostImageModel(Base):
"""文章图片关联表SQLAlchemy模型"""
__tablename__ = "post_images"
post_id: Mapped[int] = mapped_column(Integer, ForeignKey("posts.id", ondelete="CASCADE"), primary_key=True)
image_id: Mapped[int] = mapped_column(Integer, ForeignKey("images.id", ondelete="CASCADE"), primary_key=True)
连接文章模型(PostModel)和图片模型(ImageModel)的多对多关系
主要功能
-
用户管理
- 用户认证和授权
- 管理员/普通用户区分
- 用户活动跟踪
-
内容管理
- 支持HTML内容的博客文章
- 文章分类和标签组织
- 图片上传和管理
- 评论系统
-
网站配置
- 通过WebConfigModel实现动态网站设置
- 可配置的站点参数
数据库关系概览
用户 ─┬─── 文章 ───┬─── 分类
│ │
│ ├─── 标签 (多对多)
│ │
├─── 图片 ─┘
│
└─── 评论
安全特性
- 用户账户密码哈希处理
- 用户活动状态控制
- 管理员权限控制
- 关键字段唯一性约束
数据验证
每个模型都有对应的Pydantic模型用于:
- 创建 (如 UserCreate, PostCreate)
- 读取 (如 UserRead, PostRead)
- 更新 (适用时)
这确保了API层面的类型安全和数据验证。
模型使用示例
1. 基础依赖导入
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import select, and_, or_
from typing import List, Optional
from datetime import datetime
from dao.database import get_db
from models import UserModel, PostModel, CategoryModel, TagModel, CommentModel
2. 用户相关操作
# 创建用户
async def create_user(db: Session, user_create: UserCreate) -> UserModel:
db_user = UserModel(
username=user_create.username,
email=user_create.email,
hashed_password=get_password_hash(user_create.password)
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
# 获取用户信息
async def get_user(db: Session, user_id: int) -> Optional[UserModel]:
return db.query(UserModel).filter(UserModel.id == user_id).first()
# 用户登录验证
async def authenticate_user(db: Session, username: str, password: str) -> Optional[UserModel]:
user = db.query(UserModel).filter(UserModel.username == username).first()
if not user or not verify_password(password, user.hashed_password):
return None
return user
3. 文章相关操作
# 创建文章
async def create_post(
db: Session,
post_create: PostCreate,
author_id: int,
tags: List[int] = None
) -> PostModel:
db_post = PostModel(
title=post_create.title,
raw_content=post_create.raw_content,
html_content=post_create.html_content,
slug=post_create.slug,
category_id=post_create.category_id,
author_id=author_id
)
# 添加标签
if tags:
db_tags = db.query(TagModel).filter(TagModel.id.in_(tags)).all()
db_post.tags.extend(db_tags)
db.add(db_post)
db.commit()
db.refresh(db_post)
return db_post
# 获取文章列表(带分页和过滤)
async def get_posts(
db: Session,
skip: int = 0,
limit: int = 10,
category_id: Optional[int] = None,
tag_id: Optional[int] = None,
author_id: Optional[int] = None
) -> List[PostModel]:
query = select(PostModel)
# 添加过滤条件
filters = []
if category_id:
filters.append(PostModel.category_id == category_id)
if author_id:
filters.append(PostModel.author_id == author_id)
if tag_id:
query = query.join(PostModel.tags).filter(TagModel.id == tag_id)
if filters:
query = query.filter(and_(*filters))
# 添加排序和分页
query = query.order_by(PostModel.created_at.desc())
query = query.offset(skip).limit(limit)
return db.scalars(query).all()
# 更新文章
async def update_post(
db: Session,
post_id: int,
post_update: PostCreate,
current_user_id: int
) -> Optional[PostModel]:
db_post = db.query(PostModel).filter(
PostModel.id == post_id,
PostModel.author_id == current_user_id
).first()
if not db_post:
return None
for key, value in post_update.dict(exclude_unset=True).items():
setattr(db_post, key, value)
db.commit()
db.refresh(db_post)
return db_post
4. 评论相关操作
# 添加评论
async def create_comment(
db: Session,
comment_create: CommentCreate,
author_id: int
) -> CommentModel:
db_comment = CommentModel(
post_id=comment_create.post_id,
author_id=author_id,
content=comment_create.content
)
db.add(db_comment)
db.commit()
db.refresh(db_comment)
return db_comment
# 获取文章评论
async def get_post_comments(
db: Session,
post_id: int,
skip: int = 0,
limit: int = 10
) -> List[CommentModel]:
return db.query(CommentModel)\
.filter(CommentModel.post_id == post_id)\
.order_by(CommentModel.created_at.desc())\
.offset(skip)\
.limit(limit)\
.all()
5. 网站配置操作
# 获取所有配置
async def get_all_configs(db: Session) -> dict:
configs = db.query(WebConfigModel).all()
return {config.key: config.value for config in configs}
# 更新配置
async def update_config(
db: Session,
key: str,
value: str,
current_user: UserModel
) -> WebConfigModel:
if not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only administrators can modify configurations"
)
config = db.query(WebConfigModel).filter(WebConfigModel.key == key).first()
if not config:
config = WebConfigModel(key=key)
config.value = value
db.add(config)
db.commit()
db.refresh(config)
return config
6. FastAPI路由示例
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
router = APIRouter()
# 获取文章列表
@router.get("/posts/", response_model=List[PostRead])
async def read_posts(
skip: int = 0,
limit: int = 10,
category_id: Optional[int] = None,
tag_id: Optional[int] = None,
db: Session = Depends(get_db)
):
posts = await get_posts(db, skip, limit, category_id, tag_id)
return posts
# 创建新文章
@router.post("/posts/", response_model=PostRead)
async def create_new_post(
post: PostCreate,
current_user: UserModel = Depends(get_current_user),
db: Session = Depends(get_db)
):
return await create_post(db, post, current_user.id)
# 添加评论
@router.post("/comments/", response_model=CommentRead)
async def create_new_comment(
comment: CommentCreate,
current_user: UserModel = Depends(get_current_user),
db: Session = Depends(get_db)
):
return await create_comment(db, comment, current_user.id)
7. 前端调用示例
// 获取文章列表
async function fetchPosts(page = 1, category = null, tag = null) {
const limit = 10;
const skip = (page - 1) * limit;
let url = `/api/posts/?skip=${skip}&limit=${limit}`;
if (category) url += `&category_id=${category}`;
if (tag) url += `&tag_id=${tag}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch posts');
const posts = await response.json();
return posts;
} catch (error) {
console.error('Error fetching posts:', error);
throw error;
}
}
// 创建新文章
async function createPost(postData) {
try {
const response = await fetch('/api/posts/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(postData)
});
if (!response.ok) throw new Error('Failed to create post');
return await response.json();
} catch (error) {
console.error('Error creating post:', error);
throw error;
}
}
// 添加评论
async function addComment(postId, content) {
try {
const response = await fetch('/api/comments/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
post_id: postId,
content: content
})
});
if (!response.ok) throw new Error('Failed to add comment');
return await response.json();
} catch (error) {
console.error('Error adding comment:', error);
throw error;
}
}