Initial commit: MyBlog full stack blog
This commit is contained in:
@@ -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="创建时间")
|
||||
Reference in New Issue
Block a user