Files
----/后端源码/yifan.action-ai.cn/app/utils/oss_util.py

211 lines
6.7 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.
# -*- coding: utf-8 -*-
import oss2
import random
from datetime import datetime
from fastapi import UploadFile
from pathlib import Path
from urllib.parse import urljoin
from app.config.setting import settings
from app.core.exceptions import CustomException
from app.core.logger import log
class OSSUtil:
"""
阿里云OSS上传工具类
"""
def __init__(self):
"""初始化OSS客户端"""
try:
# 创建Bucket对象所有Object相关的接口都可以通过Bucket对象来进行
auth = oss2.Auth(settings.OSS_ACCESS_KEY_ID, settings.OSS_ACCESS_KEY_SECRET)
self.bucket = oss2.Bucket(auth, settings.OSS_ENDPOINT, settings.OSS_BUCKET_NAME)
log.info("OSS客户端初始化成功")
except Exception as e:
log.error(f"OSS客户端初始化失败: {e}")
raise CustomException(msg="OSS服务初始化失败")
@staticmethod
def generate_random_number() -> str:
"""
生成3位随机数字字符串。
返回:
- str: 三位随机数字字符串。
"""
return f'{random.randint(1, 999):03}'
@staticmethod
def check_file_extension(file: UploadFile) -> bool:
"""
检查文件后缀是否合法。
参数:
- file (UploadFile): 上传的文件对象。
返回:
- bool: 文件后缀是否合法。
异常:
- CustomException: 文件类型不支持时抛出。
"""
if file.content_type and file.filename:
# 优先使用文件名的扩展名
file_extension = '.' + file.filename.rsplit('.', 1)[-1].lower() if '.' in file.filename else None
if file_extension and file_extension in settings.ALLOWED_EXTENSIONS:
return True
raise CustomException(msg="文件类型不支持")
else:
raise CustomException(msg="文件类型不支持")
@staticmethod
def check_file_size(file: UploadFile) -> bool:
"""
校验文件大小是否合法。
参数:
- file (UploadFile): 上传的文件对象。
返回:
- bool: 文件大小是否合法(未提供 size 返回 False
"""
if file.size:
return file.size <= settings.MAX_FILE_SIZE
else:
return False
@classmethod
def generate_file_name(cls, filename: str) -> str:
"""
生成文件名称。
参数:
- filename (str): 原始文件名(包含拓展名)。
返回:
- str: 生成的文件名(包含时间戳、机器码、随机码)。
"""
name, ext = filename.rsplit(".", 1)
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
return f'{name}_{timestamp}{settings.UPLOAD_MACHINE}{cls.generate_random_number()}.{ext}'
@classmethod
def generate_oss_key(cls, filename: str) -> str:
"""
生成OSS对象键路径
参数:
- filename (str): 文件名
返回:
- str: OSS对象键格式如: upload/2026/02/08/filename.jpg
"""
date_path = datetime.now().strftime("%Y/%m/%d")
return f"upload/{date_path}/{filename}"
# 上传文件至OSS方法
async def upload_file(self, file: UploadFile) -> tuple[str, str, str]:
"""
上传文件到阿里云OSS
参数:
- file (UploadFile): 上传的文件对象。
返回:
- tuple[str, str, str]: (文件名, OSS对象键, 文件访问URL)。
异常:
- CustomException: 当文件类型不支持或大小超限时抛出。
"""
# 文件校验(校验文件大小、校验文件后缀)
if not all([self.check_file_extension(file), self.check_file_size(file)]):
raise CustomException(msg='文件类型或大小不合法')
try:
# 生成文件名
if not file.filename:
raise CustomException(msg='文件名不能为空')
# 生成文件名称
filename = self.generate_file_name(file.filename)
# 生成oss文件路径格式如: upload/2026/02/08/filename.jpg
oss_key = self.generate_oss_key(filename)
# 读取文件内容
file_content = await file.read()
# 上传到OSS
result = self.bucket.put_object(oss_key, file_content)
if result.status == 200:
# 生成访问URLOSS访问域名/文件路径信息)
file_url = f"{settings.OSS_DOMAIN}/{oss_key}"
log.info(f"文件上传OSS成功: {oss_key}")
return filename, oss_key, file_url
else:
log.error(f"OSS上传失败状态码: {result.status}")
raise CustomException(msg='文件上传失败')
except oss2.exceptions.OssError as e:
log.error(f"OSS上传异常: {e}")
raise CustomException(msg=f'OSS上传失败: {e}')
except Exception as e:
log.error(f"文件上传失败: {e}")
raise CustomException(msg='文件上传失败')
def delete_file(self, oss_key: str) -> bool:
"""
删除OSS中的文件
参数:
- oss_key (str): OSS对象键
返回:
- bool: 删除是否成功
"""
try:
result = self.bucket.delete_object(oss_key)
if result.status == 204:
log.info(f"OSS文件删除成功: {oss_key}")
return True
else:
log.error(f"OSS文件删除失败状态码: {result.status}")
return False
except oss2.exceptions.OssError as e:
log.error(f"OSS删除异常: {e}")
return False
except Exception as e:
log.error(f"文件删除失败: {e}")
return False
def get_file_url(self, oss_key: str) -> str:
"""
获取文件访问URL
参数:
- oss_key (str): OSS对象键
返回:
- str: 文件访问URL
"""
return f"{settings.OSS_DOMAIN}/{oss_key}"
def file_exists(self, oss_key: str) -> bool:
"""
检查文件是否存在
参数:
- oss_key (str): OSS对象键
返回:
- bool: 文件是否存在
"""
try:
return self.bucket.object_exists(oss_key)
except Exception as e:
log.error(f"检查文件存在性失败: {e}")
return False