Optimize S3 asset archive uploads
This commit is contained in:
@@ -286,6 +286,7 @@ def _write_archive_to_storage(
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Release ZIP 包不能为空")
|
||||
|
||||
driver.ensure_directory(storage_root_path)
|
||||
ensured_directories = {normalize_virtual_path(storage_root_path)}
|
||||
extracted_count = 0
|
||||
try:
|
||||
with zipfile.ZipFile(io.BytesIO(archive_content)) as archive:
|
||||
@@ -296,7 +297,10 @@ def _write_archive_to_storage(
|
||||
if relative_path is None:
|
||||
continue
|
||||
target_path = normalize_virtual_path(f"{storage_root_path.rstrip('/')}/{relative_path}")
|
||||
driver.ensure_directory(_parent_virtual_path(target_path))
|
||||
parent_path = _parent_virtual_path(target_path)
|
||||
if parent_path not in ensured_directories:
|
||||
driver.ensure_directory(parent_path)
|
||||
ensured_directories.add(parent_path)
|
||||
try:
|
||||
content = archive.read(member)
|
||||
except Exception as exc:
|
||||
|
||||
@@ -302,6 +302,7 @@ class S3StorageDriver:
|
||||
def __init__(self, *, config: dict[str, Any], mount_root_path: str) -> None:
|
||||
try:
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
except ImportError as exc:
|
||||
raise StorageNotConfiguredError("S3 driver requires boto3 dependency") from exc
|
||||
|
||||
@@ -309,6 +310,15 @@ class S3StorageDriver:
|
||||
if not bucket:
|
||||
raise StorageNotConfiguredError("S3 backend requires config.bucket")
|
||||
|
||||
client_config = Config(
|
||||
connect_timeout=_coerce_positive_number(config.get("connect_timeout_seconds"), default=3.0),
|
||||
read_timeout=_coerce_positive_number(config.get("read_timeout_seconds"), default=10.0),
|
||||
retries={"max_attempts": int(_coerce_positive_number(config.get("max_attempts"), default=2.0))},
|
||||
s3={"addressing_style": _coerce_non_empty_string(config.get("addressing_style")) or "path"},
|
||||
request_checksum_calculation="when_required",
|
||||
response_checksum_validation="when_required",
|
||||
)
|
||||
|
||||
session = boto3.session.Session(
|
||||
aws_access_key_id=_coerce_non_empty_string(config.get("access_key_id")),
|
||||
aws_secret_access_key=_coerce_non_empty_string(config.get("secret_access_key")),
|
||||
@@ -319,9 +329,11 @@ class S3StorageDriver:
|
||||
"s3",
|
||||
endpoint_url=_coerce_non_empty_string(config.get("endpoint_url")),
|
||||
region_name=_coerce_non_empty_string(config.get("region_name")),
|
||||
config=client_config,
|
||||
)
|
||||
self._bucket = bucket
|
||||
self._root_prefix = _normalize_s3_prefix(mount_root_path)
|
||||
self._should_write_directory_markers = bool(config.get("write_directory_markers", False))
|
||||
|
||||
def list_dir(self, path: str) -> list[StorageObject]:
|
||||
normalized = normalize_virtual_path(path)
|
||||
@@ -402,6 +414,12 @@ class S3StorageDriver:
|
||||
if normalized == "/":
|
||||
return
|
||||
|
||||
# S3-compatible object stores do not require directory marker objects
|
||||
# before nested keys are written. The marker PUT is optional and is
|
||||
# expensive on high-file-count uploads.
|
||||
if not self._should_write_directory_markers:
|
||||
return
|
||||
|
||||
key = self._key_for_path(normalized)
|
||||
if key and not key.endswith("/"):
|
||||
key = f"{key}/"
|
||||
@@ -711,6 +729,21 @@ def _coerce_non_empty_string(value: Any) -> str | None:
|
||||
return stripped if stripped else None
|
||||
|
||||
|
||||
def _coerce_positive_number(value: Any, *, default: float) -> float:
|
||||
if isinstance(value, bool):
|
||||
return default
|
||||
if isinstance(value, (int, float)):
|
||||
number = float(value)
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
number = float(value.strip())
|
||||
except ValueError:
|
||||
return default
|
||||
else:
|
||||
return default
|
||||
return number if number > 0 else default
|
||||
|
||||
|
||||
def _is_s3_not_found(exc: Exception) -> bool:
|
||||
response = getattr(exc, "response", None)
|
||||
if not isinstance(response, dict):
|
||||
|
||||
Reference in New Issue
Block a user