125 lines
3.8 KiB
Python
125 lines
3.8 KiB
Python
|
|
"""
|
|||
|
|
文件上传路由
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import os
|
|||
|
|
import shutil
|
|||
|
|
from datetime import datetime
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, status
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
|
|||
|
|
from app.core.config import settings
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
router = APIRouter()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class UploadResponse(BaseModel):
|
|||
|
|
"""上传响应模型"""
|
|||
|
|
success: bool
|
|||
|
|
filename: str
|
|||
|
|
file_type: str
|
|||
|
|
original_filename: str
|
|||
|
|
task_description: str
|
|||
|
|
message: Optional[str] = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
class UploadImageResponse(BaseModel):
|
|||
|
|
"""上传图片响应模型"""
|
|||
|
|
success: bool
|
|||
|
|
filename: str
|
|||
|
|
file_type: str
|
|||
|
|
original_filename: str
|
|||
|
|
original_image: str
|
|||
|
|
task_description: str
|
|||
|
|
message: str
|
|||
|
|
|
|||
|
|
|
|||
|
|
def allowed_file(filename: str) -> bool:
|
|||
|
|
"""检查文件是否被允许"""
|
|||
|
|
if '.' not in filename:
|
|||
|
|
return False
|
|||
|
|
ext = filename.rsplit('.', 1)[1].lower()
|
|||
|
|
return ext in settings.ALLOWED_EXTENSIONS
|
|||
|
|
|
|||
|
|
|
|||
|
|
@router.post("/upload", response_model=UploadResponse, summary="上传CSV或图片文件")
|
|||
|
|
async def upload_file(
|
|||
|
|
file: UploadFile = File(...),
|
|||
|
|
task_description: str = Form(default="时间序列数据分析")
|
|||
|
|
) -> dict:
|
|||
|
|
"""
|
|||
|
|
上传数据文件(CSV 或图片)
|
|||
|
|
|
|||
|
|
- **file**: CSV 或图片文件 (PNG, JPG, BMP, TIFF)
|
|||
|
|
- **task_description**: 分析任务描述
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
logger.info(f"=== 上传请求开始 ===")
|
|||
|
|
logger.info(f"文件名: {file.filename}")
|
|||
|
|
logger.info(f"任务描述: {task_description}")
|
|||
|
|
|
|||
|
|
# 检查文件名
|
|||
|
|
if not file.filename:
|
|||
|
|
logger.error("文件名为空")
|
|||
|
|
raise HTTPException(
|
|||
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|||
|
|
detail="没有选择文件"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 检查文件类型
|
|||
|
|
if not allowed_file(file.filename):
|
|||
|
|
logger.error(f"不支持的文件类型: {file.filename}")
|
|||
|
|
raise HTTPException(
|
|||
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|||
|
|
detail=f"不支持的文件类型。允许的类型: {', '.join(settings.ALLOWED_EXTENSIONS)}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 获取文件扩展名
|
|||
|
|
file_ext = file.filename.rsplit('.', 1)[1].lower()
|
|||
|
|
|
|||
|
|
# 生成文件名
|
|||
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|||
|
|
new_filename = f"upload_{timestamp}_{file.filename}"
|
|||
|
|
|
|||
|
|
# 保存文件
|
|||
|
|
file_path = settings.get_upload_path(new_filename)
|
|||
|
|
logger.info(f"保存文件到: {file_path}")
|
|||
|
|
|
|||
|
|
content = await file.read()
|
|||
|
|
with open(file_path, 'wb') as f:
|
|||
|
|
f.write(content)
|
|||
|
|
|
|||
|
|
logger.info(f"文件保存成功,大小: {len(content)} bytes")
|
|||
|
|
|
|||
|
|
# 处理不同的文件类型
|
|||
|
|
if file_ext == 'csv':
|
|||
|
|
logger.info("处理 CSV 文件")
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"filename": new_filename,
|
|||
|
|
"file_type": "csv",
|
|||
|
|
"original_filename": file.filename,
|
|||
|
|
"task_description": task_description
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
logger.warning(f"不支持的文件类型: {file_ext}")
|
|||
|
|
raise HTTPException(
|
|||
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|||
|
|
detail=f"目前只支持 CSV 文件。您上传的是: {file_ext}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except HTTPException:
|
|||
|
|
raise
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"上传处理异常: {str(e)}", exc_info=True)
|
|||
|
|
raise HTTPException(
|
|||
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|||
|
|
detail=str(e)
|
|||
|
|
)
|