feat: sync latest ARR implementation
This commit is contained in:
@@ -5,10 +5,13 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Protocol
|
||||
from typing import Optional, Protocol
|
||||
|
||||
from arr_ingestion.contracts import IngestionError
|
||||
from arr_storage.store import ManagedObjectStore
|
||||
from arr_web.contracts import PortalError
|
||||
|
||||
|
||||
@@ -59,6 +62,77 @@ class UnavailableArtifactReader:
|
||||
raise PortalError("DOWNLOAD_UNAVAILABLE", "文件读取服务暂不可用", 503)
|
||||
|
||||
|
||||
class ManagedObjectArtifactReader:
|
||||
"""Read a committed daily XLSX from ARR's immutable object store."""
|
||||
|
||||
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":
|
||||
raise PortalError("DOWNLOAD_REFERENCE_INVALID", "文件身份无效", 500)
|
||||
try:
|
||||
stored = self._object_store.inspect_committed(
|
||||
descriptor.storage_key,
|
||||
descriptor.original_filename,
|
||||
)
|
||||
if (
|
||||
stored.role != "daily_report"
|
||||
or stored.sha256 != descriptor.sha256
|
||||
or stored.byte_size != descriptor.byte_size
|
||||
or stored.mime_type != descriptor.mime_type
|
||||
):
|
||||
raise PortalError(
|
||||
"DOWNLOAD_IDENTITY_MISMATCH", "文件完整性校验失败", 503
|
||||
)
|
||||
with tempfile.TemporaryDirectory(prefix="arr-download-") as temporary:
|
||||
destination = Path(temporary) / "daily.xlsx"
|
||||
self._object_store.materialize(
|
||||
descriptor.storage_key,
|
||||
destination,
|
||||
MAX_DOWNLOAD_BYTES,
|
||||
)
|
||||
value = destination.read_bytes()
|
||||
except PortalError:
|
||||
raise
|
||||
except (IngestionError, OSError):
|
||||
raise PortalError(
|
||||
"DOWNLOAD_UNAVAILABLE", "文件读取服务暂不可用", 503
|
||||
) from None
|
||||
if (
|
||||
len(value) != descriptor.byte_size
|
||||
or hashlib.sha256(value).hexdigest() != descriptor.sha256
|
||||
):
|
||||
raise PortalError(
|
||||
"DOWNLOAD_IDENTITY_MISMATCH", "文件完整性校验失败", 503
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class RoutedArtifactReader:
|
||||
"""Route OSS daily artifacts and controlled local report artifacts safely."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
daily_reader: Optional[ArtifactReader],
|
||||
local_reader: ArtifactReader,
|
||||
) -> None:
|
||||
self._daily_reader = daily_reader
|
||||
self._local_reader = local_reader
|
||||
|
||||
def read(self, descriptor: ArtifactDescriptor) -> bytes:
|
||||
descriptor.validate()
|
||||
if descriptor.file_kind == "daily_xlsx":
|
||||
if self._daily_reader is None:
|
||||
raise PortalError(
|
||||
"DOWNLOAD_UNAVAILABLE", "文件读取服务暂不可用", 503
|
||||
)
|
||||
return self._daily_reader.read(descriptor)
|
||||
return self._local_reader.read(descriptor)
|
||||
|
||||
|
||||
class ControlledProjectArtifactReader:
|
||||
"""Development/local publication reader constrained to one project root."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user