Files

174 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
数据库 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="创建时间")