From 035b8338cc70cd4da48582bb13b973b451aa557c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Sun, 9 Nov 2025 12:37:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Efolder=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_app.py | 126 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 16 deletions(-) diff --git a/fastapi_app.py b/fastapi_app.py index a07f3f3..22fa0f2 100644 --- a/fastapi_app.py +++ b/fastapi_app.py @@ -8,9 +8,10 @@ import requests import aiohttp from typing import AsyncGenerator, Dict, List, Optional, Union, Any from datetime import datetime +import re import uvicorn -from fastapi import FastAPI, HTTPException, Depends, Header, UploadFile, File +from fastapi import FastAPI, HTTPException, Depends, Header, UploadFile, File, Form from fastapi.responses import StreamingResponse, HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware @@ -51,10 +52,69 @@ from modified_assistant import update_agent_llm from task_queue.manager import queue_manager from task_queue.integration_tasks import process_files_async, process_files_incremental_async, cleanup_project_async from task_queue.task_status import task_status_store -import re os.environ["TOKENIZERS_PARALLELISM"] = "false" + +def get_versioned_filename(upload_dir: str, name_without_ext: str, file_extension: str) -> tuple[str, int]: + """ + 获取带版本号的文件名,自动处理文件删除和版本递增 + + Args: + upload_dir: 上传目录路径 + name_without_ext: 不含扩展名的文件名 + file_extension: 文件扩展名(包含点号) + + Returns: + tuple[str, int]: (最终文件名, 版本号) + """ + # 检查原始文件是否存在 + original_file = os.path.join(upload_dir, name_without_ext + file_extension) + original_exists = os.path.exists(original_file) + + # 查找所有相关的版本化文件 + pattern = re.compile(re.escape(name_without_ext) + r'_(\d+)' + re.escape(file_extension) + r'$') + existing_versions = [] + files_to_delete = [] + + for filename in os.listdir(upload_dir): + # 检查是否是原始文件 + if filename == name_without_ext + file_extension: + files_to_delete.append(filename) + continue + + # 检查是否是版本化文件 + match = pattern.match(filename) + if match: + version_num = int(match.group(1)) + existing_versions.append(version_num) + files_to_delete.append(filename) + + # 如果没有任何相关文件存在,使用原始文件名(版本1) + if not original_exists and not existing_versions: + return name_without_ext + file_extension, 1 + + # 删除所有现有文件(原始文件和版本化文件) + for filename in files_to_delete: + file_to_delete = os.path.join(upload_dir, filename) + try: + os.remove(file_to_delete) + print(f"已删除文件: {file_to_delete}") + except OSError as e: + print(f"删除文件失败 {file_to_delete}: {e}") + + # 确定下一个版本号 + if existing_versions: + next_version = max(existing_versions) + 1 + else: + next_version = 2 + + # 生成带版本号的文件名 + versioned_filename = f"{name_without_ext}_{next_version}{file_extension}" + + return versioned_filename, next_version + + # Custom version for qwen-agent messages - keep this function as it's specific to this app def get_content_from_messages(messages: List[dict], tool_response: bool = True) -> str: """Extract content from qwen-agent messages with special formatting""" @@ -987,11 +1047,12 @@ async def chat_completions_v2(request: ChatRequestV2, authorization: Optional[st @app.post("/api/v1/upload") -async def upload_file(file: UploadFile = File(...), folder: Optional[str] = None): +async def upload_file(file: UploadFile = File(...), folder: Optional[str] = Form(None)): """ 文件上传API接口,上传文件到 ./projects/uploads/ 目录下 可以指定自定义文件夹名,如果不指定则使用日期文件夹 + 指定文件夹时使用原始文件名并支持版本控制 Args: file: 上传的文件 @@ -1001,6 +1062,10 @@ async def upload_file(file: UploadFile = File(...), folder: Optional[str] = None dict: 包含文件路径和文件夹信息的响应 """ try: + # 调试信息 + print(f"Received folder parameter: {folder}") + print(f"File received: {file.filename if file else 'None'}") + # 确定上传文件夹 if folder: # 使用指定的自定义文件夹 @@ -1016,22 +1081,51 @@ async def upload_file(file: UploadFile = File(...), folder: Optional[str] = None upload_dir = os.path.join("projects", "uploads", target_folder) os.makedirs(upload_dir, exist_ok=True) - # 生成唯一文件名 - file_extension = os.path.splitext(file.filename)[1] if file.filename else "" - unique_filename = f"{uuid.uuid4()}{file_extension}" - file_path = os.path.join(upload_dir, unique_filename) + # 处理文件名 + if not file.filename: + raise HTTPException(status_code=400, detail="文件名不能为空") - # 保存文件 - with open(file_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) + # 解析文件名和扩展名 + original_filename = file.filename + name_without_ext, file_extension = os.path.splitext(original_filename) - return { - "success": True, - "message": "文件上传成功", - "file_path": file_path, - "folder": target_folder - } + # 根据是否指定文件夹决定命名策略 + if folder: + # 使用原始文件名,支持版本控制 + final_filename, version = get_versioned_filename(upload_dir, name_without_ext, file_extension) + file_path = os.path.join(upload_dir, final_filename) + # 保存文件 + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + return { + "success": True, + "message": f"文件上传成功{' (版本: ' + str(version) + ')' if version > 1 else ''}", + "file_path": file_path, + "folder": target_folder, + "original_filename": original_filename, + "version": version + } + else: + # 使用UUID唯一文件名(原有逻辑) + unique_filename = f"{uuid.uuid4()}{file_extension}" + file_path = os.path.join(upload_dir, unique_filename) + + # 保存文件 + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + return { + "success": True, + "message": "文件上传成功", + "file_path": file_path, + "folder": target_folder, + "original_filename": original_filename + } + + except HTTPException: + raise except Exception as e: print(f"Error uploading file: {str(e)}") raise HTTPException(status_code=500, detail=f"文件上传失败: {str(e)}")