feat: 支持S3预签名上传
This commit is contained in:
+110
@@ -79,6 +79,23 @@ class FileStorageInterface:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def generate_presigned_upload_url(self, save_path: str, expires_in: int = 900) -> Optional[str]:
|
||||
"""
|
||||
生成预签名上传URL
|
||||
:param save_path: 文件保存路径
|
||||
:param expires_in: URL过期时间(秒),默认15分钟
|
||||
:return: 预签名URL,如果不支持直传则返回None
|
||||
"""
|
||||
return None # 默认不支持直传,使用代理模式
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def clean_chunks(self, upload_id: str, save_path: str):
|
||||
"""
|
||||
清理临时分片文件
|
||||
@@ -219,6 +236,15 @@ class SystemFileStorage(FileStorageInterface):
|
||||
except Exception as e:
|
||||
logger.info(f"清理 chunks 父目录失败: {e}")
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在于本地文件系统
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
file_path = self.root_path / save_path
|
||||
return file_path.exists()
|
||||
|
||||
|
||||
class S3FileStorage(FileStorageInterface):
|
||||
def __init__(self):
|
||||
@@ -425,6 +451,48 @@ class S3FileStorage(FileStorageInterface):
|
||||
except Exception as e:
|
||||
logger.info(f"清理 S3 分片数据时出错: {e}")
|
||||
|
||||
async def generate_presigned_upload_url(self, save_path: str, expires_in: int = 900) -> Optional[str]:
|
||||
"""
|
||||
生成S3预签名上传URL
|
||||
:param save_path: 文件保存路径
|
||||
:param expires_in: URL过期时间(秒),默认15分钟
|
||||
:return: 预签名PUT URL
|
||||
"""
|
||||
async with self.session.client(
|
||||
"s3",
|
||||
endpoint_url=self.endpoint_url,
|
||||
aws_session_token=self.aws_session_token,
|
||||
region_name=self.region_name,
|
||||
config=Config(signature_version=self.signature_version),
|
||||
) as s3:
|
||||
return await s3.generate_presigned_url(
|
||||
"put_object",
|
||||
Params={
|
||||
"Bucket": self.bucket_name,
|
||||
"Key": save_path,
|
||||
},
|
||||
ExpiresIn=expires_in,
|
||||
)
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在于S3
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
async with self.session.client(
|
||||
"s3",
|
||||
endpoint_url=self.endpoint_url,
|
||||
aws_session_token=self.aws_session_token,
|
||||
region_name=self.region_name,
|
||||
config=Config(signature_version=self.signature_version),
|
||||
) as s3:
|
||||
try:
|
||||
await s3.head_object(Bucket=self.bucket_name, Key=save_path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class OneDriveFileStorage(FileStorageInterface):
|
||||
def __init__(self):
|
||||
@@ -660,6 +728,25 @@ class OneDriveFileStorage(FileStorageInterface):
|
||||
except Exception as e:
|
||||
logger.info(f"清理 OneDrive 分片时出错: {e}")
|
||||
|
||||
def _file_exists(self, save_path: str) -> bool:
|
||||
"""同步检查文件是否存在"""
|
||||
try:
|
||||
path = self._get_path_str(save_path)
|
||||
self.root_path.get_by_path(path).get().execute_query()
|
||||
return True
|
||||
except self._ClientRequestException as e:
|
||||
if e.code == "itemNotFound":
|
||||
return False
|
||||
raise e
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在于OneDrive
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
return await asyncio.to_thread(self._file_exists, save_path)
|
||||
|
||||
|
||||
class OpenDALFileStorage(FileStorageInterface):
|
||||
def __init__(self):
|
||||
@@ -743,6 +830,18 @@ class OpenDALFileStorage(FileStorageInterface):
|
||||
except Exception as e:
|
||||
logger.info(f"清理 OpenDAL 分片时出错: {e}")
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在于OpenDAL存储
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
try:
|
||||
await self.operator.stat(save_path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class WebDAVFileStorage(FileStorageInterface):
|
||||
_instance: Optional["WebDAVFileStorage"] = None
|
||||
@@ -997,6 +1096,17 @@ class WebDAVFileStorage(FileStorageInterface):
|
||||
except Exception as e:
|
||||
logger.info(f"清理 WebDAV 分片时出错: {e}")
|
||||
|
||||
async def file_exists(self, save_path: str) -> bool:
|
||||
"""
|
||||
检查文件是否存在于WebDAV
|
||||
:param save_path: 文件路径
|
||||
:return: 文件是否存在
|
||||
"""
|
||||
url = self._build_url(save_path)
|
||||
async with aiohttp.ClientSession(auth=self.auth) as session:
|
||||
async with session.head(url) as resp:
|
||||
return resp.status == 200
|
||||
|
||||
|
||||
storages = {
|
||||
"local": SystemFileStorage,
|
||||
|
||||
Reference in New Issue
Block a user