from fastapi import APIRouter, Depends, HTTPException, Query, Body, UploadFile, File, Form from sqlalchemy.orm import Session from typing import List, Optional import logging from database import get_db from models.model import Model from schemas import ModelCreate, ModelUpdate, ModelResponse from response import ApiResponse, PageData import requests import os import shutil from pathlib import Path # Configure logging logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/models", tags=["models"]) # Meilisearch 配置(从环境变量读取) MEILISEARCH_URL = os.getenv("MEILISEARCH_URL", "http://localhost:7700") MEILISEARCH_API_KEY = os.getenv("MEILISEARCH_API_KEY", "") MEILISEARCH_INDEX = os.getenv("MEILISEARCH_INDEX", "models") # 文件上传配置 UPLOAD_FOLDER = Path("/data/project/autoeq/measurements") ALLOWED_EXTENSIONS = {'.csv', '.txt', '.json'} # 记录上传路径配置 logger.info(f"UPLOAD_FOLDER configured as: {UPLOAD_FOLDER}") logger.info(f"UPLOAD_FOLDER absolute path: {UPLOAD_FOLDER.absolute()}") @router.get("/", response_model=ApiResponse) def get_models( skip: int = Query(0, ge=0, description="跳过记录数"), limit: int = Query(100, ge=1, le=1000, description="返回记录数"), brand_name: Optional[str] = Query(None, description="按品牌名称模糊查询"), name: Optional[str] = Query(None, description="按型号名称模糊查询"), db: Session = Depends(get_db) ): """获取所有型号列表(支持按品牌名称和型号名称模糊查询)""" try: logger.info(f"Getting models: skip={skip}, limit={limit}, brand_name={brand_name}, name={name}") query = db.query(Model) if brand_name: query = query.filter(Model.brand_name.like(f"%{brand_name}%")) if name: query = query.filter(Model.name.like(f"%{name}%")) total = query.count() models = query.offset(skip).limit(limit).all() logger.info(f"Found {len(models)} models, total={total}") if not models: logger.warning("No models found") return ApiResponse(code=2, msg="empty", data=None) # 转换为字典列表 models_data = [model.to_dict() for model in models] return ApiResponse(code=1, msg="success", data={"items": models_data, "total": total, "skip": skip, "limit": limit}) except Exception as e: logger.error(f"Error getting models: {str(e)}", exc_info=True) return ApiResponse(code=0, msg="error", data=None) @router.get("/{model_id}", response_model=ApiResponse) def get_model(model_id: int, db: Session = Depends(get_db)): """获取单个型号""" try: logger.info(f"Getting model: id={model_id}") model = db.query(Model).filter(Model.id == model_id).first() if not model: logger.warning(f"Model not found: id={model_id}") return ApiResponse(code=2, msg="empty", data=None) logger.info(f"Model found: {model.to_dict()}") return ApiResponse(code=1, msg="success", data=model.to_dict()) except Exception as e: logger.error(f"Error getting model {model_id}: {str(e)}", exc_info=True) return ApiResponse(code=0, msg="error", data=None) @router.post("/", response_model=ApiResponse) def create_model( brand_name: str = Form(...), name: str = Form(...), form: str = Form(None), rig: str = Form(None), source: str = Form(None), eq_key: str = Form(None), measurement_file: UploadFile = File(None), db: Session = Depends(get_db) ): """创建新型号(支持文件上传)""" try: logger.info(f"Creating model: brand_name={brand_name}, name={name}, form={form}, source={source}") # 检查是否已存在 existing = db.query(Model).filter( Model.brand_name == brand_name, Model.name == name ).first() if existing: logger.warning(f"Model already exists: brand_name={brand_name}, name={name}") return ApiResponse(code=0, msg="该品牌下型号名称已存在", data=None) # 处理文件上传 measurement_filename = None if measurement_file and measurement_file.filename: logger.info(f"Uploading measurement file: {measurement_file.filename}") logger.info(f"UPLOAD_FOLDER is: {UPLOAD_FOLDER}") # 验证文件扩展名 file_ext = os.path.splitext(measurement_file.filename)[1].lower() if file_ext not in ALLOWED_EXTENSIONS: logger.error(f"Unsupported file format: {file_ext}") return ApiResponse(code=0, msg=f"不支持的文件格式:{file_ext}", data=None) # 创建保存路径:autoeq/measurements/{source}/data/{form}/{filename} save_dir = UPLOAD_FOLDER / source / "data" / form logger.info(f"Creating directory: {save_dir}") save_dir.mkdir(parents=True, exist_ok=True) # 保存文件(保留原文件名) file_path = save_dir / measurement_file.filename logger.info(f"Saving file to: {file_path}") with open(file_path, "wb") as buffer: shutil.copyfileobj(measurement_file.file, buffer) measurement_filename = measurement_file.filename logger.info(f"File saved: {file_path}") db_model = Model( brand_name=brand_name, name=name, form=form, rig=rig, source=source, eq_key=eq_key ) db.add(db_model) db.commit() db.refresh(db_model) logger.info(f"Model created successfully: id={db_model.id}") return ApiResponse(code=1, msg="success", data=db_model.to_dict()) except Exception as e: logger.error(f"Error creating model: {str(e)}", exc_info=True) db.rollback() return ApiResponse(code=0, msg="error", data=None) @router.put("/{model_id}", response_model=ApiResponse) def update_model(model_id: int, model: ModelUpdate, db: Session = Depends(get_db)): """更新型号""" try: logger.info(f"Updating model: id={model_id}, data={model.dict()}") db_model = db.query(Model).filter(Model.id == model_id).first() if not db_model: logger.warning(f"Model not found: id={model_id}") return ApiResponse(code=2, msg="empty", data=None) # 如果更新品牌或型号名称,检查是否冲突 if model.brand_name or model.name: new_brand_name = model.brand_name or db_model.brand_name new_name = model.name or db_model.name if new_brand_name != db_model.brand_name or new_name != db_model.name: existing = db.query(Model).filter( Model.brand_name == new_brand_name, Model.name == new_name ).first() if existing: logger.warning(f"Model already exists: brand_name={new_brand_name}, name={new_name}") return ApiResponse(code=0, msg="该品牌下型号名称已存在", data=None) # 更新字段 if model.brand_name: db_model.brand_name = model.brand_name if model.name: db_model.name = model.name if model.form is not None: db_model.form = model.form if model.rig is not None: db_model.rig = model.rig if model.source is not None: db_model.source = model.source if model.eq_key is not None: db_model.eq_key = model.eq_key db.commit() db.refresh(db_model) logger.info(f"Model updated successfully: id={db_model.id}") return ApiResponse(code=1, msg="success", data=db_model.to_dict()) except Exception as e: logger.error(f"Error updating model {model_id}: {str(e)}", exc_info=True) db.rollback() return ApiResponse(code=0, msg="error", data=None) @router.delete("/{model_id}", response_model=ApiResponse) def delete_model(model_id: int, db: Session = Depends(get_db)): """删除型号""" try: logger.info(f"Deleting model: id={model_id}") db_model = db.query(Model).filter(Model.id == model_id).first() if not db_model: logger.warning(f"Model not found: id={model_id}") return ApiResponse(code=2, msg="empty", data=None) db.delete(db_model) db.commit() logger.info(f"Model deleted successfully: id={model_id}") return ApiResponse(code=1, msg="success", data=None) except Exception as e: logger.error(f"Error deleting model {model_id}: {str(e)}", exc_info=True) db.rollback() return ApiResponse(code=0, msg="error", data=None) @router.post("/push-to-search", response_model=ApiResponse) def push_to_search( model_ids: List[int] = Body(..., embed=True, description="型号 ID 列表"), db: Session = Depends(get_db) ): """推送型号数据到 Meilisearch""" try: if not model_ids: return ApiResponse(code=0, msg="请选择要推送的型号", data=None) # 查询选中的型号 models = db.query(Model).filter(Model.id.in_(model_ids)).all() if not models: return ApiResponse(code=0, msg="未找到选中的型号数据", data=None) # 准备推送数据 push_data = [] for model in models: model_dict = { 'id': model.id, 'brand_name': model.brand_name, 'name': model.name, 'rig': model.rig, 'form': model.form, 'source': model.source } # 只包含有值的字段 push_data.append({k: v for k, v in model_dict.items() if v is not None}) # 推送到 Meilisearch headers = { "Authorization": f"Bearer {MEILISEARCH_API_KEY}", "Content-Type": "application/json" } # 如果索引不存在,先创建索引 try: # 更新或添加文档 response = requests.post( f"{MEILISEARCH_URL}/indexes/{MEILISEARCH_INDEX}/documents", json=push_data, headers=headers, timeout=30 ) if response.status_code not in [200, 202]: return ApiResponse(code=0, msg=f"推送到 Meilisearch 失败:{response.text}", data=None) task_info = response.json() return ApiResponse( code=1, msg="success", data={ "pushed_count": len(push_data), "task_uid": task_info.get("taskUid"), "models": push_data } ) except requests.exceptions.RequestException as e: return ApiResponse(code=0, msg=f"连接 Meilisearch 失败:{str(e)}", data=None) except Exception as e: return ApiResponse(code=0, msg="error", data=None)