taibai_shopping/app/utils/file_upload.py
2025-07-04 19:07:35 +08:00

429 lines
12 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.

"""
文件上传处理工具
"""
import os
import magic
from PIL import Image
from io import BytesIO
from werkzeug.utils import secure_filename
from config.cos_config import COSConfig
from .cos_client import cos_client
class FileUploadHandler:
"""文件上传处理器"""
@staticmethod
def validate_file(file_obj, file_type='image'):
"""
验证文件
Args:
file_obj: 文件对象
file_type: 文件类型 (image, file)
Returns:
dict: 验证结果
"""
if not file_obj or not file_obj.filename:
return {'valid': False, 'error': '请选择文件'}
# 检查文件扩展名
filename = secure_filename(file_obj.filename)
if '.' not in filename:
return {'valid': False, 'error': '文件格式不正确'}
file_ext = filename.rsplit('.', 1)[1].lower()
if file_type == 'image':
allowed_extensions = COSConfig.ALLOWED_IMAGE_EXTENSIONS
max_size = COSConfig.MAX_IMAGE_SIZE
else:
allowed_extensions = COSConfig.ALLOWED_FILE_EXTENSIONS
max_size = COSConfig.MAX_FILE_SIZE
if file_ext not in allowed_extensions:
return {
'valid': False,
'error': f'不支持的文件格式,支持格式: {", ".join(allowed_extensions)}'
}
# 检查文件大小
file_obj.seek(0, 2) # 移动到文件末尾
file_size = file_obj.tell()
file_obj.seek(0) # 重置文件指针
if file_size > max_size:
max_size_mb = max_size / (1024 * 1024)
return {'valid': False, 'error': f'文件大小不能超过 {max_size_mb:.1f}MB'}
# 验证文件内容类型(防止恶意文件)
try:
file_content = file_obj.read(1024) # 读取前1KB用于检测
file_obj.seek(0) # 重置文件指针
mime_type = magic.from_buffer(file_content, mime=True)
if file_type == 'image' and not mime_type.startswith('image/'):
return {'valid': False, 'error': '文件内容不是有效的图片格式'}
except Exception:
# 如果magic检测失败继续处理某些环境可能没有libmagic
pass
return {'valid': True, 'filename': filename, 'size': file_size}
@staticmethod
def process_image(file_obj, max_width=1200, max_height=1200, quality=None):
"""
处理图片(压缩、调整尺寸)
Args:
file_obj: 图片文件对象
max_width: 最大宽度
max_height: 最大高度
quality: 压缩质量
Returns:
BytesIO: 处理后的图片数据
"""
try:
# 打开图片
image = Image.open(file_obj)
# 转换RGBA到RGB处理PNG透明背景
if image.mode in ('RGBA', 'LA', 'P'):
background = Image.new('RGB', image.size, (255, 255, 255))
if image.mode == 'P':
image = image.convert('RGBA')
background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = background
# 调整图片尺寸
if image.width > max_width or image.height > max_height:
image.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
# 保存处理后的图片
output = BytesIO()
image.save(
output,
format='JPEG',
quality=quality or COSConfig.IMAGE_QUALITY,
optimize=True
)
output.seek(0)
return output
except Exception as e:
raise Exception(f"图片处理失败: {str(e)}")
@staticmethod
def upload_image(file_obj, folder_type='temp', process_image=True):
"""
上传图片到COS
Args:
file_obj: 图片文件对象
folder_type: 存储文件夹类型
process_image: 是否处理图片
Returns:
dict: 上传结果
"""
# 验证文件
validation = FileUploadHandler.validate_file(file_obj, 'image')
if not validation['valid']:
return {
'success': False,
'error': validation['error'],
'url': None,
'file_key': None
}
try:
# 处理图片
if process_image:
processed_file = FileUploadHandler.process_image(file_obj)
upload_file = processed_file
else:
file_obj.seek(0)
upload_file = file_obj
# 上传到COS
result = cos_client.upload_file(
upload_file,
folder_type,
validation['filename']
)
return result
except Exception as e:
return {
'success': False,
'error': f"上传失败: {str(e)}",
'url': None,
'file_key': None
}
@staticmethod
def upload_file(file_obj, folder_type='temp'):
"""
上传普通文件到COS
Args:
file_obj: 文件对象
folder_type: 存储文件夹类型
Returns:
dict: 上传结果
"""
# 验证文件
validation = FileUploadHandler.validate_file(file_obj, 'file')
if not validation['valid']:
return {
'success': False,
'error': validation['error'],
'url': None,
'file_key': None
}
try:
file_obj.seek(0)
# 上传到COS
result = cos_client.upload_file(
file_obj,
folder_type,
validation['filename']
)
return result
except Exception as e:
return {
'success': False,
'error': f"上传失败: {str(e)}",
'url': None,
'file_key': None
}
# 创建全局文件上传处理器实例
file_upload_handler = FileUploadHandler()
"""
文件上传处理工具
"""
import os
import magic
from PIL import Image
from io import BytesIO
from werkzeug.utils import secure_filename
from config.cos_config import COSConfig
from .cos_client import cos_client
class FileUploadHandler:
"""文件上传处理器"""
@staticmethod
def validate_file(file_obj, file_type='image'):
"""
验证文件
Args:
file_obj: 文件对象
file_type: 文件类型 (image, file)
Returns:
dict: 验证结果
"""
if not file_obj or not file_obj.filename:
return {'valid': False, 'error': '请选择文件'}
# 检查文件扩展名
filename = secure_filename(file_obj.filename)
if '.' not in filename:
return {'valid': False, 'error': '文件格式不正确'}
file_ext = filename.rsplit('.', 1)[1].lower()
if file_type == 'image':
allowed_extensions = COSConfig.ALLOWED_IMAGE_EXTENSIONS
max_size = COSConfig.MAX_IMAGE_SIZE
else:
allowed_extensions = COSConfig.ALLOWED_FILE_EXTENSIONS
max_size = COSConfig.MAX_FILE_SIZE
if file_ext not in allowed_extensions:
return {
'valid': False,
'error': f'不支持的文件格式,支持格式: {", ".join(allowed_extensions)}'
}
# 检查文件大小
file_obj.seek(0, 2) # 移动到文件末尾
file_size = file_obj.tell()
file_obj.seek(0) # 重置文件指针
if file_size > max_size:
max_size_mb = max_size / (1024 * 1024)
return {'valid': False, 'error': f'文件大小不能超过 {max_size_mb:.1f}MB'}
# 验证文件内容类型(防止恶意文件)
try:
file_content = file_obj.read(1024) # 读取前1KB用于检测
file_obj.seek(0) # 重置文件指针
mime_type = magic.from_buffer(file_content, mime=True)
if file_type == 'image' and not mime_type.startswith('image/'):
return {'valid': False, 'error': '文件内容不是有效的图片格式'}
except Exception:
# 如果magic检测失败继续处理某些环境可能没有libmagic
pass
return {'valid': True, 'filename': filename, 'size': file_size}
@staticmethod
def process_image(file_obj, max_width=1200, max_height=1200, quality=None):
"""
处理图片(压缩、调整尺寸)
Args:
file_obj: 图片文件对象
max_width: 最大宽度
max_height: 最大高度
quality: 压缩质量
Returns:
BytesIO: 处理后的图片数据
"""
try:
# 打开图片
image = Image.open(file_obj)
# 转换RGBA到RGB处理PNG透明背景
if image.mode in ('RGBA', 'LA', 'P'):
background = Image.new('RGB', image.size, (255, 255, 255))
if image.mode == 'P':
image = image.convert('RGBA')
background.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = background
# 调整图片尺寸
if image.width > max_width or image.height > max_height:
image.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
# 保存处理后的图片
output = BytesIO()
image.save(
output,
format='JPEG',
quality=quality or COSConfig.IMAGE_QUALITY,
optimize=True
)
output.seek(0)
return output
except Exception as e:
raise Exception(f"图片处理失败: {str(e)}")
@staticmethod
def upload_image(file_obj, folder_type='temp', process_image=True):
"""
上传图片到COS
Args:
file_obj: 图片文件对象
folder_type: 存储文件夹类型
process_image: 是否处理图片
Returns:
dict: 上传结果
"""
# 验证文件
validation = FileUploadHandler.validate_file(file_obj, 'image')
if not validation['valid']:
return {
'success': False,
'error': validation['error'],
'url': None,
'file_key': None
}
try:
# 处理图片
if process_image:
processed_file = FileUploadHandler.process_image(file_obj)
upload_file = processed_file
else:
file_obj.seek(0)
upload_file = file_obj
# 上传到COS
result = cos_client.upload_file(
upload_file,
folder_type,
validation['filename']
)
return result
except Exception as e:
return {
'success': False,
'error': f"上传失败: {str(e)}",
'url': None,
'file_key': None
}
@staticmethod
def upload_file(file_obj, folder_type='temp'):
"""
上传普通文件到COS
Args:
file_obj: 文件对象
folder_type: 存储文件夹类型
Returns:
dict: 上传结果
"""
# 验证文件
validation = FileUploadHandler.validate_file(file_obj, 'file')
if not validation['valid']:
return {
'success': False,
'error': validation['error'],
'url': None,
'file_key': None
}
try:
file_obj.seek(0)
# 上传到COS
result = cos_client.upload_file(
file_obj,
folder_type,
validation['filename']
)
return result
except Exception as e:
return {
'success': False,
'error': f"上传失败: {str(e)}",
'url': None,
'file_key': None
}
# 创建全局文件上传处理器实例
file_upload_handler = FileUploadHandler()