980a2db6d9
- 基础设施层 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 可用
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)
|