jiachenlong/backend/app/services/oss.py

131 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 阿里云OSS服务
import os
import uuid
import datetime
from typing import Optional
import oss2
from PIL import Image
import io
from app.core.config import settings
# 图片压缩配置
IMAGE_CONFIG = {
"max_size": 1024 * 1024, # 1MB
"max_width": 2048,
"max_height": 2048,
"quality": 85,
"format": "JPEG"
}
# 懒加载OSS连接
_oss_bucket = None
def _get_oss_bucket():
"""获取OSS Bucket实例懒加载"""
global _oss_bucket
if _oss_bucket is None:
if not settings.OSS_ACCESS_KEY_ID or not settings.OSS_ACCESS_KEY_SECRET:
raise ValueError("OSS配置未设置请检查环境变量 OSS_ACCESS_KEY_ID 和 OSS_ACCESS_KEY_SECRET")
auth = oss2.Auth(settings.OSS_ACCESS_KEY_ID, settings.OSS_ACCESS_KEY_SECRET)
_oss_bucket = oss2.Bucket(auth, settings.OSS_ENDPOINT, settings.OSS_BUCKET)
return _oss_bucket
def get_oss_path(prefix: str, user_id: str = None, filename: str = None) -> str:
"""生成OSS存储路径"""
now = datetime.datetime.now()
year = now.strftime("%Y")
month = now.strftime("%m")
day = now.strftime("%d")
if filename:
ext = filename.split('.')[-1] if '.' in filename else 'jpg'
unique_name = f"{uuid.uuid4().hex}.{ext}"
else:
unique_name = f"{uuid.uuid4().hex}.jpg"
if user_id:
path = f"{prefix}/{user_id}/{year}/{month}/{unique_name}"
else:
path = f"{prefix}/{year}/{month}/{day}/{unique_name}"
return path, unique_name
def upload_to_oss(file_data: bytes, oss_key: str, compress: bool = True) -> str:
"""上传文件到OSS返回公网URL"""
try:
bucket = _get_oss_bucket()
# 如果是图片,进行压缩
if compress and any(oss_key.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.webp']):
file_data = compress_image(file_data)
# 上传文件
result = bucket.put_object(oss_key, file_data)
if result.status == 200:
# 返回公网URL
return f"https://{settings.OSS_BUCKET}.{settings.OSS_ENDPOINT}/{oss_key}"
else:
raise Exception(f"OSS上传失败: {result.status}")
except Exception as e:
raise Exception(f"OSS上传失败: {str(e)}")
def delete_from_oss(oss_key: str) -> bool:
"""从OSS删除文件"""
try:
bucket = _get_oss_bucket()
result = bucket.delete_object(oss_key)
return result.status == 204
except Exception as e:
print(f"OSS删除失败: {str(e)}")
return False
def get_public_url(oss_key: str) -> str:
"""获取公网URL"""
return f"https://{settings.OSS_BUCKET}.{settings.OSS_ENDPOINT}/{oss_key}"
def compress_image(image_data: bytes, max_size: int = None) -> bytes:
"""压缩图片到指定大小以内"""
if max_size is None:
max_size = IMAGE_CONFIG["max_size"]
# 如果已经小于限制,直接返回
if len(image_data) <= max_size:
return image_data
# 打开图片
img = Image.open(io.BytesIO(image_data))
# 如果是PNG且有透明通道转换为RGB
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# 逐步降低质量直到达到目标大小
quality = 95
compressed_data = image_data
while quality > 30 and len(compressed_data) > max_size:
output = io.BytesIO()
img.save(output, format=IMAGE_CONFIG["format"], quality=quality, optimize=True)
compressed_data = output.getvalue()
quality -= 10
# 如果还是太大,缩小尺寸
if len(compressed_data) > max_size:
width, height = img.size
while len(compressed_data) > max_size and width > 400:
width = int(width * 0.8)
height = int(height * 0.8)
img_resized = img.resize((width, height), Image.Resampling.LANCZOS)
output = io.BytesIO()
img_resized.save(output, format=IMAGE_CONFIG["format"], quality=80, optimize=True)
compressed_data = output.getvalue()
return compressed_data