Files
dashboard/backend/routes/models.py
T
yangy 90aec37461 Add initial files for Audio Dashboard Management System
- Created .env.example for environment variable configuration.
- Added docker-compose.yml for service orchestration.
- Implemented frequency response extraction in extract_frequency_response.py and convert_to_frequency_db.py.
- Generated output files: frequency_response_detailed.json, frequency_response_points.json, frequency_response.csv, and frequency_response_curve.png.
- Included sample measurement data for FiiO FA19 in CSV format.
2026-03-17 14:20:08 +08:00

278 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(__file__).parent.parent.parent / "autoeq" / "measurements"
ALLOWED_EXTENSIONS = {'.csv', '.txt', '.json'}
@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}")
# 验证文件扩展名
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
save_dir.mkdir(parents=True, exist_ok=True)
# 保存文件(保留原文件名)
file_path = save_dir / measurement_file.filename
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)