fix: 修复本地文件无法上传的问题;修复秒传错误的问题。

hsyh
NoahLan 7 months ago
parent 244f9447f1
commit eeeb82ae9b

@ -23,6 +23,8 @@ class LocalStorage(StorageInterface):
def upload(self, file_stream: BinaryIO, key: str, mime_type: Optional[str] = None) -> Dict: def upload(self, file_stream: BinaryIO, key: str, mime_type: Optional[str] = None) -> Dict:
"""上传文件到本地""" """上传文件到本地"""
# 本地文件存储需要移除前缀
key = key.lstrip(self.storage_type + ":")
abs_path = self._abs_path(key) abs_path = self._abs_path(key)
os.makedirs(os.path.dirname(abs_path), exist_ok=True) os.makedirs(os.path.dirname(abs_path), exist_ok=True)

@ -7,7 +7,7 @@ from io import BytesIO
from typing import Dict, Optional from typing import Dict, Optional
from flask import current_app, url_for from flask import current_app, url_for
from sqlalchemy import select from sqlalchemy import select, exists
from iti.applications.common.enums import StatusEnum from iti.applications.common.enums import StatusEnum
from iti.applications.common.exceptions.biz_exp import BizException from iti.applications.common.exceptions.biz_exp import BizException
@ -40,9 +40,12 @@ class SysFileService:
resolved_storage_type = cls._resolve_storage_type(storage_type, directory_id) resolved_storage_type = cls._resolve_storage_type(storage_type, directory_id)
existing = db.session.scalar( existing = db.session.scalar(
select(SysFile) select(
.filter_by(file_hash=file_hash, status=StatusEnum.ENABLED.value) exists(SysFile).where(
.limit(1) SysFile.file_hash == file_hash,
SysFile.status == StatusEnum.ENABLED.value,
)
)
) )
if existing and existing.storage_type == resolved_storage_type: if existing and existing.storage_type == resolved_storage_type:
# 秒传:更新已有记录 # 秒传:更新已有记录
@ -56,7 +59,9 @@ class SysFileService:
ext = os.path.splitext(file.filename or "")[1] ext = os.path.splitext(file.filename or "")[1]
# 为支持多存储类型,前缀加上存储类型,使用冒号分隔,避免唯一索引冲突 # 为支持多存储类型,前缀加上存储类型,使用冒号分隔,避免唯一索引冲突
file_key = f"{resolved_storage_type}:{datetime.now():%Y%m%d}/{file_hash}{ext}" file_key = f"{resolved_storage_type}:{datetime.now():%Y%m%d}/{file_hash}{ext}"
upload_result = storage.upload(BytesIO(file_bytes), file_key, getattr(file, "mimetype", None)) upload_result = storage.upload(
BytesIO(file_bytes), file_key, getattr(file, "mimetype", None)
)
new_file = SysFile( new_file = SysFile(
filename=file.filename, filename=file.filename,
@ -175,7 +180,9 @@ class SysFileService:
file_size=file_size, file_size=file_size,
extension=os.path.splitext(upload_data.get("filename") or "")[1], extension=os.path.splitext(upload_data.get("filename") or "")[1],
storage_type=storage_type, storage_type=storage_type,
storage_info=upload_data.get("storage_info") if storage_type != "local" else None, storage_info=upload_data.get("storage_info")
if storage_type != "local"
else None,
directory_id=upload_data.get("directory_id"), directory_id=upload_data.get("directory_id"),
metadata_=metadata if metadata else None, metadata_=metadata if metadata else None,
status=StatusEnum.ENABLED.value, status=StatusEnum.ENABLED.value,
@ -250,7 +257,9 @@ class SysFileService:
return storage.get_preview_url(file_obj.file_key, expires=3600) return storage.get_preview_url(file_obj.file_key, expires=3600)
@classmethod @classmethod
def get_thumbnail_url(cls, file_id: str, width: int = 200, height: int = 200, mode: str = "fit") -> Optional[str]: def get_thumbnail_url(
cls, file_id: str, width: int = 200, height: int = 200, mode: str = "fit"
) -> Optional[str]:
"""获取缩略图URL """获取缩略图URL
- local: 返回后端缩略图路由 - local: 返回后端缩略图路由
@ -278,31 +287,33 @@ class SysFileService:
) )
@classmethod @classmethod
def get_thumbnail(cls, file_id: str, width: int = 200, height: int = 200, mode: str = "fit") -> BytesIO: def get_thumbnail(
cls, file_id: str, width: int = 200, height: int = 200, mode: str = "fit"
) -> BytesIO:
"""生成缩略图""" """生成缩略图"""
file_obj = cls.get_file_by_id(file_id) file_obj = cls.get_file_by_id(file_id)
# 非图片类型不支持缩略图 # 非图片类型不支持缩略图
if not file_obj.mime_type or not file_obj.mime_type.startswith("image/"): if not file_obj.mime_type or not file_obj.mime_type.startswith("image/"):
raise BizException("该文件类型不支持缩略图", code=400) raise BizException("该文件类型不支持缩略图", code=400)
# OSS 存储:使用 OSS 图片处理 # OSS 存储:使用 OSS 图片处理
if file_obj.storage_type != "local": if file_obj.storage_type != "local":
# 对于 OSS直接重定向到带图片处理参数的URL # 对于 OSS直接重定向到带图片处理参数的URL
# TODO: 各OSS需要实现 get_thumbnail_url 方法 # TODO: 各OSS需要实现 get_thumbnail_url 方法
return cls.download_file(file_id) return cls.download_file(file_id)
# 本地存储:使用 Pillow 生成缩略图 # 本地存储:使用 Pillow 生成缩略图
try: try:
from PIL import Image from PIL import Image
from io import BytesIO as PILBytesIO from io import BytesIO as PILBytesIO
except ImportError: except ImportError:
raise BizException("需要安装 Pillow: pip install Pillow", code=500) raise BizException("需要安装 Pillow: pip install Pillow", code=500)
# 下载原图 # 下载原图
file_stream = cls.download_file(file_id) file_stream = cls.download_file(file_id)
img = Image.open(file_stream) img = Image.open(file_stream)
# 转换为 RGB处理 PNG 透明通道等) # 转换为 RGB处理 PNG 透明通道等)
if img.mode in ("RGBA", "LA", "P"): if img.mode in ("RGBA", "LA", "P"):
background = Image.new("RGB", img.size, (255, 255, 255)) background = Image.new("RGB", img.size, (255, 255, 255))
@ -312,7 +323,7 @@ class SysFileService:
img = background img = background
elif img.mode != "RGB": elif img.mode != "RGB":
img = img.convert("RGB") img = img.convert("RGB")
# 根据模式调整大小 # 根据模式调整大小
if mode == "fill": if mode == "fill":
# 填充模式:裁剪居中 # 填充模式:裁剪居中
@ -330,12 +341,12 @@ class SysFileService:
else: # fit (默认) else: # fit (默认)
# 适应模式:保持比例 # 适应模式:保持比例
img.thumbnail((width, height), Image.Resampling.LANCZOS) img.thumbnail((width, height), Image.Resampling.LANCZOS)
# 保存为 JPEG # 保存为 JPEG
output = PILBytesIO() output = PILBytesIO()
img.save(output, format="JPEG", quality=85, optimize=True) img.save(output, format="JPEG", quality=85, optimize=True)
output.seek(0) output.seek(0)
return output return output
@classmethod @classmethod

Loading…
Cancel
Save