64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""基础设施层 · OSS 对象存储异步封装(aioboto3,兼容阿里云 OSS / MinIO)。
|
||
|
|
|
||
|
|
未配置 OSS 时降级为本地上传目录(uploads/),保证无 OSS 也可用。
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from .. import config
|
||
|
|
|
||
|
|
|
||
|
|
class OSS:
|
||
|
|
"""对象存储:上传文件 / 生成访问 URL。配置了 OSS_ENDPOINT 才启用,否则落本地上传目录。"""
|
||
|
|
|
||
|
|
def __init__(self) -> None:
|
||
|
|
self._client: Optional[object] = None
|
||
|
|
self.enabled = bool(config.OSS_ENDPOINT)
|
||
|
|
self.bucket = config.OSS_BUCKET
|
||
|
|
self.local_dir = Path(__file__).resolve().parent.parent.parent / "uploads"
|
||
|
|
self.local_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
async def _get_client(self):
|
||
|
|
if self._client is None:
|
||
|
|
import aioboto3
|
||
|
|
|
||
|
|
session = aioboto3.Session(
|
||
|
|
aws_access_key_id=config.OSS_ACCESS_KEY,
|
||
|
|
aws_secret_access_key=config.OSS_SECRET_KEY,
|
||
|
|
)
|
||
|
|
self._client = session.client(
|
||
|
|
"s3",
|
||
|
|
endpoint_url=config.OSS_ENDPOINT,
|
||
|
|
region_name="oss-cn-hangzhou",
|
||
|
|
)
|
||
|
|
return self._client
|
||
|
|
|
||
|
|
async def upload(self, key: str, data: bytes, content_type: str = "application/octet-stream") -> str:
|
||
|
|
"""上传对象,返回可访问的相对 URL。"""
|
||
|
|
if self.enabled:
|
||
|
|
client = await self._get_client()
|
||
|
|
await client.put_object(Bucket=self.bucket, Key=key, Body=data, ContentType=content_type)
|
||
|
|
return f"/oss/{key}"
|
||
|
|
# 本地降级
|
||
|
|
dest = self.local_dir / key
|
||
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
dest.write_bytes(data)
|
||
|
|
return f"/uploads/{key}"
|
||
|
|
|
||
|
|
async def presigned_url(self, key: str, expires: int = 3600) -> str:
|
||
|
|
"""生成临时访问 URL(OSS 启用时)。"""
|
||
|
|
if self.enabled:
|
||
|
|
client = await self._get_client()
|
||
|
|
return await client.generate_presigned_url(
|
||
|
|
"get_object", Params={"Bucket": self.bucket, "Key": key}, ExpiresIn=expires
|
||
|
|
)
|
||
|
|
return f"/uploads/{key}"
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def is_configured() -> bool:
|
||
|
|
return bool(config.OSS_ENDPOINT)
|