3ae15eb4fc
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
119 lines
4.0 KiB
Python
Executable File
119 lines
4.0 KiB
Python
Executable File
"""Storage 端口接口 — 统一存储服务的抽象定义。
|
|
|
|
所有存储实现(OSS、本地、S3等)都必须实现这个端口。
|
|
API 和 Worker 都通过这个端口与存储交互,消除两套独立实现。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Optional, Union
|
|
|
|
|
|
class StoragePort(ABC):
|
|
"""统一存储服务端口。
|
|
|
|
定义所有存储后端必须实现的核心能力。
|
|
具体实现见 packages.shared.storage.SharedStorageService。
|
|
"""
|
|
|
|
# ── 基础上传 / 下载 ────────────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def upload_file(
|
|
self,
|
|
file_or_path: Union[str, Path, object],
|
|
storage_key: str,
|
|
content_type: str = "application/octet-stream",
|
|
) -> str:
|
|
"""上传文件到存储,返回公开 URL。
|
|
|
|
Args:
|
|
file_or_path: 本地文件路径(str/Path)或类文件对象
|
|
storage_key: 目标存储键
|
|
content_type: MIME 类型
|
|
|
|
Returns:
|
|
公开访问 URL
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def download_file(self, storage_key_or_url: str, local_path: Union[str, Path]) -> bool:
|
|
"""从存储下载文件到本地。
|
|
|
|
自动识别输入:完整URL走HTTP下载(支持预签名),存储键走SDK下载。
|
|
|
|
Args:
|
|
storage_key_or_url: 存储键或完整 URL
|
|
local_path: 本地保存路径
|
|
|
|
Returns:
|
|
True 成功,False 失败
|
|
"""
|
|
...
|
|
|
|
# ── URL 生成 ──────────────────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def get_url(self, storage_key: str) -> str:
|
|
"""获取公开 URL。"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def get_download_url(self, storage_key_or_url: str, expires_seconds: int = 3600) -> str:
|
|
"""获取预签名下载 URL(私有 bucket 用)。
|
|
|
|
未配置OSS时降级为公开URL。
|
|
"""
|
|
...
|
|
|
|
# ── 文件操作 ──────────────────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def delete_file(self, storage_key: str) -> None:
|
|
"""删除文件(不抛异常)。"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def file_exists(self, storage_key: str) -> bool:
|
|
"""检查文件是否存在。"""
|
|
...
|
|
|
|
# ── 浏览器直传 ────────────────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def create_direct_upload_post(
|
|
self,
|
|
storage_key: str,
|
|
content_type: str,
|
|
max_size_bytes: int,
|
|
expires_seconds: int,
|
|
) -> dict[str, object]:
|
|
"""创建浏览器直传 POST 表单(用于前端直传OSS)。"""
|
|
...
|
|
|
|
# ── Asset 解析(Worker 用)────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def resolve_asset_path(self, asset_id: str, work_dir: Union[str, Path]) -> Optional[Path]:
|
|
"""从 asset_id 解析到本地文件路径。
|
|
|
|
策略:本地路径 → 缓存命中 → OSS下载 → None
|
|
缓存:SHA256(asset_id)[:16] 为文件名,避免重复下载
|
|
"""
|
|
...
|
|
|
|
# ── 工具方法 ──────────────────────────────────────────────────────
|
|
|
|
@abstractmethod
|
|
def normalize_storage_key(self, storage_key_or_url: str) -> str:
|
|
"""从 URL 提取存储键,URL decode 处理。"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def diagnose(self) -> None:
|
|
"""输出存储配置诊断日志。"""
|
|
...
|