This commit is contained in:
Lan
2025-05-21 00:14:40 +08:00
parent 136ea5830f
commit eeb70c7f44
4 changed files with 50 additions and 14 deletions
View File
+5 -5
View File
@@ -9,7 +9,7 @@ from typing import Optional, Tuple
from apps.base.dependencies import IPRateLimit from apps.base.dependencies import IPRateLimit
from apps.base.models import FileCodes from apps.base.models import FileCodes
from core.settings import settings from core.settings import settings
from core.utils import get_random_num, get_random_string, max_save_times_desc from core.utils import get_random_num, get_random_string, max_save_times_desc, sanitize_filename
async def get_file_path_name(file: UploadFile) -> Tuple[str, str, str, str, str]: async def get_file_path_name(file: UploadFile) -> Tuple[str, str, str, str, str]:
@@ -17,17 +17,17 @@ async def get_file_path_name(file: UploadFile) -> Tuple[str, str, str, str, str]
today = datetime.datetime.now() today = datetime.datetime.now()
storage_path = settings.storage_path.strip("/") # 移除开头和结尾的斜杠 storage_path = settings.storage_path.strip("/") # 移除开头和结尾的斜杠
file_uuid = uuid.uuid4().hex file_uuid = uuid.uuid4().hex
filename = await sanitize_filename(file.filename)
# 使用 UUID 作为子目录名 # 使用 UUID 作为子目录名
base_path = f"share/data/{today.strftime('%Y/%m/%d')}/{file_uuid}" base_path = f"share/data/{today.strftime('%Y/%m/%d')}/{file_uuid}"
# 如果设置了存储路径,将其添加到基础路径中 # 如果设置了存储路径,将其添加到基础路径中
path = f"{storage_path}/{base_path}" if storage_path else base_path path = f"{storage_path}/{base_path}" if storage_path else base_path
prefix, suffix = os.path.splitext(file.filename) prefix, suffix = os.path.splitext(filename)
# 保持原始文件名 # 保持原始文件名
save_path = f"{path}/{file.filename}" save_path = f"{path}/{filename}"
return path, suffix, prefix, file.filename, save_path return path, suffix, prefix, filename, save_path
async def get_chunk_file_path_name(file_name: str, upload_id: str) -> Tuple[str, str, str, str, str]: async def get_chunk_file_path_name(file_name: str, upload_id: str) -> Tuple[str, str, str, str, str]:
+19 -9
View File
@@ -22,7 +22,7 @@ from fastapi import HTTPException, Response, UploadFile
from core.response import APIResponse from core.response import APIResponse
from core.settings import data_root, settings from core.settings import data_root, settings
from apps.base.models import FileCodes, UploadChunk from apps.base.models import FileCodes, UploadChunk
from core.utils import get_file_url from core.utils import get_file_url, sanitize_filename
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@@ -109,10 +109,16 @@ class SystemFileStorage(FileStorageInterface):
chunk = file.read(self.chunk_size) chunk = file.read(self.chunk_size)
async def save_file(self, file: UploadFile, save_path: str): async def save_file(self, file: UploadFile, save_path: str):
save_path = self.root_path / save_path path_obj = Path(save_path)
if not save_path.parent.exists(): directory = str(path_obj.parent)
save_path.parent.mkdir(parents=True) # 提取原始文件名并进行清理
await asyncio.to_thread(self._save, file.file, save_path) filename = await sanitize_filename(path_obj.name)
# 构建安全的完整保存路径
safe_save_path = self.root_path / directory / filename
# 确保目录存在
if not safe_save_path.parent.exists():
safe_save_path.parent.mkdir(parents=True)
await asyncio.to_thread(self._save, file.file, safe_save_path)
async def delete_file(self, file_code: FileCodes): async def delete_file(self, file_code: FileCodes):
save_path = self.root_path / await file_code.get_file_path() save_path = self.root_path / await file_code.get_file_path()
@@ -447,7 +453,7 @@ class OneDriveFileStorage(FileStorageInterface):
def _save(self, file, save_path): def _save(self, file, save_path):
content = file.file.read() content = file.file.read()
name = file.filename name = save_path(file.filename)
path = self._get_path_str(save_path) path = self._get_path_str(save_path)
self.root_path.get_by_path(path).upload(name, content).execute_query() self.root_path.get_by_path(path).upload(name, content).execute_query()
@@ -627,16 +633,20 @@ class WebDAVFileStorage(FileStorageInterface):
async def save_file(self, file: UploadFile, save_path: str): async def save_file(self, file: UploadFile, save_path: str):
"""保存文件(自动创建目录)""" """保存文件(自动创建目录)"""
# 分离文件名和目录路径
path_obj = Path(save_path) path_obj = Path(save_path)
directory_path = str(path_obj.parent) directory_path = str(path_obj.parent)
# 提取原始文件名并进行清理
filename = await sanitize_filename(path_obj.name)
# 构建安全的保存路径
safe_save_path = str(Path(directory_path) / filename)
try: try:
# 先创建目录结构 # 先创建目录结构
await self._mkdir_p(directory_path) await self._mkdir_p(directory_path)
# 上传文件 # 上传文件
url = self._build_url(save_path) url = self._build_url(safe_save_path)
async with aiohttp.ClientSession(auth=self.auth) as session: async with aiohttp.ClientSession(auth=self.auth) as session:
content = await file.read() # 注意:大文件需要分块读取 content = await file.read()
async with session.put( async with session.put(
url, data=content, headers={ url, data=content, headers={
"Content-Type": file.content_type} "Content-Type": file.content_type}
+26
View File
@@ -4,7 +4,9 @@
# @Software: PyCharm # @Software: PyCharm
import datetime import datetime
import hashlib import hashlib
import os
import random import random
import re
import string import string
import time import time
from core.settings import settings from core.settings import settings
@@ -92,3 +94,27 @@ async def max_save_times_desc(max_save_seconds: int):
desc_zh += gen_desc_zh(max_timedelta.seconds % 60, "") desc_zh += gen_desc_zh(max_timedelta.seconds % 60, "")
desc_en += gen_desc_en(max_timedelta.seconds % 60, "second") desc_en += gen_desc_en(max_timedelta.seconds % 60, "second")
return desc_zh, desc_en return desc_zh, desc_en
async def sanitize_filename(filename: str) -> str:
"""
安全处理文件名:
1. 剥离路径只保留文件名
2. 替换非法字符
3. 处理空文件名情况
"""
filename = os.path.basename(filename)
illegal_chars = r'[\\/*?:"<>|\x00-\x1F]' # 包含控制字符
# 替换非法字符为下划线
cleaned = re.sub(illegal_chars, '_', filename)
# 处理空格(可选替换为_
cleaned = cleaned.replace(' ', '_')
# 处理连续下划线
cleaned = re.sub(r'_+', '_', cleaned)
# 处理首尾特殊字符
cleaned = cleaned.strip('._')
# 处理空文件名情况
if not cleaned:
cleaned = 'unnamed_file'
# 长度限制(按需调整)
return cleaned[:255]