Initial commit: MyBlog full stack blog

This commit is contained in:
2026-08-22 22:28:41 +08:00
commit c61193f2af
64 changed files with 7290 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
"""
数据库 ORM 模型模块。
定义全局架构中的五个核心模型,以及邮箱验证码模型:
- User:用户(visitor / friend / blogger
- Article:文章(public / friend 两种可见性)
- Comment:评论(支持回复,parent_id 指向父评论)
- Like:点赞
- Friend:好友申请
- EmailCode:邮箱验证码(一次性、带过期时间)
"""
from datetime import datetime, timezone
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import relationship
from .database import Base
# ---------- 常量定义 ----------
# 用户角色(全局架构:visitor / friend / blogger
ROLE_VISITOR = "visitor"
ROLE_FRIEND = "friend"
ROLE_BLOGGER = "blogger"
# 文章可见性(全局架构:public / friend
VISIBILITY_PUBLIC = "public"
VISIBILITY_FRIEND = "friend"
# 文章分区(life 生活 / study 学习)
ARTICLE_CATEGORY_LIFE = "life"
ARTICLE_CATEGORY_STUDY = "study"
# 项目类型(L0 静态托管 / GitHub 外链)
PROJECT_TYPE_STATIC = "static"
PROJECT_TYPE_LINK = "link"
# 好友申请状态
FRIEND_STATUS_PENDING = "pending"
FRIEND_STATUS_ACCEPTED = "accepted"
FRIEND_STATUS_REJECTED = "rejected"
# 邮箱验证码用途
PURPOSE_REGISTER = "register"
PURPOSE_RESET = "reset"
def utcnow() -> datetime:
"""返回当前 UTC 时间(无时区),作为各模型时间字段的默认值。"""
return datetime.now(timezone.utc).replace(tzinfo=None)
class User(Base):
"""用户模型:对应权限系统的 visitor / friend / blogger 三种角色。"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True, comment="用户 ID")
email = Column(String(255), unique=True, index=True, nullable=False, comment="邮箱,唯一")
username = Column(String(50), unique=True, index=True, nullable=False, comment="用户名,唯一")
password_hash = Column(String(255), nullable=False, comment="bcrypt 密码哈希,禁止保存明文")
role = Column(String(20), nullable=False, default=ROLE_VISITOR, comment="角色")
avatar = Column(String(500), nullable=True, comment="头像图片路径(uploads/avatar/")
avatar_updated_time = Column(DateTime, nullable=True, comment="头像最近一次更换时间(配合更换频率限制)")
bio = Column(Text, nullable=True, comment="个人简介(我的简介页面展示)")
token_version = Column(Integer, nullable=False, default=0, comment="令牌版本号(重置密码时自增,使旧 JWT 立即失效)")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="创建时间")
# 关联关系:删除用户时级联删除其文章、评论、点赞与好友记录
articles = relationship("Article", back_populates="author", cascade="all, delete-orphan")
comments = relationship("Comment", back_populates="user", cascade="all, delete-orphan")
likes = relationship("Like", back_populates="user", cascade="all, delete-orphan")
friends = relationship("Friend", back_populates="user", cascade="all, delete-orphan")
class Article(Base):
"""文章模型:Markdown 正文,支持 public / friend 两种可见性。"""
__tablename__ = "articles"
id = Column(Integer, primary_key=True, index=True, comment="文章 ID")
title = Column(String(200), nullable=False, comment="文章标题")
content = Column(Text, nullable=False, comment="Markdown 格式正文")
cover = Column(String(500), nullable=True, comment="文章封面图片路径(uploads/article/")
visibility = Column(String(20), nullable=False, default=VISIBILITY_PUBLIC, comment="可见性")
category = Column(String(30), nullable=False, default=ARTICLE_CATEGORY_LIFE, index=True, comment="分区:life 生活 / study 学习")
author_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="作者 ID")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="创建时间")
author = relationship("User", back_populates="articles")
comments = relationship("Comment", back_populates="article", cascade="all, delete-orphan")
likes = relationship("Like", back_populates="article", cascade="all, delete-orphan")
class Project(Base):
"""项目模型:L0 静态托管(static,自动在线演示)或 GitHub 外链(link)。"""
__tablename__ = "projects"
id = Column(Integer, primary_key=True, index=True, comment="项目 ID")
name = Column(String(100), nullable=False, comment="项目名称")
description = Column(Text, nullable=True, comment="项目简介")
tech = Column(String(500), nullable=True, comment="技术标签(逗号分隔)")
project_type = Column(String(20), nullable=False, default=PROJECT_TYPE_STATIC, comment="项目类型:static 静态托管 / link 外链")
visibility = Column(String(20), nullable=False, default=VISIBILITY_PUBLIC, comment="可见性:public 公开 / friend 好友")
demo_url = Column(String(500), nullable=True, comment="在线运行地址(static 为站内演示地址,link 为 GitHub 链接)")
download_url = Column(String(500), nullable=True, comment="下载文件地址(uploads/project/")
github_url = Column(String(500), nullable=True, comment="GitHub 仓库链接(link 类型必填)")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="创建时间")
updated_time = Column(DateTime, nullable=False, default=utcnow, onupdate=utcnow, comment="更新时间")
class Comment(Base):
"""评论模型:好友及博主可对文章发表评论,支持回复(parent_id)。"""
__tablename__ = "comments"
id = Column(Integer, primary_key=True, index=True, comment="评论 ID")
article_id = Column(Integer, ForeignKey("articles.id"), nullable=False, index=True, comment="文章 ID")
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="评论者 ID")
parent_id = Column(Integer, ForeignKey("comments.id"), nullable=True, index=True, comment="父评论 ID(回复时填写,顶层评论为空)")
content = Column(Text, nullable=False, comment="评论内容")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="创建时间")
article = relationship("Article", back_populates="comments")
user = relationship("User", back_populates="comments")
parent = relationship("Comment", remote_side=[id], back_populates="replies")
replies = relationship("Comment", back_populates="parent")
class Like(Base):
"""点赞模型:同一用户对同一文章只能点赞一次。"""
__tablename__ = "likes"
id = Column(Integer, primary_key=True, index=True, comment="点赞记录 ID")
article_id = Column(Integer, ForeignKey("articles.id"), nullable=False, index=True, comment="文章 ID")
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="点赞者 ID")
# 唯一约束:防止同一用户重复点赞同一文章
__table_args__ = (
UniqueConstraint("article_id", "user_id", name="uq_like_article_user"),
)
article = relationship("Article", back_populates="likes")
user = relationship("User", back_populates="likes")
class Friend(Base):
"""好友申请模型:按已确认架构字段(id / user_id / status)实现。"""
__tablename__ = "friends"
id = Column(Integer, primary_key=True, index=True, comment="好友记录 ID")
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="申请人 ID")
status = Column(String(20), nullable=False, default=FRIEND_STATUS_PENDING, comment="申请状态")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="申请创建时间")
user = relationship("User", back_populates="friends")
class EmailCode(Base):
"""邮箱验证码模型:一次性验证码,带过期时间。"""
__tablename__ = "email_codes"
id = Column(Integer, primary_key=True, index=True, comment="验证码记录 ID")
email = Column(String(255), index=True, nullable=False, comment="目标邮箱")
code = Column(String(10), nullable=False, comment="验证码")
purpose = Column(String(30), nullable=False, comment="用途:register / reset")
expires_at = Column(DateTime, nullable=False, comment="过期时间")
used = Column(Boolean, nullable=False, default=False, comment="是否已使用")
created_time = Column(DateTime, nullable=False, default=utcnow, comment="创建时间")