feat(storage): support remove old files
This commit is contained in:
@@ -40,6 +40,11 @@ async def upload_avatar(
|
|||||||
# check file
|
# check file
|
||||||
check_image(content, 5 * 1024 * 1024, 256, 256)
|
check_image(content, 5 * 1024 * 1024, 256, 256)
|
||||||
|
|
||||||
|
if url := current_user.avatar_url:
|
||||||
|
path = storage.get_file_name_by_url(url)
|
||||||
|
if path:
|
||||||
|
await storage.delete_file(path)
|
||||||
|
|
||||||
filehash = hashlib.sha256(content).hexdigest()
|
filehash = hashlib.sha256(content).hexdigest()
|
||||||
storage_path = f"avatars/{current_user.id}_{filehash}.png"
|
storage_path = f"avatars/{current_user.id}_{filehash}.png"
|
||||||
if not await storage.is_exists(storage_path):
|
if not await storage.is_exists(storage_path):
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ async def upload_cover(
|
|||||||
# check file
|
# check file
|
||||||
check_image(content, 10 * 1024 * 1024, 3000, 2000)
|
check_image(content, 10 * 1024 * 1024, 3000, 2000)
|
||||||
|
|
||||||
|
if url := current_user.cover["url"]:
|
||||||
|
path = storage.get_file_name_by_url(url)
|
||||||
|
if path:
|
||||||
|
await storage.delete_file(path)
|
||||||
|
|
||||||
filehash = hashlib.sha256(content).hexdigest()
|
filehash = hashlib.sha256(content).hexdigest()
|
||||||
storage_path = f"cover/{current_user.id}_{filehash}.png"
|
storage_path = f"cover/{current_user.id}_{filehash}.png"
|
||||||
if not await storage.is_exists(storage_path):
|
if not await storage.is_exists(storage_path):
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ async def update_team(
|
|||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
check_image(flag, 2 * 1024 * 1024, 240, 120)
|
check_image(flag, 2 * 1024 * 1024, 240, 120)
|
||||||
|
|
||||||
|
if old_flag := team.flag_url:
|
||||||
|
path = storage.get_file_name_by_url(old_flag)
|
||||||
|
if path:
|
||||||
|
await storage.delete_file(path)
|
||||||
filehash = hashlib.sha256(flag).hexdigest()
|
filehash = hashlib.sha256(flag).hexdigest()
|
||||||
storage_path = f"team_flag/{team.id}_{filehash}.png"
|
storage_path = f"team_flag/{team.id}_{filehash}.png"
|
||||||
if not await storage.is_exists(storage_path):
|
if not await storage.is_exists(storage_path):
|
||||||
@@ -122,6 +127,11 @@ async def update_team(
|
|||||||
team.flag_url = await storage.get_file_url(storage_path)
|
team.flag_url = await storage.get_file_url(storage_path)
|
||||||
if cover:
|
if cover:
|
||||||
check_image(cover, 10 * 1024 * 1024, 3000, 2000)
|
check_image(cover, 10 * 1024 * 1024, 3000, 2000)
|
||||||
|
|
||||||
|
if old_cover := team.cover_url:
|
||||||
|
path = storage.get_file_name_by_url(old_cover)
|
||||||
|
if path:
|
||||||
|
await storage.delete_file(path)
|
||||||
filehash = hashlib.sha256(cover).hexdigest()
|
filehash = hashlib.sha256(cover).hexdigest()
|
||||||
storage_path = f"team_cover/{team.id}_{filehash}.png"
|
storage_path = f"team_cover/{team.id}_{filehash}.png"
|
||||||
if not await storage.is_exists(storage_path):
|
if not await storage.is_exists(storage_path):
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .base import StorageService
|
from .base import StorageService
|
||||||
|
|
||||||
import aioboto3
|
import aioboto3
|
||||||
@@ -101,3 +103,23 @@ class AWSS3StorageService(StorageService):
|
|||||||
return url
|
return url
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
raise RuntimeError(f"Failed to generate file URL: {e}")
|
raise RuntimeError(f"Failed to generate file URL: {e}")
|
||||||
|
|
||||||
|
def get_file_name_by_url(self, url: str) -> str | None:
|
||||||
|
parsed = urlparse(url)
|
||||||
|
path = parsed.path.lstrip("/")
|
||||||
|
|
||||||
|
# 1. 如果是 public_url_base 拼接出来的
|
||||||
|
if self.public_url_base and url.startswith(self.public_url_base.rstrip("/")):
|
||||||
|
return path
|
||||||
|
|
||||||
|
# 2. 如果是 S3 path-style: s3.amazonaws.com/<bucket>/<key>
|
||||||
|
if parsed.netloc == "s3.amazonaws.com":
|
||||||
|
parts = path.split("/", 1)
|
||||||
|
return parts[1] if len(parts) > 1 else None
|
||||||
|
|
||||||
|
# 3. 如果是 virtual-hosted-style: <bucket>.s3.<region>.amazonaws.com/<key>
|
||||||
|
if ".s3." in parsed.netloc or parsed.netloc.endswith(".s3.amazonaws.com"):
|
||||||
|
return path
|
||||||
|
|
||||||
|
# 4. 直接返回 path
|
||||||
|
return path or None
|
||||||
|
|||||||
@@ -30,5 +30,9 @@ class StorageService(abc.ABC):
|
|||||||
async def get_file_url(self, file_path: str) -> str:
|
async def get_file_url(self, file_path: str) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_file_name_by_url(self, url: str) -> str | None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .aws_s3 import AWSS3StorageService
|
from .aws_s3 import AWSS3StorageService
|
||||||
|
|
||||||
|
|
||||||
@@ -24,3 +26,18 @@ class CloudflareR2StorageService(AWSS3StorageService):
|
|||||||
@property
|
@property
|
||||||
def endpoint_url(self) -> str:
|
def endpoint_url(self) -> str:
|
||||||
return f"https://{self.account_id}.r2.cloudflarestorage.com"
|
return f"https://{self.account_id}.r2.cloudflarestorage.com"
|
||||||
|
|
||||||
|
def get_file_name_by_url(self, url: str) -> str | None:
|
||||||
|
if not url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parsed = urlparse(url)
|
||||||
|
path = parsed.path.lstrip("/")
|
||||||
|
|
||||||
|
if self.public_url_base and url.startswith(self.public_url_base.rstrip("/")):
|
||||||
|
return path
|
||||||
|
|
||||||
|
if ".r2.cloudflarestorage.com" in parsed.netloc:
|
||||||
|
return path
|
||||||
|
|
||||||
|
return path or None
|
||||||
|
|||||||
@@ -78,3 +78,8 @@ class LocalStorageService(StorageService):
|
|||||||
|
|
||||||
async def get_file_url(self, file_path: str) -> str:
|
async def get_file_url(self, file_path: str) -> str:
|
||||||
return f"{settings.server_url}file/{file_path.lstrip('/')}"
|
return f"{settings.server_url}file/{file_path.lstrip('/')}"
|
||||||
|
|
||||||
|
def get_file_name_by_url(self, url: str) -> str | None:
|
||||||
|
if not url.startswith(str(settings.server_url)):
|
||||||
|
return None
|
||||||
|
return url[len(settings.server_url) + len("file/") :]
|
||||||
|
|||||||
Reference in New Issue
Block a user