143 lines
4.3 KiB
Python
143 lines
4.3 KiB
Python
"""Document 文档模型。"""
|
||
|
||
from sqlalchemy import ForeignKey, Integer, String, Text
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
||
from app.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
|
||
|
||
|
||
class Document(UUIDPrimaryKeyMixin, TimestampMixin, Base):
|
||
"""文档模型。
|
||
|
||
技术审查 §2.2:documents 表。
|
||
"""
|
||
|
||
__tablename__ = "documents"
|
||
|
||
knowledge_base_id: Mapped[str] = mapped_column(
|
||
String(32),
|
||
ForeignKey("knowledge_bases.id"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属知识库 ID",
|
||
)
|
||
user_id: Mapped[str] = mapped_column(
|
||
String(32),
|
||
ForeignKey("users.id"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所有者用户 ID (冗余,便于隔离校验)",
|
||
)
|
||
category_id: Mapped[str | None] = mapped_column(
|
||
String(32),
|
||
ForeignKey("document_categories.id"),
|
||
nullable=True,
|
||
comment="分类 ID",
|
||
)
|
||
original_filename: Mapped[str] = mapped_column(
|
||
String(512),
|
||
nullable=False,
|
||
comment="原始文件名或文本标题 (仅展示)",
|
||
)
|
||
storage_path: Mapped[str | None] = mapped_column(
|
||
String(1024),
|
||
nullable=True,
|
||
comment="相对于 data/ 的物理存储路径(文本内容文档为 NULL)",
|
||
)
|
||
content: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="直接输入的文本内容(非文件上传时使用)",
|
||
)
|
||
content_format: Mapped[str] = mapped_column(
|
||
String(16),
|
||
default="markdown",
|
||
nullable=False,
|
||
comment="内容格式:markdown / text",
|
||
)
|
||
markdown_path: Mapped[str | None] = mapped_column(
|
||
String(1024),
|
||
nullable=True,
|
||
comment="相对于 data/ 的 Markdown 文件路径",
|
||
)
|
||
file_size: Mapped[int] = mapped_column(
|
||
Integer,
|
||
default=0,
|
||
nullable=False,
|
||
comment="文件大小 (字节),文本内容文档为内容长度",
|
||
)
|
||
mime_type: Mapped[str] = mapped_column(
|
||
String(127),
|
||
default="text/markdown",
|
||
nullable=False,
|
||
comment="MIME 类型",
|
||
)
|
||
file_ext: Mapped[str] = mapped_column(
|
||
String(16),
|
||
default=".md",
|
||
nullable=False,
|
||
comment="文件扩展名 (.docx/.pdf/.md)",
|
||
)
|
||
sha256: Mapped[str | None] = mapped_column(
|
||
String(64),
|
||
nullable=True,
|
||
comment="文件 SHA-256 哈希(文本内容文档为 NULL)",
|
||
)
|
||
doc_token_hash: Mapped[str] = mapped_column(
|
||
String(64),
|
||
unique=True,
|
||
nullable=True,
|
||
index=True,
|
||
comment="SHA-256(document_token) 十六进制",
|
||
)
|
||
doc_token_encrypted: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="Fernet 加密的 document_token 原文",
|
||
)
|
||
doc_token_hint: Mapped[str | None] = mapped_column(
|
||
String(16),
|
||
nullable=True,
|
||
comment="document_token 末 8 位明文",
|
||
)
|
||
title: Mapped[str | None] = mapped_column(
|
||
String(512),
|
||
nullable=True,
|
||
comment="文档标题 (用户可改)",
|
||
)
|
||
description: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="文档描述 (用户可改)",
|
||
)
|
||
keywords: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="关键词 (逗号分隔)",
|
||
)
|
||
content_summary: Mapped[str | None] = mapped_column(
|
||
Text,
|
||
nullable=True,
|
||
comment="抽取式摘要 (~200字)",
|
||
)
|
||
status: Mapped[str] = mapped_column(
|
||
String(16),
|
||
default="PENDING",
|
||
nullable=False,
|
||
comment="状态 (PENDING/PROCESSING/READY/FAILED/DELETED)",
|
||
)
|
||
error_code: Mapped[str | None] = mapped_column(
|
||
String(64),
|
||
nullable=True,
|
||
comment="错误码 (如 SCANNED_PDF_NO_TEXT_LAYER)",
|
||
)
|
||
|
||
# 关系
|
||
knowledge_base = relationship("KnowledgeBase", back_populates="documents", lazy="selectin")
|
||
user = relationship("User", back_populates="documents", lazy="selectin")
|
||
category = relationship("DocumentCategory", back_populates="documents", lazy="selectin")
|
||
access_logs = relationship("AccessLog", back_populates="document", lazy="selectin")
|
||
|
||
def __repr__(self) -> str:
|
||
return f"<Document {self.original_filename!r} (status={self.status!r})>"
|