Initial commit: Audio Dashboard Management System
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# Database Configuration
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=3306
|
||||
DATABASE_NAME=audio
|
||||
DATABASE_USER=root
|
||||
DATABASE_PASSWORD=root1
|
||||
|
||||
# Application Settings
|
||||
APP_NAME=Audio Dashboard API
|
||||
DEBUG=True
|
||||
@@ -0,0 +1,38 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -0,0 +1,98 @@
|
||||
# Audio Dashboard Backend
|
||||
|
||||
耳机品牌与型号管理平台后端 API
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **FastAPI** - 现代高性能 Web 框架
|
||||
- **SQLAlchemy** - ORM 框架
|
||||
- **PyMySQL** - MySQL 数据库驱动
|
||||
- **Pydantic** - 数据验证
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── main.py # 应用入口
|
||||
├── database.py # 数据库配置
|
||||
├── models/ # 数据模型
|
||||
│ ├── __init__.py
|
||||
│ ├── brand.py # 品牌模型
|
||||
│ └── model.py # 型号模型
|
||||
├── routes/ # API 路由
|
||||
│ ├── __init__.py
|
||||
│ ├── brands.py # 品牌 API
|
||||
│ └── models.py # 型号 API
|
||||
├── schemas.py # Pydantic 模式
|
||||
├── requirements.txt # 依赖
|
||||
├── .env # 环境变量
|
||||
└── .env.example # 环境变量示例
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
1. 创建虚拟环境(可选但推荐):
|
||||
```bash
|
||||
python -m venv venv
|
||||
# Windows
|
||||
venv\Scripts\activate
|
||||
# Linux/Mac
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. 配置环境变量:
|
||||
复制 `.env.example` 为 `.env` 并根据需要修改配置
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
python main.py
|
||||
|
||||
# 或使用 uvicorn
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
## API 文档
|
||||
|
||||
启动后访问:
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- ReDoc: http://localhost:8000/redoc
|
||||
|
||||
## API 端点
|
||||
|
||||
### 品牌管理
|
||||
- `GET /api/brands/` - 获取品牌列表
|
||||
- `GET /api/brands/{id}` - 获取单个品牌
|
||||
- `POST /api/brands/` - 创建品牌
|
||||
- `PUT /api/brands/{id}` - 更新品牌
|
||||
- `DELETE /api/brands/{id}` - 删除品牌
|
||||
|
||||
### 型号管理
|
||||
- `GET /api/models/` - 获取型号列表
|
||||
- `GET /api/models/{id}` - 获取单个型号
|
||||
- `POST /api/models/` - 创建型号
|
||||
- `PUT /api/models/{id}` - 更新型号
|
||||
- `DELETE /api/models/{id}` - 删除型号
|
||||
|
||||
## 数据库配置
|
||||
|
||||
默认配置:
|
||||
- Host: localhost
|
||||
- Port: 3306
|
||||
- Database: audio
|
||||
- User: root
|
||||
- Password: root1
|
||||
|
||||
可在 `.env` 文件中修改配置。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 首次运行前请确保数据库已创建且表结构存在
|
||||
2. 生产环境请修改 CORS 配置,限制允许的域名
|
||||
3. 生产环境请使用更安全的密码管理方式
|
||||
@@ -0,0 +1,39 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Database configuration
|
||||
DATABASE_HOST = os.getenv("DATABASE_HOST", "localhost")
|
||||
DATABASE_PORT = os.getenv("DATABASE_PORT", "3306")
|
||||
DATABASE_NAME = os.getenv("DATABASE_NAME", "audio")
|
||||
DATABASE_USER = os.getenv("DATABASE_USER", "root")
|
||||
DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD", "root123")
|
||||
|
||||
# Create database URL (same format as working code)
|
||||
DATABASE_URL = f"mysql+pymysql://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}?charset=utf8mb4"
|
||||
|
||||
# Create engine (same config as working code)
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
echo=os.getenv("DEBUG", "False") == "True"
|
||||
)
|
||||
|
||||
# Create session factory
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base class for models
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Database session dependency"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,63 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from database import engine, Base
|
||||
from routes import brands_router, models_router
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Create database tables (only if tables don't exist)
|
||||
try:
|
||||
Base.metadata.create_all(bind=engine)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not create tables: {e}")
|
||||
print("Continuing without table creation...")
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
title=os.getenv("APP_NAME", "Audio Dashboard API"),
|
||||
description="耳机品牌与型号管理平台后端 API",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 生产环境应该限制具体域名
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(brands_router)
|
||||
app.include_router(models_router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
"""根路径"""
|
||||
return {
|
||||
"message": "欢迎使用 Audio Dashboard API",
|
||||
"docs": "/docs",
|
||||
"redoc": "/redoc"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
"""健康检查"""
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8002,
|
||||
reload=False
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
from models.brand import Brand
|
||||
from models.model import Model
|
||||
|
||||
__all__ = ["Brand", "Model"]
|
||||
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from database import Base
|
||||
|
||||
|
||||
class Brand(Base):
|
||||
"""耳机品牌模型"""
|
||||
__tablename__ = "brand"
|
||||
__table_args__ = {"comment": "耳机品牌","extend_existing": True}
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment="品牌 ID")
|
||||
name = Column(String(100), unique=True, nullable=False, comment="品牌名称")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Brand(id={self.id}, name='{self.name}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, func
|
||||
from database import Base
|
||||
|
||||
|
||||
class Model(Base):
|
||||
"""耳机型号模型"""
|
||||
__tablename__ = "model"
|
||||
__table_args__ = {"comment": "耳机型号"}
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, comment="型号 ID")
|
||||
brand_name = Column(String(100), nullable=False, comment="品牌名称")
|
||||
name = Column(String(100), nullable=False, comment="型号名称")
|
||||
form = Column(String(100), nullable=True, comment="形式")
|
||||
rig = Column(String(100), nullable=True, comment="阻抗")
|
||||
source = Column(String(100), nullable=True, comment="来源")
|
||||
eq_key = Column(String(255), nullable=True, comment="EQ 键")
|
||||
create_at = Column(DateTime, nullable=False, default=func.now(), comment="创建时间")
|
||||
|
||||
__table_args__ = (
|
||||
{"comment": "耳机型号"},
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Model(id={self.id}, brand_name='{self.brand_name}', name='{self.name}')>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"brand_name": self.brand_name,
|
||||
"name": self.name,
|
||||
"form": self.form,
|
||||
"rig": self.rig,
|
||||
"source": self.source,
|
||||
"eq_key": self.eq_key,
|
||||
"create_at": self.create_at.isoformat() if self.create_at else None
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fastapi==0.104.1
|
||||
sqlalchemy==2.0.23
|
||||
pymysql==1.1.0
|
||||
cryptography==41.0.7
|
||||
pydantic==2.5.2
|
||||
uvicorn==0.24.0
|
||||
python-dotenv==1.0.0
|
||||
@@ -0,0 +1,36 @@
|
||||
from typing import Optional, Any, List, Union
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class PageData(BaseModel):
|
||||
"""分页数据"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
items: List[Any]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
|
||||
class ApiResponse(BaseModel):
|
||||
"""统一 API 响应格式"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
code: int = 0
|
||||
msg: str = "success"
|
||||
data: Optional[Any] = None
|
||||
|
||||
@classmethod
|
||||
def success(cls, data: Any = None, msg: str = "success"):
|
||||
"""成功响应"""
|
||||
return cls(code=1, msg=msg, data=data)
|
||||
|
||||
@classmethod
|
||||
def error(cls, msg: str = "error", code: int = 0):
|
||||
"""错误响应"""
|
||||
return cls(code=code, msg=msg, data=None)
|
||||
|
||||
@classmethod
|
||||
def no_data(cls, msg: str = "no data"):
|
||||
"""无数据响应"""
|
||||
return cls(code=2, msg=msg, data=None)
|
||||
@@ -0,0 +1,48 @@
|
||||
@echo off
|
||||
echo ================================================
|
||||
echo Audio Dashboard API - 重启服务
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM 切换到脚本所在目录
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo [1/3] 停止服务...
|
||||
echo.
|
||||
|
||||
REM 查找占用 8002 端口的进程
|
||||
set PID=
|
||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8002') do (
|
||||
set PID=%%a
|
||||
goto :found
|
||||
)
|
||||
|
||||
:found
|
||||
if defined PID (
|
||||
echo 找到进程 PID: %PID%
|
||||
taskkill /F /PID %PID% >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo 无法终止进程 %PID%,可能已经停止
|
||||
) else (
|
||||
echo 服务已停止
|
||||
)
|
||||
timeout /t 2 /nobreak >nul
|
||||
) else (
|
||||
echo 端口 8002 没有被占用
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/3] 等待端口释放...
|
||||
timeout /t 2 /nobreak >nul
|
||||
|
||||
echo.
|
||||
echo [3/3] 启动服务...
|
||||
echo.
|
||||
echo 服务运行在 http://localhost:8002
|
||||
echo API 文档:http://localhost:8002/docs
|
||||
echo.
|
||||
echo 按 Ctrl+C 停止服务
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
python main.py
|
||||
@@ -0,0 +1,4 @@
|
||||
from routes.brands import router as brands_router
|
||||
from routes.models import router as models_router
|
||||
|
||||
__all__ = ["brands_router", "models_router"]
|
||||
@@ -0,0 +1,106 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from database import get_db
|
||||
from models.brand import Brand
|
||||
from schemas import BrandCreate, BrandUpdate, BrandResponse
|
||||
from response import ApiResponse, PageData
|
||||
|
||||
router = APIRouter(prefix="/api/brands", tags=["brands"])
|
||||
|
||||
|
||||
@router.get("/", response_model=ApiResponse)
|
||||
def get_brands(
|
||||
skip: int = Query(0, ge=0, description="跳过记录数"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="返回记录数"),
|
||||
name: Optional[str] = Query(None, description="品牌名称(支持模糊查询)"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取所有品牌列表(支持按名称模糊查询)"""
|
||||
try:
|
||||
query = db.query(Brand)
|
||||
if name:
|
||||
query = query.filter(Brand.name.like(f"%{name}%"))
|
||||
|
||||
total = query.count()
|
||||
brands = query.offset(skip).limit(limit).all()
|
||||
|
||||
if not brands:
|
||||
return ApiResponse(code=2, msg="empty", data=None)
|
||||
|
||||
# 转换为字典列表
|
||||
brands_data = [brand.to_dict() for brand in brands]
|
||||
|
||||
return ApiResponse(code=1, msg="success", data={"items": brands_data, "total": total, "skip": skip, "limit": limit})
|
||||
except Exception as e:
|
||||
return ApiResponse(code=0, msg="error", data=None)
|
||||
|
||||
|
||||
@router.get("/{brand_id}", response_model=ApiResponse)
|
||||
def get_brand(brand_id: int, db: Session = Depends(get_db)):
|
||||
"""获取单个品牌"""
|
||||
try:
|
||||
brand = db.query(Brand).filter(Brand.id == brand_id).first()
|
||||
if not brand:
|
||||
return ApiResponse(code=2, msg="empty", data=None)
|
||||
return ApiResponse(code=1, msg="success", data=brand.to_dict())
|
||||
except Exception as e:
|
||||
return ApiResponse(code=0, msg="error", data=None)
|
||||
|
||||
|
||||
@router.post("/", response_model=ApiResponse)
|
||||
def create_brand(brand: BrandCreate, db: Session = Depends(get_db)):
|
||||
"""创建新品牌"""
|
||||
try:
|
||||
# 检查是否已存在
|
||||
existing = db.query(Brand).filter(Brand.name == brand.name).first()
|
||||
if existing:
|
||||
return ApiResponse.error(msg="品牌名称已存在", code=0)
|
||||
|
||||
db_brand = Brand(name=brand.name)
|
||||
db.add(db_brand)
|
||||
db.commit()
|
||||
db.refresh(db_brand)
|
||||
return ApiResponse.success(data=db_brand.to_dict(), msg="品牌创建成功")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return ApiResponse.error(msg=f"创建失败:{str(e)}", code=0)
|
||||
|
||||
|
||||
@router.put("/{brand_id}", response_model=ApiResponse)
|
||||
def update_brand(brand_id: int, brand: BrandUpdate, db: Session = Depends(get_db)):
|
||||
"""更新品牌"""
|
||||
try:
|
||||
db_brand = db.query(Brand).filter(Brand.id == brand_id).first()
|
||||
if not db_brand:
|
||||
return ApiResponse.no_data(msg="品牌不存在")
|
||||
|
||||
# 如果更新名称,检查是否冲突
|
||||
if brand.name and brand.name != db_brand.name:
|
||||
existing = db.query(Brand).filter(Brand.name == brand.name).first()
|
||||
if existing:
|
||||
return ApiResponse.error(msg="品牌名称已存在", code=0)
|
||||
db_brand.name = brand.name
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_brand)
|
||||
return ApiResponse.success(data=db_brand.to_dict(), msg="品牌更新成功")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return ApiResponse.error(msg=f"更新失败:{str(e)}", code=0)
|
||||
|
||||
|
||||
@router.delete("/{brand_id}", response_model=ApiResponse)
|
||||
def delete_brand(brand_id: int, db: Session = Depends(get_db)):
|
||||
"""删除品牌"""
|
||||
try:
|
||||
db_brand = db.query(Brand).filter(Brand.id == brand_id).first()
|
||||
if not db_brand:
|
||||
return ApiResponse.no_data(msg="品牌不存在")
|
||||
|
||||
db.delete(db_brand)
|
||||
db.commit()
|
||||
return ApiResponse.success(msg="删除成功")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return ApiResponse.error(msg=f"删除失败:{str(e)}", code=0)
|
||||
@@ -0,0 +1,214 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
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
|
||||
|
||||
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")
|
||||
|
||||
|
||||
@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:
|
||||
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()
|
||||
|
||||
if not models:
|
||||
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:
|
||||
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:
|
||||
model = db.query(Model).filter(Model.id == model_id).first()
|
||||
if not model:
|
||||
return ApiResponse(code=2, msg="empty", data=None)
|
||||
return ApiResponse(code=1, msg="success", data=model.to_dict())
|
||||
except Exception as e:
|
||||
return ApiResponse(code=0, msg="error", data=None)
|
||||
|
||||
|
||||
@router.post("/", response_model=ApiResponse)
|
||||
def create_model(model: ModelCreate, db: Session = Depends(get_db)):
|
||||
"""创建新型号"""
|
||||
try:
|
||||
# 检查是否已存在
|
||||
existing = db.query(Model).filter(
|
||||
Model.brand_name == model.brand_name,
|
||||
Model.name == model.name
|
||||
).first()
|
||||
if existing:
|
||||
return ApiResponse(code=0, msg="该品牌下型号名称已存在", data=None)
|
||||
|
||||
db_model = Model(
|
||||
brand_name=model.brand_name,
|
||||
name=model.name,
|
||||
form=model.form,
|
||||
rig=model.rig,
|
||||
source=model.source,
|
||||
eq_key=model.eq_key
|
||||
)
|
||||
db.add(db_model)
|
||||
db.commit()
|
||||
db.refresh(db_model)
|
||||
return ApiResponse(code=1, msg="success", data=db_model.to_dict())
|
||||
except Exception as e:
|
||||
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:
|
||||
db_model = db.query(Model).filter(Model.id == model_id).first()
|
||||
if not db_model:
|
||||
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:
|
||||
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)
|
||||
return ApiResponse(code=1, msg="success", data=db_model.to_dict())
|
||||
except Exception as e:
|
||||
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:
|
||||
db_model = db.query(Model).filter(Model.id == model_id).first()
|
||||
if not db_model:
|
||||
return ApiResponse(code=2, msg="empty", data=None)
|
||||
|
||||
db.delete(db_model)
|
||||
db.commit()
|
||||
return ApiResponse(code=1, msg="success", data=None)
|
||||
except Exception as e:
|
||||
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)
|
||||
@@ -0,0 +1,54 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# Brand Schemas
|
||||
class BrandBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100, description="品牌名称")
|
||||
|
||||
|
||||
class BrandCreate(BrandBase):
|
||||
pass
|
||||
|
||||
|
||||
class BrandUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100, description="品牌名称")
|
||||
|
||||
|
||||
class BrandResponse(BrandBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Model Schemas
|
||||
class ModelBase(BaseModel):
|
||||
brand_name: str = Field(..., min_length=1, max_length=100, description="品牌名称")
|
||||
name: str = Field(..., min_length=1, max_length=100, description="型号名称")
|
||||
form: Optional[str] = Field(None, max_length=100, description="形式")
|
||||
rig: Optional[str] = Field(None, max_length=100, description="阻抗")
|
||||
source: Optional[str] = Field(None, max_length=100, description="来源")
|
||||
eq_key: Optional[str] = Field(None, max_length=255, description="EQ 键")
|
||||
|
||||
|
||||
class ModelCreate(ModelBase):
|
||||
pass
|
||||
|
||||
|
||||
class ModelUpdate(BaseModel):
|
||||
brand_name: Optional[str] = Field(None, min_length=1, max_length=100, description="品牌名称")
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100, description="型号名称")
|
||||
form: Optional[str] = Field(None, max_length=100, description="形式")
|
||||
rig: Optional[str] = Field(None, max_length=100, description="阻抗")
|
||||
source: Optional[str] = Field(None, max_length=100, description="来源")
|
||||
eq_key: Optional[str] = Field(None, max_length=255, description="EQ 键")
|
||||
|
||||
|
||||
class ModelResponse(ModelBase):
|
||||
id: int
|
||||
create_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
echo Starting Audio Dashboard API...
|
||||
cd /d "%~dp0"
|
||||
python main.py
|
||||
pause
|
||||
@@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
echo Stopping Audio Dashboard API...
|
||||
|
||||
REM 查找占用 8002 端口的进程
|
||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8002') do (
|
||||
set PID=%%a
|
||||
goto :found
|
||||
)
|
||||
|
||||
:found
|
||||
if defined PID (
|
||||
echo Found process PID: %PID%
|
||||
taskkill /F /PID %PID%
|
||||
echo Service stopped.
|
||||
) else (
|
||||
echo No service running on port 8002.
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -0,0 +1,12 @@
|
||||
from database import get_db
|
||||
from models import Brand
|
||||
|
||||
if __name__ == "__main__":
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
try:
|
||||
brands = db.query(Brand).all()
|
||||
for brand in brands:
|
||||
print(brand.name)
|
||||
finally:
|
||||
db_gen.close() # 这会触发 finally 块中的 db.close()
|
||||
@@ -0,0 +1,17 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
try:
|
||||
response = requests.get("http://localhost:8001/api/brands/", params={"skip": 0, "limit": 10})
|
||||
print(f"Status Code: {response.status_code}")
|
||||
print(f"Content-Type: {response.headers.get('content-type')}")
|
||||
print(f"Response: {response.text[:500]}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"\nSuccess! Got {len(data)} brands")
|
||||
for brand in data[:5]:
|
||||
print(f" - {brand}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -0,0 +1,52 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
BASE_URL = "http://localhost:8002"
|
||||
|
||||
def test_api(endpoint, method="GET", data=None, params=None):
|
||||
"""测试 API 接口"""
|
||||
url = f"{BASE_URL}{endpoint}"
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url, params=params)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, json=data, params=params)
|
||||
elif method == "PUT":
|
||||
response = requests.put(url, json=data)
|
||||
elif method == "DELETE":
|
||||
response = requests.delete(url)
|
||||
|
||||
result = response.json()
|
||||
print(f"\n{method} {endpoint}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"\n{method} {endpoint} - Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
# 测试品牌接口
|
||||
print("=" * 60)
|
||||
print("测试品牌接口")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 获取品牌列表(成功,有数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 5})
|
||||
|
||||
# 2. 模糊查询品牌(成功,有数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "Audio"})
|
||||
|
||||
# 3. 模糊查询品牌(无数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "不存在的品牌 XYZ"})
|
||||
|
||||
# 4. 获取单个品牌(成功)
|
||||
test_api("/api/brands/1")
|
||||
|
||||
# 5. 获取单个品牌(不存在)
|
||||
test_api("/api/brands/999999")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("测试完成!")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,65 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
BASE_URL = "http://localhost:8002"
|
||||
|
||||
def test_api(endpoint, method="GET", data=None, params=None):
|
||||
"""测试 API 接口"""
|
||||
url = f"{BASE_URL}{endpoint}"
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url, params=params)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, json=data, params=params)
|
||||
elif method == "PUT":
|
||||
response = requests.put(url, json=data)
|
||||
elif method == "DELETE":
|
||||
response = requests.delete(url)
|
||||
|
||||
result = response.json()
|
||||
print(f"\n{method} {endpoint}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"\n{method} {endpoint} - Error: {e}")
|
||||
return None
|
||||
|
||||
# 测试品牌接口
|
||||
print("=" * 60)
|
||||
print("测试品牌接口")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 获取品牌列表(成功,有数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 5})
|
||||
|
||||
# 2. 模糊查询品牌(成功,有数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "Audio"})
|
||||
|
||||
# 3. 模糊查询品牌(无数据)
|
||||
test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "不存在的品牌XYZ"})
|
||||
|
||||
# 4. 获取单个品牌(成功)
|
||||
test_api("/api/brands/1")
|
||||
|
||||
# 5. 获取单个品牌(不存在)
|
||||
test_api("/api/brands/999999")
|
||||
|
||||
# 6. 创建品牌(成功)
|
||||
test_api("/api/brands/", method="POST", data={"name": "Test Brand 测试"})
|
||||
|
||||
# 7. 创建品牌(重复)
|
||||
test_api("/api/brands/", method="POST", data={"name": "1MORE"})
|
||||
|
||||
# 8. 更新品牌(成功)
|
||||
test_api("/api/brands/1", method="PUT", data={"name": "1MORE Updated"})
|
||||
|
||||
# 9. 删除品牌(成功)
|
||||
test_api("/api/brands/623", method="DELETE")
|
||||
|
||||
# 10. 删除品牌(不存在)
|
||||
test_api("/api/brands/999999", method="DELETE")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("测试完成!")
|
||||
print("=" * 60)
|
||||
Reference in New Issue
Block a user