1108 lines
38 KiB
Python
1108 lines
38 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
FastAPI接口测试应用 (MySQL版本)
|
||
提供所有管理功能的API接口测试页面,直接使用MySQL数据库
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import pymysql
|
||
from datetime import datetime, timedelta
|
||
from typing import List, Optional
|
||
from pydantic import BaseModel
|
||
|
||
from fastapi import FastAPI, HTTPException, Depends, status
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
|
||
# 添加项目根目录到Python路径
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
# 导入配置
|
||
from config import Config
|
||
|
||
# 解析数据库URL
|
||
def parse_database_url(url):
|
||
"""解析数据库URL"""
|
||
# 格式: mysql+pymysql://user:password@host:port/database
|
||
try:
|
||
# 移除mysql+pymysql://前缀
|
||
url = url.replace('mysql+pymysql://', '')
|
||
|
||
# 找到@符号前的用户名密码
|
||
at_index = url.index('@')
|
||
user_pass = url[:at_index]
|
||
url = url[at_index + 1:]
|
||
|
||
# 分割用户名和密码
|
||
user, password = user_pass.split(':', 1)
|
||
|
||
# 找到数据库名
|
||
if '/' in url:
|
||
host_port, database = url.split('/', 1)
|
||
# 提取端口
|
||
if ':' in host_port:
|
||
host, port = host_port.split(':', 1)
|
||
else:
|
||
host = host_port
|
||
port = '3306'
|
||
else:
|
||
raise ValueError("URL格式不正确")
|
||
|
||
return {
|
||
'user': user,
|
||
'password': password,
|
||
'host': host,
|
||
'port': port,
|
||
'database': database
|
||
}
|
||
except Exception as e:
|
||
print(f"无法解析数据库URL: {e}")
|
||
return None
|
||
|
||
# 获取数据库连接
|
||
def get_db_connection():
|
||
"""获取MySQL数据库连接"""
|
||
db_config = parse_database_url(Config.SQLALCHEMY_DATABASE_URI)
|
||
if not db_config:
|
||
raise Exception("无法解析数据库配置")
|
||
|
||
connection = pymysql.connect(
|
||
host=db_config['host'],
|
||
port=int(db_config['port']),
|
||
user=db_config['user'],
|
||
password=db_config['password'],
|
||
database=db_config['database'],
|
||
charset='utf8mb4',
|
||
cursorclass=pymysql.cursors.DictCursor
|
||
)
|
||
return connection
|
||
|
||
# 创建FastAPI应用
|
||
app = FastAPI(
|
||
title="KaMiXiTong API测试平台 (MySQL版)",
|
||
description="软件授权管理系统的完整API接口测试平台,使用MySQL数据库",
|
||
version="1.0.0",
|
||
docs_url="/docs",
|
||
redoc_url="/redoc"
|
||
)
|
||
|
||
# 添加CORS中间件
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# ==================== 用户管理模型 ====================
|
||
class AdminBase(BaseModel):
|
||
username: str
|
||
email: Optional[str] = None
|
||
role: Optional[int] = 0 # 0=普通管理员, 1=超级管理员
|
||
status: Optional[int] = 1 # 0=禁用, 1=正常
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class AdminCreate(AdminBase):
|
||
password: str
|
||
|
||
class AdminUpdate(AdminBase):
|
||
password: Optional[str] = None
|
||
|
||
class AdminInDB(AdminBase):
|
||
admin_id: int
|
||
create_time: datetime
|
||
update_time: datetime
|
||
|
||
# ==================== 工单管理模型 ====================
|
||
class TicketBase(BaseModel):
|
||
title: str
|
||
product_id: str
|
||
description: str
|
||
priority: Optional[int] = 1 # 1=低, 2=中, 3=高
|
||
status: Optional[int] = 0 # 0=待处理, 1=处理中, 2=已解决, 3=已关闭
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class TicketCreate(TicketBase):
|
||
software_version: Optional[str] = None
|
||
machine_code: Optional[str] = None
|
||
license_key: Optional[str] = None
|
||
|
||
class TicketUpdate(TicketBase):
|
||
pass
|
||
|
||
class TicketInDB(TicketBase):
|
||
ticket_id: int
|
||
create_time: datetime
|
||
update_time: datetime
|
||
|
||
# ==================== 卡密管理模型 ====================
|
||
class LicenseBase(BaseModel):
|
||
product_id: str
|
||
type: int = 1 # 0=试用, 1=正式
|
||
status: Optional[int] = 0 # 0=未使用, 1=已使用, 2=已过期, 3=已禁用
|
||
valid_days: Optional[int] = 365
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class LicenseCreate(LicenseBase):
|
||
count: int = 1
|
||
prefix: Optional[str] = ""
|
||
length: Optional[int] = 32
|
||
|
||
class LicenseUpdate(LicenseBase):
|
||
pass
|
||
|
||
class LicenseInDB(LicenseBase):
|
||
license_id: int
|
||
license_key: str
|
||
create_time: datetime
|
||
update_time: datetime
|
||
expire_time: Optional[datetime] = None
|
||
|
||
# ==================== 版本管理模型 ====================
|
||
class VersionBase(BaseModel):
|
||
product_id: str
|
||
version_num: str
|
||
platform: Optional[str] = ""
|
||
description: Optional[str] = ""
|
||
update_log: Optional[str] = ""
|
||
download_url: Optional[str] = ""
|
||
file_hash: Optional[str] = ""
|
||
force_update: Optional[int] = 0
|
||
download_status: Optional[int] = 1 # 0=下架, 1=上架
|
||
min_license_version: Optional[str] = ""
|
||
publish_status: Optional[int] = 0 # 0=未发布, 1=已发布
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class VersionCreate(VersionBase):
|
||
publish_now: Optional[bool] = False
|
||
|
||
class VersionUpdate(VersionBase):
|
||
pass
|
||
|
||
class VersionInDB(VersionBase):
|
||
version_id: int
|
||
create_time: datetime
|
||
update_time: datetime
|
||
|
||
# ==================== 设备管理模型 ====================
|
||
class DeviceBase(BaseModel):
|
||
product_id: str
|
||
machine_code: str
|
||
software_version: Optional[str] = ""
|
||
status: Optional[int] = 1 # 0=禁用, 1=正常, 2=黑名单
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class DeviceCreate(DeviceBase):
|
||
license_key: Optional[str] = None
|
||
|
||
class DeviceUpdate(DeviceBase):
|
||
pass
|
||
|
||
class DeviceInDB(DeviceBase):
|
||
device_id: int
|
||
create_time: datetime
|
||
last_verify_time: Optional[datetime] = None
|
||
|
||
# ==================== 产品管理模型 ====================
|
||
class ProductBase(BaseModel):
|
||
product_name: str
|
||
description: Optional[str] = ""
|
||
status: Optional[int] = 1 # 0=禁用, 1=正常
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class ProductCreate(ProductBase):
|
||
product_id: Optional[str] = None
|
||
|
||
class ProductUpdate(ProductBase):
|
||
pass
|
||
|
||
class ProductInDB(ProductBase):
|
||
product_id: str
|
||
create_time: datetime
|
||
update_time: datetime
|
||
|
||
# ==================== 用户管理接口 ====================
|
||
@app.get("/")
|
||
async def root():
|
||
return {"message": "欢迎使用KaMiXiTong API测试平台 (MySQL版)", "version": "1.0.0"}
|
||
|
||
@app.get("/admins", response_model=List[AdminInDB])
|
||
async def get_admins(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
keyword: Optional[str] = None,
|
||
role: Optional[int] = None,
|
||
status: Optional[int] = None
|
||
):
|
||
"""获取管理员列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM admin WHERE is_deleted = 0"
|
||
params = []
|
||
|
||
if keyword:
|
||
sql += " AND username LIKE %s"
|
||
params.append(f"%{keyword}%")
|
||
|
||
if role is not None:
|
||
sql += " AND role = %s"
|
||
params.append(role)
|
||
|
||
if status is not None:
|
||
sql += " AND status = %s"
|
||
params.append(status)
|
||
|
||
sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
admins = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return admins
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"数据库查询失败: {str(e)}")
|
||
|
||
@app.post("/admins", response_model=AdminInDB)
|
||
async def create_admin(admin: AdminCreate):
|
||
"""创建管理员"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查用户名是否已存在
|
||
cursor.execute(
|
||
"SELECT admin_id FROM admin WHERE username = %s AND is_deleted = 0",
|
||
(admin.username,)
|
||
)
|
||
existing = cursor.fetchone()
|
||
|
||
if existing:
|
||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||
|
||
# 创建管理员(简化密码处理)
|
||
sql = """
|
||
INSERT INTO admin (username, email, password_hash, role, status, create_time, update_time)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||
"""
|
||
params = (
|
||
admin.username,
|
||
admin.email,
|
||
f"hashed_{admin.password}", # 简化处理
|
||
admin.role,
|
||
admin.status,
|
||
datetime.utcnow(),
|
||
datetime.utcnow()
|
||
)
|
||
|
||
cursor.execute(sql, params)
|
||
admin_id = cursor.lastrowid
|
||
|
||
connection.commit()
|
||
|
||
# 查询创建的管理员
|
||
cursor.execute("SELECT * FROM admin WHERE admin_id = %s", (admin_id,))
|
||
created_admin = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return created_admin
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"创建管理员失败: {str(e)}")
|
||
|
||
@app.get("/admins/{admin_id}", response_model=AdminInDB)
|
||
async def get_admin(admin_id: int):
|
||
"""获取管理员详情"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
cursor.execute(
|
||
"SELECT * FROM admin WHERE admin_id = %s AND is_deleted = 0",
|
||
(admin_id,)
|
||
)
|
||
admin = cursor.fetchone()
|
||
|
||
connection.close()
|
||
|
||
if not admin:
|
||
raise HTTPException(status_code=404, detail="管理员不存在")
|
||
return admin
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询管理员失败: {str(e)}")
|
||
|
||
@app.put("/admins/{admin_id}", response_model=AdminInDB)
|
||
async def update_admin(admin_id: int, admin: AdminUpdate):
|
||
"""更新管理员"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查管理员是否存在
|
||
cursor.execute(
|
||
"SELECT * FROM admin WHERE admin_id = %s AND is_deleted = 0",
|
||
(admin_id,)
|
||
)
|
||
existing_admin = cursor.fetchone()
|
||
|
||
if not existing_admin:
|
||
raise HTTPException(status_code=404, detail="管理员不存在")
|
||
|
||
# 检查新用户名是否已存在
|
||
if admin.username and admin.username != existing_admin['username']:
|
||
cursor.execute(
|
||
"SELECT admin_id FROM admin WHERE username = %s AND admin_id != %s AND is_deleted = 0",
|
||
(admin.username, admin_id)
|
||
)
|
||
duplicate = cursor.fetchone()
|
||
|
||
if duplicate:
|
||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||
|
||
# 更新字段
|
||
updates = []
|
||
params = []
|
||
|
||
if admin.username is not None:
|
||
updates.append("username = %s")
|
||
params.append(admin.username)
|
||
if admin.email is not None:
|
||
updates.append("email = %s")
|
||
params.append(admin.email)
|
||
if admin.role is not None:
|
||
updates.append("role = %s")
|
||
params.append(admin.role)
|
||
if admin.status is not None:
|
||
updates.append("status = %s")
|
||
params.append(admin.status)
|
||
if admin.password:
|
||
updates.append("password_hash = %s")
|
||
params.append(f"hashed_{admin.password}") # 简化处理
|
||
if updates:
|
||
updates.append("update_time = %s")
|
||
params.append(datetime.utcnow())
|
||
params.append(admin_id)
|
||
|
||
sql = f"UPDATE admin SET {', '.join(updates)} WHERE admin_id = %s"
|
||
cursor.execute(sql, params)
|
||
connection.commit()
|
||
|
||
# 查询更新后的管理员
|
||
cursor.execute("SELECT * FROM admin WHERE admin_id = %s", (admin_id,))
|
||
updated_admin = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return updated_admin
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"更新管理员失败: {str(e)}")
|
||
|
||
@app.delete("/admins/{admin_id}")
|
||
async def delete_admin(admin_id: int):
|
||
"""删除管理员(软删除)"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查管理员是否存在
|
||
cursor.execute(
|
||
"SELECT admin_id FROM admin WHERE admin_id = %s AND is_deleted = 0",
|
||
(admin_id,)
|
||
)
|
||
admin = cursor.fetchone()
|
||
|
||
if not admin:
|
||
raise HTTPException(status_code=404, detail="管理员不存在")
|
||
|
||
# 软删除
|
||
cursor.execute(
|
||
"UPDATE admin SET is_deleted = 1, delete_time = %s WHERE admin_id = %s",
|
||
(datetime.utcnow(), admin_id)
|
||
)
|
||
connection.commit()
|
||
|
||
connection.close()
|
||
return {"message": "管理员删除成功"}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"删除管理员失败: {str(e)}")
|
||
|
||
@app.post("/admins/{admin_id}/toggle-status")
|
||
async def toggle_admin_status(admin_id: int):
|
||
"""切换管理员状态"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查管理员是否存在
|
||
cursor.execute(
|
||
"SELECT * FROM admin WHERE admin_id = %s AND is_deleted = 0",
|
||
(admin_id,)
|
||
)
|
||
admin = cursor.fetchone()
|
||
|
||
if not admin:
|
||
raise HTTPException(status_code=404, detail="管理员不存在")
|
||
|
||
# 切换状态
|
||
new_status = 0 if admin['status'] == 1 else 1
|
||
cursor.execute(
|
||
"UPDATE admin SET status = %s, update_time = %s WHERE admin_id = %s",
|
||
(new_status, datetime.utcnow(), admin_id)
|
||
)
|
||
connection.commit()
|
||
|
||
# 查询更新后的管理员
|
||
cursor.execute("SELECT * FROM admin WHERE admin_id = %s", (admin_id,))
|
||
updated_admin = cursor.fetchone()
|
||
|
||
connection.close()
|
||
|
||
status_name = "正常" if updated_admin['status'] == 1 else "禁用"
|
||
action = "启用" if updated_admin['status'] == 1 else "禁用"
|
||
return {"message": f"管理员已{action}", "status": updated_admin['status'], "status_name": status_name}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"切换管理员状态失败: {str(e)}")
|
||
|
||
# ==================== 工单管理接口 ====================
|
||
@app.get("/tickets", response_model=List[TicketInDB])
|
||
async def get_tickets(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
status: Optional[int] = None,
|
||
priority: Optional[int] = None,
|
||
product_id: Optional[str] = None
|
||
):
|
||
"""获取工单列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM ticket"
|
||
params = []
|
||
conditions = []
|
||
|
||
if status is not None:
|
||
conditions.append("status = %s")
|
||
params.append(status)
|
||
if priority is not None:
|
||
conditions.append("priority = %s")
|
||
params.append(priority)
|
||
if product_id:
|
||
conditions.append("product_id = %s")
|
||
params.append(product_id)
|
||
|
||
if conditions:
|
||
sql += " WHERE " + " AND ".join(conditions)
|
||
|
||
sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
tickets = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return tickets
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询工单失败: {str(e)}")
|
||
|
||
@app.post("/tickets", response_model=TicketInDB)
|
||
async def create_ticket(ticket: TicketCreate):
|
||
"""创建工单"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 创建工单
|
||
sql = """
|
||
INSERT INTO ticket (title, product_id, software_version, machine_code,
|
||
license_key, description, priority, status, create_time, update_time)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||
"""
|
||
params = (
|
||
ticket.title,
|
||
ticket.product_id,
|
||
ticket.software_version,
|
||
ticket.machine_code,
|
||
ticket.license_key,
|
||
ticket.description,
|
||
ticket.priority,
|
||
ticket.status,
|
||
datetime.utcnow(),
|
||
datetime.utcnow()
|
||
)
|
||
|
||
cursor.execute(sql, params)
|
||
ticket_id = cursor.lastrowid
|
||
|
||
connection.commit()
|
||
|
||
# 查询创建的工单
|
||
cursor.execute("SELECT * FROM ticket WHERE ticket_id = %s", (ticket_id,))
|
||
created_ticket = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return created_ticket
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"创建工单失败: {str(e)}")
|
||
|
||
# ==================== 卡密管理接口 ====================
|
||
@app.get("/licenses", response_model=List[LicenseInDB])
|
||
async def get_licenses(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
product_id: Optional[str] = None,
|
||
status: Optional[int] = None,
|
||
license_type: Optional[int] = None,
|
||
keyword: Optional[str] = None
|
||
):
|
||
"""获取卡密列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM license"
|
||
params = []
|
||
conditions = []
|
||
|
||
if product_id:
|
||
conditions.append("product_id = %s")
|
||
params.append(product_id)
|
||
if status is not None:
|
||
conditions.append("status = %s")
|
||
params.append(status)
|
||
if license_type is not None:
|
||
conditions.append("type = %s")
|
||
params.append(license_type)
|
||
if keyword:
|
||
conditions.append("license_key LIKE %s")
|
||
params.append(f"%{keyword}%")
|
||
|
||
if conditions:
|
||
sql += " WHERE " + " AND ".join(conditions)
|
||
|
||
sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
licenses = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return licenses
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询卡密失败: {str(e)}")
|
||
|
||
@app.post("/licenses", response_model=dict)
|
||
async def generate_licenses(license: LicenseCreate):
|
||
"""批量生成卡密"""
|
||
try:
|
||
# 验证参数
|
||
if license.count < 1 or license.count > 10000:
|
||
raise HTTPException(status_code=400, detail="生成数量必须在1-10000之间")
|
||
|
||
if license.length < 16 or license.length > 35:
|
||
raise HTTPException(status_code=400, detail="卡密长度必须在16-35之间")
|
||
|
||
# 试用卡密最大有效期限制
|
||
if license.type == 0 and license.valid_days and license.valid_days > 90:
|
||
raise HTTPException(status_code=400, detail="试用卡密有效期不能超过90天")
|
||
|
||
# 生成卡密
|
||
import secrets
|
||
import string
|
||
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
licenses = []
|
||
characters = string.ascii_uppercase + string.digits
|
||
|
||
for i in range(license.count):
|
||
# 生成卡密
|
||
key = license.prefix + ''.join(secrets.choice(characters) for _ in range(license.length - len(license.prefix)))
|
||
|
||
# 确保卡密唯一
|
||
max_attempts = 10
|
||
for attempt in range(max_attempts):
|
||
cursor.execute("SELECT license_id FROM license WHERE license_key = %s", (key,))
|
||
existing = cursor.fetchone()
|
||
if not existing:
|
||
break
|
||
key = license.prefix + ''.join(secrets.choice(characters) for _ in range(license.length - len(license.prefix)))
|
||
else:
|
||
raise HTTPException(status_code=500, detail="无法生成唯一的卡密,请稍后重试")
|
||
|
||
# 计算过期时间
|
||
expire_time = None
|
||
if license.valid_days:
|
||
expire_time = datetime.utcnow() + timedelta(days=license.valid_days)
|
||
|
||
# 创建卡密
|
||
sql = """
|
||
INSERT INTO license (product_id, license_key, type, status, create_time, update_time, expire_time)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||
"""
|
||
params = (
|
||
license.product_id,
|
||
key,
|
||
license.type,
|
||
0, # 未使用
|
||
datetime.utcnow(),
|
||
datetime.utcnow(),
|
||
expire_time
|
||
)
|
||
|
||
cursor.execute(sql, params)
|
||
license_id = cursor.lastrowid
|
||
|
||
# 查询创建的卡密
|
||
cursor.execute("SELECT * FROM license WHERE license_id = %s", (license_id,))
|
||
created_license = cursor.fetchone()
|
||
licenses.append(created_license)
|
||
|
||
connection.commit()
|
||
|
||
connection.close()
|
||
|
||
return {
|
||
"message": f"成功生成 {license.count} 个卡密",
|
||
"licenses": licenses,
|
||
"count": len(licenses)
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"生成卡密失败: {str(e)}")
|
||
|
||
# ==================== 版本管理接口 ====================
|
||
@app.get("/versions", response_model=List[VersionInDB])
|
||
async def get_versions(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
product_id: Optional[str] = None,
|
||
publish_status: Optional[int] = None
|
||
):
|
||
"""获取版本列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM version"
|
||
params = []
|
||
conditions = []
|
||
|
||
if product_id:
|
||
conditions.append("product_id = %s")
|
||
params.append(product_id)
|
||
if publish_status is not None:
|
||
conditions.append("publish_status = %s")
|
||
params.append(publish_status)
|
||
|
||
if conditions:
|
||
sql += " WHERE " + " AND ".join(conditions)
|
||
|
||
sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
versions = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return versions
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询版本失败: {str(e)}")
|
||
|
||
@app.post("/versions", response_model=VersionInDB)
|
||
async def create_version(version: VersionCreate):
|
||
"""创建版本"""
|
||
try:
|
||
# 验证参数
|
||
if not version.product_id or not version.version_num:
|
||
raise HTTPException(status_code=400, detail="缺少必要参数")
|
||
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查版本号是否重复
|
||
cursor.execute(
|
||
"SELECT version_id FROM version WHERE product_id = %s AND version_num = %s",
|
||
(version.product_id, version.version_num)
|
||
)
|
||
existing = cursor.fetchone()
|
||
|
||
if existing:
|
||
raise HTTPException(status_code=400, detail="版本号已存在")
|
||
|
||
# 创建版本
|
||
sql = """
|
||
INSERT INTO version (product_id, version_num, platform, description, update_log,
|
||
download_url, file_hash, force_update, download_status,
|
||
min_license_version, publish_status, create_time, update_time)
|
||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||
"""
|
||
params = (
|
||
version.product_id,
|
||
version.version_num,
|
||
version.platform,
|
||
version.description,
|
||
version.update_log,
|
||
version.download_url,
|
||
version.file_hash,
|
||
version.force_update,
|
||
version.download_status,
|
||
version.min_license_version,
|
||
version.publish_status,
|
||
datetime.utcnow(),
|
||
datetime.utcnow()
|
||
)
|
||
|
||
cursor.execute(sql, params)
|
||
version_id = cursor.lastrowid
|
||
|
||
connection.commit()
|
||
|
||
# 如果选择了立即发布,则发布版本
|
||
if version.publish_now:
|
||
cursor.execute(
|
||
"UPDATE version SET publish_status = 1, update_time = %s WHERE version_id = %s",
|
||
(datetime.utcnow(), version_id)
|
||
)
|
||
connection.commit()
|
||
|
||
# 查询创建的版本
|
||
cursor.execute("SELECT * FROM version WHERE version_id = %s", (version_id,))
|
||
created_version = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return created_version
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"创建版本失败: {str(e)}")
|
||
|
||
@app.post("/versions/{version_id}/publish")
|
||
async def publish_version(version_id: int):
|
||
"""发布版本"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查版本是否存在
|
||
cursor.execute("SELECT * FROM version WHERE version_id = %s", (version_id,))
|
||
version = cursor.fetchone()
|
||
|
||
if not version:
|
||
raise HTTPException(status_code=404, detail="版本不存在")
|
||
|
||
# 发布版本
|
||
cursor.execute(
|
||
"UPDATE version SET publish_status = 1, update_time = %s WHERE version_id = %s",
|
||
(datetime.utcnow(), version_id)
|
||
)
|
||
connection.commit()
|
||
|
||
# 查询更新后的版本
|
||
cursor.execute("SELECT * FROM version WHERE version_id = %s", (version_id,))
|
||
updated_version = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return {"message": "版本发布成功", "version": updated_version}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"发布版本失败: {str(e)}")
|
||
|
||
# ==================== 设备管理接口 ====================
|
||
@app.get("/devices", response_model=List[DeviceInDB])
|
||
async def get_devices(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
product_id: Optional[str] = None,
|
||
software_version: Optional[str] = None,
|
||
status: Optional[int] = None,
|
||
keyword: Optional[str] = None
|
||
):
|
||
"""获取设备列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM device"
|
||
params = []
|
||
conditions = []
|
||
|
||
if product_id:
|
||
conditions.append("product_id = %s")
|
||
params.append(product_id)
|
||
if software_version:
|
||
conditions.append("software_version = %s")
|
||
params.append(software_version)
|
||
if status is not None:
|
||
conditions.append("status = %s")
|
||
params.append(status)
|
||
if keyword:
|
||
conditions.append("machine_code LIKE %s")
|
||
params.append(f"%{keyword}%")
|
||
|
||
if conditions:
|
||
sql += " WHERE " + " AND ".join(conditions)
|
||
|
||
sql += " ORDER BY last_verify_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
devices = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return devices
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询设备失败: {str(e)}")
|
||
|
||
@app.put("/devices/{device_id}/status")
|
||
async def update_device_status(device_id: int, status: int):
|
||
"""更新设备状态"""
|
||
if status not in [0, 1, 2]:
|
||
raise HTTPException(status_code=400, detail="无效的状态值")
|
||
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查设备是否存在
|
||
cursor.execute("SELECT * FROM device WHERE device_id = %s", (device_id,))
|
||
device = cursor.fetchone()
|
||
|
||
if not device:
|
||
raise HTTPException(status_code=404, detail="设备不存在")
|
||
|
||
# 更新设备状态
|
||
cursor.execute(
|
||
"UPDATE device SET status = %s, last_verify_time = %s WHERE device_id = %s",
|
||
(status, datetime.utcnow(), device_id)
|
||
)
|
||
connection.commit()
|
||
|
||
# 查询更新后的设备
|
||
cursor.execute("SELECT * FROM device WHERE device_id = %s", (device_id,))
|
||
updated_device = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return {"message": "设备状态更新成功", "device": updated_device}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"更新设备状态失败: {str(e)}")
|
||
|
||
@app.delete("/devices/{device_id}")
|
||
async def delete_device(device_id: int):
|
||
"""删除设备"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查设备是否存在
|
||
cursor.execute("SELECT device_id FROM device WHERE device_id = %s", (device_id,))
|
||
device = cursor.fetchone()
|
||
|
||
if not device:
|
||
raise HTTPException(status_code=404, detail="设备不存在")
|
||
|
||
# 删除设备
|
||
cursor.execute("DELETE FROM device WHERE device_id = %s", (device_id,))
|
||
connection.commit()
|
||
|
||
connection.close()
|
||
return {"message": "设备删除成功"}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"删除设备失败: {str(e)}")
|
||
|
||
# ==================== 产品管理接口 ====================
|
||
@app.get("/products", response_model=List[ProductInDB])
|
||
async def get_products(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
keyword: Optional[str] = None
|
||
):
|
||
"""获取产品列表"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 构建查询
|
||
sql = "SELECT * FROM product"
|
||
params = []
|
||
conditions = []
|
||
|
||
if keyword:
|
||
conditions.append("(product_name LIKE %s OR description LIKE %s)")
|
||
params.extend([f"%{keyword}%", f"%{keyword}%"])
|
||
|
||
if conditions:
|
||
sql += " WHERE " + " AND ".join(conditions)
|
||
|
||
sql += " ORDER BY create_time DESC LIMIT %s OFFSET %s"
|
||
params.extend([limit, skip])
|
||
|
||
cursor.execute(sql, params)
|
||
products = cursor.fetchall()
|
||
|
||
connection.close()
|
||
return products
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询产品失败: {str(e)}")
|
||
|
||
@app.post("/products", response_model=ProductInDB)
|
||
async def create_product(product: ProductCreate):
|
||
"""创建产品"""
|
||
if not product.product_name.strip():
|
||
raise HTTPException(status_code=400, detail="产品名称不能为空")
|
||
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查自定义ID是否重复
|
||
if product.product_id:
|
||
cursor.execute(
|
||
"SELECT product_id FROM product WHERE product_id = %s",
|
||
(product.product_id,)
|
||
)
|
||
existing = cursor.fetchone()
|
||
if existing:
|
||
raise HTTPException(status_code=400, detail="产品ID已存在")
|
||
|
||
# 创建产品
|
||
import uuid
|
||
product_id = product.product_id if product.product_id else f"PROD_{uuid.uuid4().hex[:8]}".upper()
|
||
|
||
sql = """
|
||
INSERT INTO product (product_id, product_name, description, status, create_time, update_time)
|
||
VALUES (%s, %s, %s, %s, %s, %s)
|
||
"""
|
||
params = (
|
||
product_id,
|
||
product.product_name,
|
||
product.description,
|
||
product.status,
|
||
datetime.utcnow(),
|
||
datetime.utcnow()
|
||
)
|
||
|
||
cursor.execute(sql, params)
|
||
connection.commit()
|
||
|
||
# 查询创建的产品
|
||
cursor.execute("SELECT * FROM product WHERE product_id = %s", (product_id,))
|
||
created_product = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return created_product
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"创建产品失败: {str(e)}")
|
||
|
||
@app.get("/products/{product_id}", response_model=ProductInDB)
|
||
async def get_product(product_id: str):
|
||
"""获取产品详情"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
cursor.execute("SELECT * FROM product WHERE product_id = %s", (product_id,))
|
||
product = cursor.fetchone()
|
||
|
||
connection.close()
|
||
|
||
if not product:
|
||
raise HTTPException(status_code=404, detail="产品不存在")
|
||
return product
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"查询产品失败: {str(e)}")
|
||
|
||
@app.put("/products/{product_id}", response_model=ProductInDB)
|
||
async def update_product(product_id: str, product: ProductUpdate):
|
||
"""更新产品"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查产品是否存在
|
||
cursor.execute("SELECT * FROM product WHERE product_id = %s", (product_id,))
|
||
existing_product = cursor.fetchone()
|
||
|
||
if not existing_product:
|
||
raise HTTPException(status_code=404, detail="产品不存在")
|
||
|
||
# 更新字段
|
||
updates = []
|
||
params = []
|
||
|
||
if product.product_name is not None:
|
||
updates.append("product_name = %s")
|
||
params.append(product.product_name)
|
||
if product.description is not None:
|
||
updates.append("description = %s")
|
||
params.append(product.description)
|
||
if product.status is not None:
|
||
updates.append("status = %s")
|
||
params.append(product.status)
|
||
|
||
if updates:
|
||
updates.append("update_time = %s")
|
||
params.append(datetime.utcnow())
|
||
params.append(product_id)
|
||
|
||
sql = f"UPDATE product SET {', '.join(updates)} WHERE product_id = %s"
|
||
cursor.execute(sql, params)
|
||
connection.commit()
|
||
|
||
# 查询更新后的产品
|
||
cursor.execute("SELECT * FROM product WHERE product_id = %s", (product_id,))
|
||
updated_product = cursor.fetchone()
|
||
|
||
connection.close()
|
||
return updated_product
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"更新产品失败: {str(e)}")
|
||
|
||
@app.delete("/products/{product_id}")
|
||
async def delete_product(product_id: str):
|
||
"""删除产品"""
|
||
try:
|
||
connection = get_db_connection()
|
||
with connection.cursor() as cursor:
|
||
# 检查产品是否存在
|
||
cursor.execute("SELECT product_id FROM product WHERE product_id = %s", (product_id,))
|
||
product = cursor.fetchone()
|
||
|
||
if not product:
|
||
raise HTTPException(status_code=404, detail="产品不存在")
|
||
|
||
# 删除产品
|
||
cursor.execute("DELETE FROM product WHERE product_id = %s", (product_id,))
|
||
connection.commit()
|
||
|
||
connection.close()
|
||
return {"message": "产品删除成功"}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"删除产品失败: {str(e)}")
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
# 使用127.0.0.1而不是0.0.0.0来避免权限问题
|
||
uvicorn.run(app, host="127.0.0.1", port=9004, log_level="info") |