From 96bf4474263c0a083d70e06bad0ab3d7442ce4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Tue, 23 Dec 2025 11:00:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=E5=A4=9A=E4=B8=AA=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=A4=96=E9=94=AE=E5=AD=97=E6=AE=B5=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=BA=A7=E8=81=94=E5=88=A0=E9=99=A4=E5=92=8C=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/download.py | 38 ++++++++++++++++++++++------ models/group.py | 8 ++++-- models/node.py | 9 +++++-- models/object.py | 43 +++++++++++++++++++++++++------ models/order.py | 7 +++++- models/policy.py | 20 ++++++++++++--- models/report.py | 7 +++++- models/share.py | 17 ++++++++++--- models/source_link.py | 6 ++++- models/storage_pack.py | 7 +++++- models/tag.py | 7 +++++- models/task.py | 14 ++++++++--- models/user.py | 57 ++++++++++++++++++++++++++++++++++-------- models/user_authn.py | 6 ++++- models/webdav.py | 7 +++++- 15 files changed, 206 insertions(+), 47 deletions(-) diff --git a/models/download.py b/models/download.py index 4f08e3c..9c7bd6d 100644 --- a/models/download.py +++ b/models/download.py @@ -66,7 +66,11 @@ class DownloadAria2InfoBase(SQLModelBase): class DownloadAria2Info(DownloadAria2InfoBase, SQLModelBase, table=True): """Aria2下载信息模型(与Download一对一关联)""" - download_id: UUID = Field(foreign_key="download.id", primary_key=True) + download_id: UUID = Field( + foreign_key="download.id", + primary_key=True, + ondelete="CASCADE" + ) """关联的下载任务UUID""" # 反向关系 @@ -77,7 +81,11 @@ class DownloadAria2Info(DownloadAria2InfoBase, SQLModelBase, table=True): class DownloadAria2File(SQLModelBase, TableBaseMixin): """Aria2下载文件列表(与Download一对多关联)""" - download_id: UUID = Field(foreign_key="download.id", index=True) + download_id: UUID = Field( + foreign_key="download.id", + index=True, + ondelete="CASCADE" + ) """关联的下载任务UUID""" file_index: int = Field(ge=1) @@ -145,23 +153,39 @@ class Download(DownloadBase, UUIDTableBaseMixin): """目标存储路径""" # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True) + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) """所属用户UUID""" - task_id: int | None = Field(default=None, foreign_key="task.id", index=True) + task_id: int | None = Field( + default=None, + foreign_key="task.id", + index=True, + ondelete="SET NULL" + ) """关联的任务ID""" - node_id: int = Field(foreign_key="node.id", index=True) + node_id: int = Field( + foreign_key="node.id", + index=True, + ondelete="RESTRICT" + ) """执行下载的节点ID""" # 关系 aria2_info: DownloadAria2Info | None = Relationship( back_populates="download", - sa_relationship_kwargs={"uselist": False}, + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"}, ) """Aria2下载信息""" - aria2_files: list[DownloadAria2File] = Relationship(back_populates="download") + aria2_files: list[DownloadAria2File] = Relationship( + back_populates="download", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """Aria2文件列表""" user: "User" = Relationship(back_populates="downloads") diff --git a/models/group.py b/models/group.py index 72e46c0..bc8684c 100644 --- a/models/group.py +++ b/models/group.py @@ -79,7 +79,11 @@ from .policy import GroupPolicyLink class GroupOptions(GroupOptionsBase, TableBaseMixin): """用户组选项模型""" - group_id: UUID = Field(foreign_key="group.id", unique=True) + group_id: UUID = Field( + foreign_key="group.id", + unique=True, + ondelete="CASCADE" + ) """关联的用户组UUID""" archive_download: bool = False @@ -125,7 +129,7 @@ class Group(GroupBase, UUIDTableBaseMixin): # 一对一关系:用户组选项 options: GroupOptions | None = Relationship( back_populates="group", - sa_relationship_kwargs={"uselist": False} + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"} ) # 多对多关系:用户组可以关联多个存储策略 diff --git a/models/node.py b/models/node.py index 74615a3..4c34d65 100644 --- a/models/node.py +++ b/models/node.py @@ -48,7 +48,12 @@ class Aria2ConfigurationBase(SQLModelBase): class Aria2Configuration(Aria2ConfigurationBase, TableBaseMixin): """Aria2配置模型(与Node一对一关联)""" - node_id: int = Field(foreign_key="node.id", unique=True, index=True) + node_id: int = Field( + foreign_key="node.id", + unique=True, + index=True, + ondelete="CASCADE" + ) """关联的节点ID""" # 反向关系 @@ -90,7 +95,7 @@ class Node(SQLModelBase, TableBaseMixin): # 关系 aria2_config: Aria2Configuration | None = Relationship( back_populates="node", - sa_relationship_kwargs={"uselist": False}, + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"}, ) """Aria2配置""" diff --git a/models/object.py b/models/object.py index 08ec6b5..0e0c2c5 100644 --- a/models/object.py +++ b/models/object.py @@ -168,7 +168,12 @@ class DirectoryResponse(SQLModelBase): class FileMetadata(FileMetadataBase, UUIDTableBaseMixin): """文件元数据模型(与Object一对一关联)""" - object_id: UUID = Field(foreign_key="object.id", unique=True, index=True) + object_id: UUID = Field( + foreign_key="object.id", + unique=True, + index=True, + ondelete="CASCADE" + ) """关联的对象UUID""" # 反向关系 @@ -228,13 +233,26 @@ class Object(ObjectBase, UUIDTableBaseMixin): # ==================== 外键 ==================== - parent_id: UUID | None = Field(default=None, foreign_key="object.id", index=True) + parent_id: UUID | None = Field( + default=None, + foreign_key="object.id", + index=True, + ondelete="CASCADE" + ) """父目录UUID,NULL 表示这是用户的根目录""" - owner_id: UUID = Field(foreign_key="user.id", index=True) + owner_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) """所有者用户UUID""" - policy_id: UUID = Field(foreign_key="policy.id", index=True) + policy_id: UUID = Field( + foreign_key="policy.id", + index=True, + ondelete="RESTRICT" + ) """存储策略UUID(文件直接使用,目录作为子文件的默认策略)""" # ==================== 关系 ==================== @@ -252,20 +270,29 @@ class Object(ObjectBase, UUIDTableBaseMixin): ) """父目录""" - children: list["Object"] = Relationship(back_populates="parent") + children: list["Object"] = Relationship( + back_populates="parent", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """子对象(文件和子目录)""" # 仅文件有效的关系 file_metadata: FileMetadata | None = Relationship( back_populates="object", - sa_relationship_kwargs={"uselist": False}, + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"}, ) """文件元数据(仅文件有效)""" - source_links: list["SourceLink"] = Relationship(back_populates="object") + source_links: list["SourceLink"] = Relationship( + back_populates="object", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """源链接列表(仅文件有效)""" - shares: list["Share"] = Relationship(back_populates="object") + shares: list["Share"] = Relationship( + back_populates="object", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """分享列表""" # ==================== 业务属性 ==================== diff --git a/models/order.py b/models/order.py index 7b88d3a..9ec6efc 100644 --- a/models/order.py +++ b/models/order.py @@ -55,7 +55,12 @@ class Order(SQLModelBase, TableBaseMixin): """订单状态""" # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True, description="所属用户UUID") + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) + """所属用户UUID""" # 关系 user: "User" = Relationship(back_populates="orders") \ No newline at end of file diff --git a/models/policy.py b/models/policy.py index 084e1ae..1fd4547 100644 --- a/models/policy.py +++ b/models/policy.py @@ -15,10 +15,18 @@ if TYPE_CHECKING: class GroupPolicyLink(SQLModelBase, table=True): """用户组与存储策略的多对多关联表""" - group_id: UUID = Field(foreign_key="group.id", primary_key=True) + group_id: UUID = Field( + foreign_key="group.id", + primary_key=True, + ondelete="CASCADE" + ) """用户组UUID""" - policy_id: UUID = Field(foreign_key="policy.id", primary_key=True) + policy_id: UUID = Field( + foreign_key="policy.id", + primary_key=True, + ondelete="CASCADE" + ) """存储策略UUID""" @@ -52,7 +60,11 @@ class PolicyOptionsBase(SQLModelBase): class PolicyOptions(PolicyOptionsBase, UUIDTableBaseMixin): """存储策略选项模型(与Policy一对一关联)""" - policy_id: UUID = Field(foreign_key="policy.id", unique=True) + policy_id: UUID = Field( + foreign_key="policy.id", + unique=True, + ondelete="CASCADE" + ) """关联的策略UUID""" # 反向关系 @@ -105,7 +117,7 @@ class Policy(SQLModelBase, UUIDTableBaseMixin): # 一对一关系:策略选项 options: PolicyOptions | None = Relationship( back_populates="policy", - sa_relationship_kwargs={"uselist": False}, + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"}, ) """策略的扩展选项""" diff --git a/models/report.py b/models/report.py index e39a450..f7a9b2e 100644 --- a/models/report.py +++ b/models/report.py @@ -24,7 +24,12 @@ class Report(SQLModelBase, TableBaseMixin): description: str | None = Field(default=None, max_length=255, description="补充描述") # 外键 - share_id: int = Field(foreign_key="share.id", index=True, description="被举报的分享ID") + share_id: int = Field( + foreign_key="share.id", + index=True, + ondelete="CASCADE" + ) + """被举报的分享ID""" # 关系 share: "Share" = Relationship(back_populates="reports") \ No newline at end of file diff --git a/models/share.py b/models/share.py index e235d72..9538c3f 100644 --- a/models/share.py +++ b/models/share.py @@ -30,7 +30,11 @@ class Share(SQLModelBase, TableBaseMixin): password: str | None = Field(default=None, max_length=255) """分享密码(加密后)""" - object_id: UUID = Field(foreign_key="object.id", index=True) + object_id: UUID = Field( + foreign_key="object.id", + index=True, + ondelete="CASCADE" + ) """关联的对象UUID""" views: int = Field(default=0, sa_column_kwargs={"server_default": "0"}) @@ -55,7 +59,11 @@ class Share(SQLModelBase, TableBaseMixin): """兑换此分享所需的积分""" # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True) + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) """创建分享的用户UUID""" # 关系 @@ -65,7 +73,10 @@ class Share(SQLModelBase, TableBaseMixin): object: "Object" = Relationship(back_populates="shares") """关联的对象""" - reports: list["Report"] = Relationship(back_populates="share") + reports: list["Report"] = Relationship( + back_populates="share", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """举报列表""" @property diff --git a/models/source_link.py b/models/source_link.py index fdc5a0a..879b9f7 100644 --- a/models/source_link.py +++ b/models/source_link.py @@ -25,7 +25,11 @@ class SourceLink(SQLModelBase, TableBaseMixin): """通过此链接的下载次数""" # 外键 - object_id: UUID = Field(foreign_key="object.id", index=True) + object_id: UUID = Field( + foreign_key="object.id", + index=True, + ondelete="CASCADE" + ) """关联的对象UUID(必须是文件类型)""" # 关系 diff --git a/models/storage_pack.py b/models/storage_pack.py index 019a10a..01edd6f 100644 --- a/models/storage_pack.py +++ b/models/storage_pack.py @@ -20,7 +20,12 @@ class StoragePack(SQLModelBase, TableBaseMixin): size: int = Field(description="容量包大小(字节)") # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True, description="所属用户UUID") + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) + """所属用户UUID""" # 关系 user: "User" = Relationship(back_populates="storage_packs") \ No newline at end of file diff --git a/models/tag.py b/models/tag.py index b2a0649..b1fd909 100644 --- a/models/tag.py +++ b/models/tag.py @@ -39,7 +39,12 @@ class Tag(SQLModelBase, TableBaseMixin): expression: str | None = Field(default=None, description="自动标签的匹配表达式") # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True, description="所属用户UUID") + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) + """所属用户UUID""" # 关系 user: "User" = Relationship(back_populates="tags") \ No newline at end of file diff --git a/models/task.py b/models/task.py index 7585ba7..c46657f 100644 --- a/models/task.py +++ b/models/task.py @@ -49,7 +49,11 @@ class TaskPropsBase(SQLModelBase): class TaskProps(TaskPropsBase, TableBaseMixin): """任务属性模型(与Task一对一关联)""" - task_id: int = Field(foreign_key="task.id", primary_key=True) + task_id: int = Field( + foreign_key="task.id", + primary_key=True, + ondelete="CASCADE" + ) """关联的任务ID""" # 反向关系 @@ -79,13 +83,17 @@ class Task(SQLModelBase, TableBaseMixin): """错误信息""" # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True) + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) """所属用户UUID""" # 关系 props: TaskProps | None = Relationship( back_populates="task", - sa_relationship_kwargs={"uselist": False}, + sa_relationship_kwargs={"uselist": False, "cascade": "all, delete-orphan"}, ) """任务属性""" diff --git a/models/user.py b/models/user.py index b7db76a..2159824 100644 --- a/models/user.py +++ b/models/user.py @@ -253,10 +253,18 @@ class User(UserBase, UUIDTableBaseMixin): """时区,UTC 偏移小时数""" # 外键 - group_id: UUID = Field(foreign_key="group.id", index=True) + group_id: UUID = Field( + foreign_key="group.id", + index=True, + ondelete="RESTRICT" + ) """所属用户组UUID""" - previous_group_id: UUID | None = Field(default=None, foreign_key="group.id") + previous_group_id: UUID | None = Field( + default=None, + foreign_key="group.id", + ondelete="SET NULL" + ) """之前的用户组UUID(用于过期后恢复)""" @@ -274,16 +282,43 @@ class User(UserBase, UUIDTableBaseMixin): } ) - downloads: list["Download"] = Relationship(back_populates="user") - objects: list["Object"] = Relationship(back_populates="owner") + downloads: list["Download"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + objects: list["Object"] = Relationship( + back_populates="owner", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) """用户的所有对象(文件和目录)""" - orders: list["Order"] = Relationship(back_populates="user") - shares: list["Share"] = Relationship(back_populates="user") - storage_packs: list["StoragePack"] = Relationship(back_populates="user") - tags: list["Tag"] = Relationship(back_populates="user") - tasks: list["Task"] = Relationship(back_populates="user") - webdavs: list["WebDAV"] = Relationship(back_populates="user") - authns: list["UserAuthn"] = Relationship(back_populates="user") + orders: list["Order"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + shares: list["Share"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + storage_packs: list["StoragePack"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + tags: list["Tag"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + tasks: list["Task"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + webdavs: list["WebDAV"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) + authns: list["UserAuthn"] = Relationship( + back_populates="user", + sa_relationship_kwargs={"cascade": "all, delete-orphan"} + ) def to_public(self) -> "UserPublic": """转换为公开 DTO,排除敏感字段""" diff --git a/models/user_authn.py b/models/user_authn.py index 3ce24be..306243b 100644 --- a/models/user_authn.py +++ b/models/user_authn.py @@ -50,7 +50,11 @@ class UserAuthn(SQLModelBase, TableBaseMixin): """用户自定义的凭证名称,便于识别""" # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True) + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) """所属用户UUID""" # 关系 diff --git a/models/webdav.py b/models/webdav.py index e2f4d67..97098e7 100644 --- a/models/webdav.py +++ b/models/webdav.py @@ -22,7 +22,12 @@ class WebDAV(SQLModelBase, TableBaseMixin): use_proxy: bool = Field(default=False, description="是否使用代理下载") # 外键 - user_id: UUID = Field(foreign_key="user.id", index=True, description="所属用户UUID") + user_id: UUID = Field( + foreign_key="user.id", + index=True, + ondelete="CASCADE" + ) + """所属用户UUID""" # 关系 user: "User" = Relationship(back_populates="webdavs") \ No newline at end of file