174 lines
8.4 KiB
Python
174 lines
8.4 KiB
Python
"""
|
||
数据库 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="创建时间") |