"""SQLAlchemy 数据模型""" from datetime import datetime from sqlalchemy import ( create_engine, Column, Integer, String, Text, DateTime, LargeBinary, Enum as SAEnum, ForeignKey, Table ) from sqlalchemy.orm import declarative_base, relationship, sessionmaker from config import get_db_url engine = create_engine(get_db_url(), echo=False, pool_pre_ping=True) SessionLocal = sessionmaker(bind=engine, autocommit=False) Base = declarative_base() def get_db(): """获取数据库会话(FastAPI 依赖注入)""" db = SessionLocal() try: yield db finally: db.close() # ===== 文件夹 ===== class Folder(Base): """文件夹表 — 笔记和网站共用""" __tablename__ = "folders" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(100), nullable=False) type = Column(SAEnum("note", "website", name="folder_type"), nullable=False, default="note") parent_id = Column(Integer, ForeignKey("folders.id"), nullable=True) sort_order = Column(Integer, default=0) created_by = Column(String(100), nullable=False, default="admin") created_at = Column(DateTime, default=datetime.utcnow) children = relationship("Folder", backref="parent", remote_side=[id]) notes = relationship("Note", back_populates="folder", cascade="all, delete-orphan") websites = relationship("Website", back_populates="folder", cascade="all, delete-orphan") # ===== 笔记 ===== class Note(Base): """笔记表""" __tablename__ = "notes" id = Column(Integer, primary_key=True, autoincrement=True) title = Column(String(500), nullable=False, default="无标题") content = Column(Text, nullable=True) # Tiptap 输出的 HTML folder_id = Column(Integer, ForeignKey("folders.id"), nullable=True) created_by = Column(String(100), nullable=False, default="admin") updated_by = Column(String(100), nullable=False, default="admin") share_token = Column(String(64), nullable=True, unique=True) share_expires_at = Column(DateTime, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) folder = relationship("Folder", back_populates="notes") tags = relationship("Tag", secondary="note_tags", back_populates="notes") # ===== 笔记-标签关联 ===== note_tags = Table( "note_tags", Base.metadata, Column("note_id", Integer, ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True), Column("tag_id", Integer, ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True), ) # ===== 标签 ===== class Tag(Base): """标签表""" __tablename__ = "tags" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(50), nullable=False, unique=True) color = Column(String(20), default="#666666") notes = relationship("Note", secondary="note_tags", back_populates="tags") # ===== 网站 ===== class Website(Base): """网站表""" __tablename__ = "websites" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(200), nullable=False) url = Column(String(500), nullable=False) folder_id = Column(Integer, ForeignKey("folders.id"), nullable=True) created_by = Column(String(100), nullable=False, default="admin") updated_by = Column(String(100), nullable=False, default="admin") created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) folder = relationship("Folder", back_populates="websites") accounts = relationship("Account", back_populates="website", cascade="all, delete-orphan", order_by="Account.sort_order") # ===== 账号 ===== class Account(Base): """账号表 — 存储网站登录凭据""" __tablename__ = "accounts" id = Column(Integer, primary_key=True, autoincrement=True) website_id = Column(Integer, ForeignKey("websites.id", ondelete="CASCADE"), nullable=False) username = Column(String(500), nullable=False) password = Column(String(500), nullable=False) # 明文存储,后续加密 remark = Column(Text, nullable=True) created_by = Column(String(100), nullable=False, default="admin") updated_by = Column(String(100), nullable=False, default="admin") sort_order = Column(Integer, default=0) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) website = relationship("Website", back_populates="accounts") # ===== 上传文件 ===== class Upload(Base): """文件上传表 — 图片存储(新文件存文件服务,旧文件兼容 DB blob)""" __tablename__ = "uploads" id = Column(Integer, primary_key=True, autoincrement=True) filename = Column(String(255), nullable=False) content_type = Column(String(100), nullable=False) data = Column(LargeBinary(length=2**32-1), nullable=True) # 旧图片 LONGBLOB(兼容),新文件为 NULL file_id = Column(String(64), nullable=True) # 文件服务返回的 fileId(新文件) size = Column(Integer, default=0) created_by = Column(String(100), nullable=False, default="admin") created_at = Column(DateTime, default=datetime.utcnow)