feat: Implement file download token management and restructure file routes

- Added DownloadTokenManager for creating and verifying JWT download tokens.
- Introduced new download routes for creating download tokens and downloading files using tokens.
- Restructured file upload routes into a dedicated sub-router.
- Updated file upload session management with improved error handling and response structures.
- Created a new MCP (Microservice Communication Protocol) router with basic request and response models.
- Added base models for MCP requests and responses, including method enumeration.
This commit is contained in:
2025-12-23 18:12:11 +08:00
parent 4cd13e4075
commit 16cec42181
14 changed files with 1884 additions and 396 deletions

View File

@@ -8,6 +8,10 @@ from .user import (
UserResponse,
UserSettingResponse,
WebAuthnInfo,
# 管理员DTO
UserAdminUpdateRequest,
UserCalibrateResponse,
UserAdminDetailResponse,
)
from .user_authn import AuthnResponse, UserAuthn
from .color import ThemeResponse
@@ -27,7 +31,11 @@ from .node import (
NodeStatus,
NodeType,
)
from .group import Group, GroupBase, GroupOptions, GroupOptionsBase, GroupResponse
from .group import (
Group, GroupBase, GroupOptions, GroupOptionsBase, GroupResponse,
# 管理员DTO
GroupCreateRequest, GroupUpdateRequest, GroupDetailResponse, GroupListResponse,
)
from .object import (
CreateFileRequest,
CreateUploadSessionRequest,
@@ -50,13 +58,21 @@ from .object import (
UploadSession,
UploadSessionBase,
UploadSessionResponse,
# 管理员DTO
AdminFileResponse,
AdminFileListResponse,
FileBanRequest,
)
from .physical_file import PhysicalFile, PhysicalFileBase
from .order import Order, OrderStatus, OrderType
from .policy import Policy, PolicyOptions, PolicyOptionsBase, PolicyType
from .redeem import Redeem, RedeemType
from .report import Report, ReportReason
from .setting import Setting, SettingsType, SiteConfigResponse
from .setting import (
Setting, SettingsType, SiteConfigResponse,
# 管理员DTO
SettingItem, SettingsUpdateRequest, SettingsGetResponse,
)
from .share import Share
from .source_link import SourceLink
from .storage_pack import StoragePack
@@ -66,13 +82,10 @@ from .webdav import WebDAV
from .database import engine, get_session
import uuid
from sqlmodel import Field
from .base import SQLModelBase
class ResponseBase(SQLModelBase):
"""通用响应模型"""
instance_id: uuid.UUID = Field(default_factory=uuid.uuid4)
"""实例ID用于标识请求的唯一性"""
from .model_base import (
MCPBase,
MCPMethod,
MCPRequestBase,
MCPResponseBase,
ResponseBase,
)

View File

@@ -45,6 +45,151 @@ class GroupOptionsBase(SQLModelBase):
# ==================== DTO 模型 ====================
class GroupCreateRequest(SQLModelBase):
"""创建用户组请求 DTO"""
name: str = Field(max_length=255)
"""用户组名称"""
max_storage: int = Field(default=0, ge=0)
"""最大存储空间字节0表示不限制"""
share_enabled: bool = False
"""是否允许创建分享"""
web_dav_enabled: bool = False
"""是否允许使用WebDAV"""
speed_limit: int = Field(default=0, ge=0)
"""速度限制 (KB/s), 0为不限制"""
# 用户组选项
share_download: bool = False
"""是否允许分享下载"""
share_free: bool = False
"""是否免积分获取需要积分的内容"""
relocate: bool = False
"""是否允许文件重定位"""
source_batch: int = Field(default=0, ge=0)
"""批量获取源地址数量"""
select_node: bool = False
"""是否允许选择节点"""
advance_delete: bool = False
"""是否允许高级删除"""
archive_download: bool = False
"""是否允许打包下载"""
archive_task: bool = False
"""是否允许创建打包任务"""
webdav_proxy: bool = False
"""是否允许WebDAV代理"""
aria2: bool = False
"""是否允许使用aria2"""
redirected_source: bool = False
"""是否使用重定向源"""
policy_ids: list[UUID] = []
"""关联的存储策略UUID列表"""
class GroupUpdateRequest(SQLModelBase):
"""更新用户组请求 DTO所有字段可选"""
name: str | None = Field(default=None, max_length=255)
"""用户组名称"""
max_storage: int | None = Field(default=None, ge=0)
"""最大存储空间(字节)"""
share_enabled: bool | None = None
"""是否允许创建分享"""
web_dav_enabled: bool | None = None
"""是否允许使用WebDAV"""
speed_limit: int | None = Field(default=None, ge=0)
"""速度限制 (KB/s)"""
# 用户组选项
share_download: bool | None = None
share_free: bool | None = None
relocate: bool | None = None
source_batch: int | None = None
select_node: bool | None = None
advance_delete: bool | None = None
archive_download: bool | None = None
archive_task: bool | None = None
webdav_proxy: bool | None = None
aria2: bool | None = None
redirected_source: bool | None = None
policy_ids: list[UUID] | None = None
"""关联的存储策略UUID列表"""
class GroupDetailResponse(SQLModelBase):
"""用户组详情响应 DTO"""
id: UUID
"""用户组UUID"""
name: str
"""用户组名称"""
max_storage: int = 0
"""最大存储空间(字节)"""
share_enabled: bool = False
"""是否允许创建分享"""
web_dav_enabled: bool = False
"""是否允许使用WebDAV"""
admin: bool = False
"""是否为管理员组"""
speed_limit: int = 0
"""速度限制 (KB/s)"""
user_count: int = 0
"""用户数量"""
policy_ids: list[UUID] = []
"""关联的存储策略UUID列表"""
# 选项
share_download: bool = False
share_free: bool = False
relocate: bool = False
source_batch: int = 0
select_node: bool = False
advance_delete: bool = False
archive_download: bool = False
archive_task: bool = False
webdav_proxy: bool = False
aria2: bool = False
redirected_source: bool = False
class GroupListResponse(SQLModelBase):
"""用户组列表响应 DTO"""
groups: list["GroupDetailResponse"] = []
"""用户组列表"""
total: int = 0
"""总数"""
class GroupResponse(GroupBase, GroupOptionsBase):
"""用户组响应 DTO"""

39
models/model_base.py Normal file
View File

@@ -0,0 +1,39 @@
import uuid
from enum import StrEnum
from sqlmodel import Field
from .base import SQLModelBase
class MCPMethod(StrEnum):
"""MCP 方法枚举"""
PING = "ping"
"""Ping 方法,用于测试连接"""
class MCPBase(SQLModelBase):
"""MCP 请求基础模型"""
jsonrpc: str = "2.0"
"""JSON-RPC 版本"""
id: uuid.UUID = Field(default_factory=uuid.uuid4)
"""请求/响应 ID用于标识请求/响应的唯一性"""
class MCPRequestBase(MCPBase):
"""MCP 请求模型基础类"""
method: str
"""方法名称"""
class MCPResponseBase(MCPBase):
"""MCP 响应模型基础类"""
result: str
"""方法返回结果"""
class ResponseBase(SQLModelBase):
"""通用响应模型"""
instance_id: uuid.UUID = Field(default_factory=uuid.uuid4)
"""实例ID用于标识请求的唯一性"""

View File

@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Literal
from uuid import UUID
from enum import StrEnum
from sqlmodel import Field, Relationship, UniqueConstraint, CheckConstraint, Index
from sqlmodel import Field, Relationship, UniqueConstraint, CheckConstraint, Index, text
from .base import SQLModelBase
from .mixin import UUIDTableBaseMixin
@@ -258,11 +258,39 @@ class Object(ObjectBase, UUIDTableBaseMixin):
)
"""存储策略UUID文件直接使用目录作为子文件的默认策略"""
# ==================== 封禁相关字段 ====================
is_banned: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")})
"""是否被封禁"""
banned_at: datetime | None = None
"""封禁时间"""
banned_by: UUID | None = Field(
default=None,
foreign_key="user.id",
index=True,
ondelete="SET NULL",
sa_column_kwargs={"name": "banned_by"}
)
"""封禁操作者UUID"""
ban_reason: str | None = Field(default=None, max_length=500)
"""封禁原因"""
# ==================== 关系 ====================
owner: "User" = Relationship(back_populates="objects")
owner: "User" = Relationship(
back_populates="objects",
sa_relationship_kwargs={"foreign_keys": "[Object.owner_id]"}
)
"""所有者"""
banner: "User" = Relationship(
sa_relationship_kwargs={"foreign_keys": "[Object.banned_by]"}
)
"""封禁操作者"""
policy: "Policy" = Relationship(back_populates="objects")
"""存储策略"""
@@ -642,3 +670,47 @@ class ObjectPropertyDetailResponse(ObjectPropertyResponse):
reference_count: int = 1
"""物理文件引用计数(仅文件有效)"""
# ==================== 管理员文件管理 DTO ====================
class AdminFileResponse(ObjectResponse):
"""管理员文件响应 DTO"""
owner_id: UUID
"""所有者UUID"""
owner_username: str
"""所有者用户名"""
policy_name: str
"""存储策略名称"""
is_banned: bool = False
"""是否被封禁"""
banned_at: datetime | None = None
"""封禁时间"""
ban_reason: str | None = None
"""封禁原因"""
class FileBanRequest(SQLModelBase):
"""文件封禁请求 DTO"""
is_banned: bool = True
"""是否封禁"""
reason: str | None = Field(default=None, max_length=500)
"""封禁原因"""
class AdminFileListResponse(SQLModelBase):
"""管理员文件列表响应 DTO"""
files: list[AdminFileResponse] = []
"""文件列表"""
total: int = 0
"""总数"""

View File

@@ -40,6 +40,32 @@ class SiteConfigResponse(SQLModelBase):
"""验证码密钥"""
# ==================== 管理员设置 DTO ====================
class SettingItem(SQLModelBase):
"""设置项 DTO"""
name: str
"""设置项名称"""
value: str | None = None
"""设置值"""
class SettingsUpdateRequest(SQLModelBase):
"""更新设置请求 DTO"""
settings: dict[str, dict[str, str | None]]
"""按类型分组的设置项,格式: {"basic": {"siteName": "xxx", ...}, ...}"""
class SettingsGetResponse(SQLModelBase):
"""获取设置响应 DTO"""
settings: dict[str, dict[str, str | None]] = {}
"""按类型分组的设置项"""
# ==================== 数据库模型 ====================
class SettingsType(StrEnum):

View File

@@ -201,6 +201,68 @@ class UserSettingResponse(SQLModelBase):
"""用户UUID"""
# ==================== 管理员用户管理 DTO ====================
class UserAdminUpdateRequest(SQLModelBase):
"""管理员更新用户请求 DTO"""
nickname: str | None = Field(default=None, max_length=50)
"""昵称"""
password: str | None = None
"""新密码(为空则不修改)"""
group_id: UUID | None = None
"""用户组UUID"""
status: bool | None = None
"""用户状态"""
score: int | None = Field(default=None, ge=0)
"""积分"""
storage: int | None = Field(default=None, ge=0)
"""已用存储空间(用于手动校准)"""
group_expires: datetime | None = None
"""用户组过期时间"""
class UserCalibrateResponse(SQLModelBase):
"""用户存储校准响应 DTO"""
user_id: UUID
"""用户UUID"""
previous_storage: int
"""校准前的存储空间(字节)"""
current_storage: int
"""校准后的存储空间(字节)"""
difference: int
"""差异值(字节)"""
file_count: int
"""实际文件数量"""
class UserAdminDetailResponse(UserPublic):
"""管理员用户详情响应 DTO"""
two_factor_enabled: bool = False
"""是否启用两步验证"""
file_count: int = 0
"""文件数量"""
share_count: int = 0
"""分享数量"""
task_count: int = 0
"""任务数量"""
# 前向引用导入
from .group import GroupResponse # noqa: E402
from .user_authn import AuthnResponse # noqa: E402