Files
server-core/app/infrastructure/oss.py
T
Pine 980a2db6d9 refactor: 平台应用异步四层架构(接口/业务/领域/基础设施)
- 基础设施层 async:SQLAlchemy 异步引擎/会话、33 Repository async 化、models/security/seed 迁入 infrastructure、新增 cache.py(redis.asyncio) 与 oss.py(aioboto3)
- 接口层:routers 迁 api/routers 并全 async,dependencies 迁 api/dependencies(get_db/get_current_user async)
- 依赖:sqlalchemy[asyncio]/aiosqlite/asyncmy/redis/aioboto3;config 异步 URL + Redis/OSS 配置
- 删除废弃:旧同步 db/dependencies/repositories/storage
- 验证:平台 19 路由 + 培训 48 路由全注册;/health /auth/login /auth/me /admin/tasks /notifications 等接口 async 可用
2026-08-23 23:52:58 +08:00

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)