upload project source code
This commit is contained in:
211
后端源码/yifan.action-ai.cn/api/app/utils/oss_util.py
Normal file
211
后端源码/yifan.action-ai.cn/api/app/utils/oss_util.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import oss2
|
||||
import random
|
||||
from datetime import datetime
|
||||
from app.utils.time_util import TimeUtil
|
||||
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 = TimeUtil.now_u8().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 = TimeUtil.now_u8().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:
|
||||
# 生成访问URL(OSS访问域名/文件路径信息)
|
||||
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
|
||||
Reference in New Issue
Block a user