Initial commit: Audio Dashboard Management System

Made-with: Cursor
This commit is contained in:
yangy
2026-03-04 18:17:34 +08:00
commit 75b1d16453
39 changed files with 3771 additions and 0 deletions
+10
View File
@@ -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
+38
View File
@@ -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
+98
View File
@@ -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. 生产环境请使用更安全的密码管理方式
+39
View File
@@ -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()
+63
View File
@@ -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
)
+4
View File
@@ -0,0 +1,4 @@
from models.brand import Brand
from models.model import Model
__all__ = ["Brand", "Model"]
+21
View File
@@ -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
}
+37
View File
@@ -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
}
+7
View File
@@ -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
+36
View File
@@ -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)
+48
View File
@@ -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
+4
View File
@@ -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"]
+106
View File
@@ -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)
+214
View File
@@ -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)
+54
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
@echo off
echo Starting Audio Dashboard API...
cd /d "%~dp0"
python main.py
pause
+19
View File
@@ -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
+12
View File
@@ -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()
+17
View File
@@ -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()
+52
View File
@@ -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)
+65
View File
@@ -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)