feat: move report artifacts to OSS storage

This commit is contained in:
Wyndham ARR
2026-08-04 12:37:26 +08:00
parent 727643f1f2
commit a2b86cdf10
45 changed files with 1288 additions and 1417 deletions

View File

@@ -29,6 +29,8 @@ class ArtifactDescriptor:
sha256: str
byte_size: int
mime_type: str
storage_provider: str = "local"
bucket_alias: str = "arr-project-root"
def validate(self) -> None:
path = PurePosixPath(self.storage_key)
@@ -48,6 +50,12 @@ class ArtifactDescriptor:
or any(character not in "0123456789abcdef" for character in self.sha256)
or not 0 < self.byte_size <= MAX_DOWNLOAD_BYTES
or not self.mime_type
or not isinstance(self.storage_provider, str)
or self.storage_provider not in {"local", "local_fixture", "oss", "s3"}
or not isinstance(self.bucket_alias, str)
or not self.bucket_alias
or "/" in self.bucket_alias
or "\\" in self.bucket_alias
):
raise PortalError("DOWNLOAD_REFERENCE_INVALID", "文件身份无效", 500)
@@ -63,14 +71,21 @@ class UnavailableArtifactReader:
class ManagedObjectArtifactReader:
"""Read a committed daily XLSX from ARR's immutable object store."""
"""Read a committed report artifact from ARR's immutable object store."""
_EXPECTED_ROLES = {
"daily_xlsx": "daily_report",
"monthly_xlsx": "monthly_report",
"company_ten_day_xlsx": "company_report",
}
def __init__(self, object_store: ManagedObjectStore) -> None:
self._object_store = object_store
def read(self, descriptor: ArtifactDescriptor) -> bytes:
descriptor.validate()
if descriptor.file_kind != "daily_xlsx":
expected_role = self._EXPECTED_ROLES.get(descriptor.file_kind)
if expected_role is None:
raise PortalError("DOWNLOAD_REFERENCE_INVALID", "文件身份无效", 500)
try:
stored = self._object_store.inspect_committed(
@@ -78,7 +93,7 @@ class ManagedObjectArtifactReader:
descriptor.original_filename,
)
if (
stored.role != "daily_report"
stored.role != expected_role
or stored.sha256 != descriptor.sha256
or stored.byte_size != descriptor.byte_size
or stored.mime_type != descriptor.mime_type
@@ -87,7 +102,7 @@ class ManagedObjectArtifactReader:
"DOWNLOAD_IDENTITY_MISMATCH", "文件完整性校验失败", 503
)
with tempfile.TemporaryDirectory(prefix="arr-download-") as temporary:
destination = Path(temporary) / "daily.xlsx"
destination = Path(temporary) / "artifact.bin"
self._object_store.materialize(
descriptor.storage_key,
destination,
@@ -111,16 +126,18 @@ class ManagedObjectArtifactReader:
class RoutedArtifactReader:
"""Route OSS daily artifacts and controlled local report artifacts safely."""
"""Route managed OSS artifacts and legacy controlled-local artifacts safely."""
def __init__(
self,
*,
daily_reader: Optional[ArtifactReader],
local_reader: ArtifactReader,
oss_reader: Optional[ArtifactReader] = None,
) -> None:
self._daily_reader = daily_reader
self._local_reader = local_reader
self._oss_reader = oss_reader
def read(self, descriptor: ArtifactDescriptor) -> bytes:
descriptor.validate()
@@ -130,6 +147,12 @@ class RoutedArtifactReader:
"DOWNLOAD_UNAVAILABLE", "文件读取服务暂不可用", 503
)
return self._daily_reader.read(descriptor)
if descriptor.storage_provider in {"oss", "s3"}:
if self._oss_reader is None:
raise PortalError(
"DOWNLOAD_UNAVAILABLE", "文件读取服务暂不可用", 503
)
return self._oss_reader.read(descriptor)
return self._local_reader.read(descriptor)