abd44fe467
- Updated docker-compose.yml to rename backend and frontend container names for clarity. - Added volume mappings for frontend and backend services to persist data. - Removed obsolete files related to frequency response extraction, including Python scripts and output data files. - Adjusted backend port configuration in main.py to align with new service architecture. - Enhanced logging for file upload paths in models.py for better traceability.
285 lines
11 KiB
Python
285 lines
11 KiB
Python
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)
|