diff --git a/.env b/.env
index aec4c70..1623f41 100644
--- a/.env
+++ b/.env
@@ -32,7 +32,7 @@ HOST=0.0.0.0
PORT=5000
# 文件上传配置
-MAX_CONTENT_LENGTH=16777216
+MAX_CONTENT_LENGTH=524288000
UPLOAD_FOLDER=static/uploads
# 日志配置
diff --git a/.env.example b/.env.example
index 99f6bf8..d89bb33 100644
--- a/.env.example
+++ b/.env.example
@@ -32,7 +32,7 @@ HOST=0.0.0.0
PORT=5000
# 文件上传配置
-MAX_CONTENT_LENGTH=16777216
+MAX_CONTENT_LENGTH=524288000
UPLOAD_FOLDER=static/uploads
# 日志配置
diff --git a/api_test.html b/api_test.html
deleted file mode 100644
index d15b205..0000000
--- a/api_test.html
+++ /dev/null
@@ -1,604 +0,0 @@
-
-
-
-
-
- KaMiXiTong API测试平台
-
-
-
-
-
KaMiXiTong API测试平台
-
这是一个用于测试KaMiXiTong系统所有API接口的前端页面。
-
-
-
用户管理
-
工单管理
-
卡密管理
-
版本管理
-
设备管理
-
产品管理
-
-
-
-
-
-
-
-
-
-
-
-
生成卡密
-
-
-
-
-
-
-
-
-
-
-
创建版本
-
-
-
-
-
-
-
-
-
-
-
-
-
-
创建产品
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/api_test_app.py b/api_test_app.py
deleted file mode 100644
index 2f86517..0000000
--- a/api_test_app.py
+++ /dev/null
@@ -1,821 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-FastAPI接口测试应用
-提供所有管理功能的API接口测试页面
-"""
-
-import os
-import sys
-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
-from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, Boolean, ForeignKey, func
-from sqlalchemy.orm import declarative_base, sessionmaker, Session
-
-# 添加项目根目录到Python路径
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-# 导入配置
-from config import Config
-
-# 数据库配置
-DATABASE_URL = Config.SQLALCHEMY_DATABASE_URI
-engine = create_engine(DATABASE_URL)
-SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-Base = declarative_base()
-
-# 创建FastAPI应用
-app = FastAPI(
- title="KaMiXiTong API测试平台",
- description="软件授权管理系统的完整API接口测试平台",
- version="1.0.0",
- docs_url="/docs",
- redoc_url="/redoc"
-)
-
-# 添加CORS中间件
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-# 依赖项
-def get_db():
- db = SessionLocal()
- try:
- yield db
- finally:
- db.close()
-
-# ==================== 用户管理模型 ====================
-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
-
-# ==================== 数据库模型 ====================
-# 用户管理表
-class DBAdmin(Base):
- __tablename__ = "admin"
-
- admin_id = Column(Integer, primary_key=True)
- username = Column(String(32), unique=True, nullable=False)
- password_hash = Column(String(128), nullable=False)
- email = Column(String(128), nullable=True)
- role = Column(Integer, default=0) # 0=普通管理员, 1=超级管理员
- status = Column(Integer, default=1) # 0=禁用, 1=正常
- create_time = Column(DateTime, default=datetime.utcnow)
- update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
- is_deleted = Column(Integer, default=0) # 软删除标志
-
- def __init__(self, **kwargs):
- super(DBAdmin, self).__init__(**kwargs)
-
-# 工单表
-class DBTicket(Base):
- __tablename__ = "ticket"
-
- ticket_id = Column(Integer, primary_key=True)
- title = Column(String(128), nullable=False)
- product_id = Column(String(32), nullable=False)
- software_version = Column(String(32), nullable=True)
- machine_code = Column(String(64), nullable=True)
- license_key = Column(String(64), nullable=True)
- description = Column(Text, nullable=False)
- priority = Column(Integer, default=1) # 1=低, 2=中, 3=高
- status = Column(Integer, default=0) # 0=待处理, 1=处理中, 2=已解决, 3=已关闭
- create_time = Column(DateTime, default=datetime.utcnow)
- update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
- def __init__(self, **kwargs):
- super(DBTicket, self).__init__(**kwargs)
-
-# 卡密表
-class DBLicense(Base):
- __tablename__ = "license"
-
- license_id = Column(Integer, primary_key=True)
- product_id = Column(String(32), nullable=False)
- license_key = Column(String(64), unique=True, nullable=False)
- type = Column(Integer, default=1) # 0=试用, 1=正式
- status = Column(Integer, default=0) # 0=未使用, 1=已使用, 2=已过期, 3=已禁用
- create_time = Column(DateTime, default=datetime.utcnow)
- update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
- expire_time = Column(DateTime, nullable=True)
-
- def __init__(self, **kwargs):
- super(DBLicense, self).__init__(**kwargs)
-
-# 版本表
-class DBVersion(Base):
- __tablename__ = "version"
-
- version_id = Column(Integer, primary_key=True)
- product_id = Column(String(32), nullable=False)
- version_num = Column(String(32), nullable=False)
- platform = Column(String(32), nullable=True)
- description = Column(Text, nullable=True)
- update_log = Column(Text, nullable=True)
- download_url = Column(String(256), nullable=True)
- file_hash = Column(String(64), nullable=True)
- force_update = Column(Integer, default=0)
- download_status = Column(Integer, default=1) # 0=下架, 1=上架
- min_license_version = Column(String(32), nullable=True)
- publish_status = Column(Integer, default=0) # 0=未发布, 1=已发布
- create_time = Column(DateTime, default=datetime.utcnow)
- update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
- def __init__(self, **kwargs):
- super(DBVersion, self).__init__(**kwargs)
-
-# 设备表
-class DBDevice(Base):
- __tablename__ = "device"
-
- device_id = Column(Integer, primary_key=True)
- product_id = Column(String(32), nullable=False)
- machine_code = Column(String(64), nullable=False)
- software_version = Column(String(32), nullable=True)
- status = Column(Integer, default=1) # 0=禁用, 1=正常, 2=黑名单
- create_time = Column(DateTime, default=datetime.utcnow)
- last_verify_time = Column(DateTime, nullable=True)
-
- def __init__(self, **kwargs):
- super(DBDevice, self).__init__(**kwargs)
-
-# 产品表
-class DBProduct(Base):
- __tablename__ = "product"
-
- product_id = Column(String(32), primary_key=True)
- product_name = Column(String(64), nullable=False)
- description = Column(Text, nullable=True)
- status = Column(Integer, default=1) # 0=禁用, 1=正常
- create_time = Column(DateTime, default=datetime.utcnow)
- update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
- def __init__(self, **kwargs):
- super(DBProduct, self).__init__(**kwargs)
-
-# ==================== 用户管理接口 ====================
-@app.get("/")
-async def root():
- return {"message": "欢迎使用KaMiXiTong API测试平台", "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,
- db: Session = Depends(get_db)
-):
- """获取管理员列表"""
- query = db.query(DBAdmin).filter(DBAdmin.is_deleted == 0)
-
- if keyword:
- query = query.filter(DBAdmin.username.contains(keyword))
-
- if role is not None:
- query = query.filter(DBAdmin.role == role)
-
- if status is not None:
- query = query.filter(DBAdmin.status == status)
-
- admins = query.offset(skip).limit(limit).all()
- return admins
-
-@app.post("/admins", response_model=AdminInDB)
-async def create_admin(admin: AdminCreate, db: Session = Depends(get_db)):
- """创建管理员"""
- # 检查用户名是否已存在
- existing = db.query(DBAdmin).filter(
- DBAdmin.username == admin.username,
- DBAdmin.is_deleted == 0
- ).first()
-
- if existing:
- raise HTTPException(status_code=400, detail="用户名已存在")
-
- # 创建管理员(简化密码处理)
- db_admin = DBAdmin(
- username=admin.username,
- email=admin.email,
- role=admin.role,
- status=admin.status,
- password_hash=f"hashed_{admin.password}" # 简化处理
- )
-
- db.add(db_admin)
- db.commit()
- db.refresh(db_admin)
- return db_admin
-
-@app.get("/admins/{admin_id}", response_model=AdminInDB)
-async def get_admin(admin_id: int, db: Session = Depends(get_db)):
- """获取管理员详情"""
- admin = db.query(DBAdmin).filter(
- DBAdmin.admin_id == admin_id,
- DBAdmin.is_deleted == 0
- ).first()
-
- if not admin:
- raise HTTPException(status_code=404, detail="管理员不存在")
- return admin
-
-@app.put("/admins/{admin_id}", response_model=AdminInDB)
-async def update_admin(admin_id: int, admin: AdminUpdate, db: Session = Depends(get_db)):
- """更新管理员"""
- db_admin = db.query(DBAdmin).filter(
- DBAdmin.admin_id == admin_id,
- DBAdmin.is_deleted == 0
- ).first()
-
- if not db_admin:
- raise HTTPException(status_code=404, detail="管理员不存在")
-
- # 更新字段
- if admin.username and admin.username != db_admin.username:
- # 检查新用户名是否已存在
- existing = db.query(DBAdmin).filter(
- DBAdmin.username == admin.username,
- DBAdmin.admin_id != admin_id,
- DBAdmin.is_deleted == 0
- ).first()
-
- if existing:
- raise HTTPException(status_code=400, detail="用户名已存在")
- db_admin.username = admin.username
-
- if admin.email is not None:
- db_admin.email = admin.email
- if admin.role is not None:
- db_admin.role = admin.role
- if admin.status is not None:
- db_admin.status = admin.status
- if admin.password:
- db_admin.password_hash = f"hashed_{admin.password}" # 简化处理
-
- db.commit()
- db.refresh(db_admin)
- return db_admin
-
-@app.delete("/admins/{admin_id}")
-async def delete_admin(admin_id: int, db: Session = Depends(get_db)):
- """删除管理员(软删除)"""
- db_admin = db.query(DBAdmin).filter(
- DBAdmin.admin_id == admin_id,
- DBAdmin.is_deleted == 0
- ).first()
-
- if not db_admin:
- raise HTTPException(status_code=404, detail="管理员不存在")
-
- db_admin.is_deleted = 1
- db.commit()
- return {"message": "管理员删除成功"}
-
-@app.post("/admins/{admin_id}/toggle-status")
-async def toggle_admin_status(admin_id: int, db: Session = Depends(get_db)):
- """切换管理员状态"""
- db_admin = db.query(DBAdmin).filter(
- DBAdmin.admin_id == admin_id,
- DBAdmin.is_deleted == 0
- ).first()
-
- if not db_admin:
- raise HTTPException(status_code=404, detail="管理员不存在")
-
- db_admin.status = 0 if db_admin.status == 1 else 1
- db.commit()
-
- status_name = "正常" if db_admin.status == 1 else "禁用"
- action = "启用" if db_admin.status == 1 else "禁用"
- return {"message": f"管理员已{action}", "status": db_admin.status, "status_name": status_name}
-
-# ==================== 工单管理接口 ====================
-@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,
- db: Session = Depends(get_db)
-):
- """获取工单列表"""
- query = db.query(DBTicket)
-
- if status is not None:
- query = query.filter(DBTicket.status == status)
- if priority is not None:
- query = query.filter(DBTicket.priority == priority)
- if product_id:
- query = query.filter(DBTicket.product_id == product_id)
-
- query = query.order_by(DBTicket.create_time.desc())
- tickets = query.offset(skip).limit(limit).all()
- return tickets
-
-@app.post("/tickets", response_model=TicketInDB)
-async def create_ticket(ticket: TicketCreate, db: Session = Depends(get_db)):
- """创建工单"""
- # 验证产品存在(简化处理)
- db_ticket = DBTicket(**ticket.model_dump())
- db.add(db_ticket)
- db.commit()
- db.refresh(db_ticket)
- return db_ticket
-
-@app.put("/tickets/batch/status")
-async def batch_update_ticket_status(
- ticket_ids: List[int],
- status: int,
- remark: Optional[str] = None,
- db: Session = Depends(get_db)
-):
- """批量更新工单状态"""
- if status not in [0, 1, 2, 3]:
- raise HTTPException(status_code=400, detail="无效的状态值")
-
- # 查找所有要更新的工单
- tickets = db.query(DBTicket).filter(DBTicket.ticket_id.in_(ticket_ids)).all()
- if len(tickets) != len(ticket_ids):
- found_ids = [t.ticket_id for t in tickets]
- missing_ids = [tid for tid in ticket_ids if tid not in found_ids]
- raise HTTPException(status_code=404, detail=f"以下工单不存在: {', '.join(map(str, missing_ids))}")
-
- # 批量更新工单状态
- for ticket in tickets:
- ticket.status = status
- ticket.update_time = datetime.utcnow()
-
- db.commit()
-
- status_names = {0: '待处理', 1: '处理中', 2: '已解决', 3: '已关闭'}
- status_name = status_names.get(status, '未知')
- return {"message": f"成功将 {len(tickets)} 个工单状态更新为{status_name}"}
-
-# ==================== 卡密管理接口 ====================
-@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,
- db: Session = Depends(get_db)
-):
- """获取卡密列表"""
- query = db.query(DBLicense)
-
- if product_id:
- query = query.filter(DBLicense.product_id == product_id)
- if status is not None:
- query = query.filter(DBLicense.status == status)
- if license_type is not None:
- query = query.filter(DBLicense.type == license_type)
- if keyword:
- query = query.filter(func.lower(DBLicense.license_key).like(f"%{keyword.lower()}%"))
-
- query = query.order_by(DBLicense.create_time.desc())
- licenses = query.offset(skip).limit(limit).all()
- return licenses
-
-@app.post("/licenses", response_model=dict)
-async def generate_licenses(license: LicenseCreate, db: Session = Depends(get_db)):
- """批量生成卡密"""
- # 验证参数
- 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
-
- 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):
- existing = db.query(DBLicense).filter(DBLicense.license_key == key).first()
- 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)
-
- # 创建卡密对象
- db_license = DBLicense(
- product_id=license.product_id,
- license_key=key,
- type=license.type,
- status=0, # 未使用
- expire_time=expire_time
- )
- licenses.append(db_license)
-
- # 批量保存
- db.add_all(licenses)
- db.commit()
-
- # 格式化结果
- license_data = []
- for db_license in licenses:
- db.refresh(db_license)
- license_data.append(LicenseInDB.model_validate(db_license))
-
- return {
- "message": f"成功生成 {license.count} 个卡密",
- "licenses": license_data,
- "count": len(licenses)
- }
-
-# ==================== 版本管理接口 ====================
-@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,
- db: Session = Depends(get_db)
-):
- """获取版本列表"""
- query = db.query(DBVersion)
-
- if product_id:
- query = query.filter(DBVersion.product_id == product_id)
- if publish_status is not None:
- query = query.filter(DBVersion.publish_status == publish_status)
-
- query = query.order_by(DBVersion.create_time.desc())
- versions = query.offset(skip).limit(limit).all()
- return versions
-
-@app.post("/versions", response_model=VersionInDB)
-async def create_version(version: VersionCreate, db: Session = Depends(get_db)):
- """创建版本"""
- # 验证产品存在(简化处理)
- if not version.product_id or not version.version_num:
- raise HTTPException(status_code=400, detail="缺少必要参数")
-
- # 检查版本号是否重复
- existing = db.query(DBVersion).filter(
- DBVersion.product_id == version.product_id,
- DBVersion.version_num == version.version_num
- ).first()
-
- if existing:
- raise HTTPException(status_code=400, detail="版本号已存在")
-
- # 创建版本
- db_version = DBVersion(**version.model_dump(exclude={'publish_now'}))
- db.add(db_version)
- db.commit()
- db.refresh(db_version)
-
- # 如果选择了立即发布,则发布版本
- if version.publish_now:
- db_version.publish_status = 1
- db.commit()
- db.refresh(db_version)
-
- return db_version
-
-@app.post("/versions/{version_id}/publish")
-async def publish_version(version_id: int, db: Session = Depends(get_db)):
- """发布版本"""
- version = db.query(DBVersion).filter(DBVersion.version_id == version_id).first()
- if not version:
- raise HTTPException(status_code=404, detail="版本不存在")
-
- version.publish_status = 1
- version.update_time = datetime.utcnow()
- db.commit()
- db.refresh(version)
-
- return {"message": "版本发布成功", "version": VersionInDB.model_validate(version)}
-
-# ==================== 设备管理接口 ====================
-@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,
- db: Session = Depends(get_db)
-):
- """获取设备列表"""
- query = db.query(DBDevice)
-
- if product_id:
- query = query.filter(DBDevice.product_id == product_id)
- if software_version:
- query = query.filter(DBDevice.software_version == software_version)
- if status is not None:
- query = query.filter(DBDevice.status == status)
- if keyword:
- query = query.filter(DBDevice.machine_code.contains(keyword))
-
- query = query.order_by(DBDevice.last_verify_time.desc())
- devices = query.offset(skip).limit(limit).all()
- return devices
-
-@app.put("/devices/{device_id}/status")
-async def update_device_status(device_id: int, status: int, db: Session = Depends(get_db)):
- """更新设备状态"""
- if status not in [0, 1, 2]:
- raise HTTPException(status_code=400, detail="无效的状态值")
-
- device = db.query(DBDevice).filter(DBDevice.device_id == device_id).first()
- if not device:
- raise HTTPException(status_code=404, detail="设备不存在")
-
- device.status = status
- device.last_verify_time = datetime.utcnow()
- db.commit()
- db.refresh(device)
-
- return {"message": "设备状态更新成功", "device": DeviceInDB.model_validate(device)}
-
-@app.delete("/devices/{device_id}")
-async def delete_device(device_id: int, db: Session = Depends(get_db)):
- """删除设备"""
- device = db.query(DBDevice).filter(DBDevice.device_id == device_id).first()
- if not device:
- raise HTTPException(status_code=404, detail="设备不存在")
-
- db.delete(device)
- db.commit()
- return {"message": "设备删除成功"}
-
-@app.delete("/devices/batch")
-async def batch_delete_devices(device_ids: List[int], db: Session = Depends(get_db)):
- """批量删除设备"""
- # 查找所有要删除的设备
- devices = db.query(DBDevice).filter(DBDevice.device_id.in_(device_ids)).all()
- if len(devices) != len(device_ids):
- found_ids = [d.device_id for d in devices]
- missing_ids = [did for did in device_ids if did not in found_ids]
- raise HTTPException(status_code=404, detail=f"以下设备不存在: {', '.join(map(str, missing_ids))}")
-
- # 批量删除设备
- for device in devices:
- db.delete(device)
-
- db.commit()
- return {"message": f"成功删除 {len(devices)} 个设备"}
-
-# ==================== 产品管理接口 ====================
-@app.get("/products", response_model=List[ProductInDB])
-async def get_products(
- skip: int = 0,
- limit: int = 100,
- keyword: Optional[str] = None,
- db: Session = Depends(get_db)
-):
- """获取产品列表"""
- query = db.query(DBProduct)
-
- if keyword:
- query = query.filter(
- DBProduct.product_name.like(f"%{keyword}%") |
- DBProduct.description.like(f"%{keyword}%")
- )
-
- query = query.order_by(DBProduct.create_time.desc())
- products = query.offset(skip).limit(limit).all()
- return products
-
-@app.post("/products", response_model=ProductInDB)
-async def create_product(product: ProductCreate, db: Session = Depends(get_db)):
- """创建产品"""
- if not product.product_name.strip():
- raise HTTPException(status_code=400, detail="产品名称不能为空")
-
- # 检查自定义ID是否重复
- if product.product_id:
- existing = db.query(DBProduct).filter(DBProduct.product_id == product.product_id).first()
- 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()
-
- db_product = DBProduct(
- product_id=product_id,
- product_name=product.product_name,
- description=product.description,
- status=product.status
- )
-
- db.add(db_product)
- db.commit()
- db.refresh(db_product)
- return db_product
-
-@app.get("/products/{product_id}", response_model=ProductInDB)
-async def get_product(product_id: str, db: Session = Depends(get_db)):
- """获取产品详情"""
- product = db.query(DBProduct).filter(DBProduct.product_id == product_id).first()
- if not product:
- raise HTTPException(status_code=404, detail="产品不存在")
- return product
-
-@app.put("/products/{product_id}", response_model=ProductInDB)
-async def update_product(product_id: str, product: ProductUpdate, db: Session = Depends(get_db)):
- """更新产品"""
- db_product = db.query(DBProduct).filter(DBProduct.product_id == product_id).first()
- if not db_product:
- raise HTTPException(status_code=404, detail="产品不存在")
-
- # 更新字段
- if product.product_name is not None:
- db_product.product_name = product.product_name
- if product.description is not None:
- db_product.description = product.description
- if product.status is not None:
- db_product.status = product.status
-
- db_product.update_time = datetime.utcnow()
- db.commit()
- db.refresh(db_product)
- return db_product
-
-@app.delete("/products/{product_id}")
-async def delete_product(product_id: str, db: Session = Depends(get_db)):
- """删除产品"""
- product = db.query(DBProduct).filter(DBProduct.product_id == product_id).first()
- if not product:
- raise HTTPException(status_code=404, detail="产品不存在")
-
- db.delete(product)
- db.commit()
- return {"message": "产品删除成功"}
-
-if __name__ == "__main__":
- import uvicorn
- # 使用127.0.0.1而不是0.0.0.0来避免权限问题
- uvicorn.run(app, host="127.0.0.1", port=9003, log_level="info")
\ No newline at end of file
diff --git a/api_test_app_mysql.py b/api_test_app_mysql.py
deleted file mode 100644
index 5768bfb..0000000
--- a/api_test_app_mysql.py
+++ /dev/null
@@ -1,1108 +0,0 @@
-#!/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")
\ No newline at end of file
diff --git a/app/models/audit_log.py b/app/models/audit_log.py
index e93c1c4..4834301 100644
--- a/app/models/audit_log.py
+++ b/app/models/audit_log.py
@@ -1,5 +1,6 @@
from datetime import datetime
from app import db
+import json
class AuditLog(db.Model):
"""审计日志模型"""
@@ -23,6 +24,14 @@ class AuditLog(db.Model):
def to_dict(self):
"""转换为字典"""
+ # 解析details字段中的JSON字符串
+ details_data = None
+ if self.details:
+ try:
+ details_data = json.loads(self.details)
+ except (json.JSONDecodeError, TypeError):
+ details_data = self.details # 如果解析失败,返回原始字符串
+
return {
'log_id': self.log_id,
'admin_id': self.admin_id,
@@ -30,7 +39,7 @@ class AuditLog(db.Model):
'action': self.action,
'target_type': self.target_type,
'target_id': self.target_id,
- 'details': self.details,
+ 'details': details_data,
'ip_address': self.ip_address,
'user_agent': self.user_agent,
'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time else None
@@ -41,12 +50,20 @@ class AuditLog(db.Model):
"""记录审计日志"""
from flask import current_app
try:
+ # 将details字典序列化为JSON字符串
+ details_str = None
+ if details is not None:
+ if isinstance(details, dict):
+ details_str = json.dumps(details, ensure_ascii=False)
+ else:
+ details_str = str(details)
+
log = AuditLog(
admin_id=admin_id,
action=action,
target_type=target_type,
target_id=target_id,
- details=details,
+ details=details_str,
ip_address=ip_address,
user_agent=user_agent
)
diff --git a/auth_validator.py b/app/utils/auth_validator.py
similarity index 79%
rename from auth_validator.py
rename to app/utils/auth_validator.py
index 263f5ea..a4fa0b5 100644
--- a/auth_validator.py
+++ b/app/utils/auth_validator.py
@@ -82,13 +82,13 @@ class MachineCodeGenerator:
# 简化的加密工具
class SimpleCrypto:
"""简单的加密解密工具"""
-
+
@staticmethod
def generate_hash(data: str, salt: str = "") -> str:
"""生成哈希值"""
combined = f"{data}{salt}".encode('utf-8')
return hashlib.sha256(combined).hexdigest()
-
+
@staticmethod
def generate_signature(data: str, secret_key: str) -> str:
"""生成签名"""
@@ -146,7 +146,7 @@ class AuthValidator:
def __init__(self,
software_id: str,
api_url: str = "http://localhost:5000/api/v1",
- secret_key: str = "default-secret-key",
+ secret_key: str = "taiyi1224",
cache_days: int = 7,
timeout: int = 3,
gui_mode: bool = False):
@@ -228,10 +228,11 @@ class AuthValidator:
return ""
def _validate_license_format(self, license_key: str) -> bool:
- """验证卡密格式(支持XXXX-XXXX-XXXX-XXXX格式)"""
+ """验证卡密格式(与服务端保持一致)"""
if not license_key:
return False
+ # 去除空格和制表符,并转为大写
license_key = license_key.strip().replace(' ', '').replace('\t', '').upper()
# 检查是否为XXXX-XXXX-XXXX-XXXX格式
@@ -242,8 +243,8 @@ class AuthValidator:
# 检查所有字符是否为大写字母或数字
combined = ''.join(parts)
if len(combined) == 32:
- import re
pattern = r'^[A-Z0-9]+$'
+ import re
return bool(re.match(pattern, combined))
return False
else:
@@ -252,8 +253,8 @@ class AuthValidator:
return False
# 检查字符(只允许大写字母、数字和下划线)
- import re
pattern = r'^[A-Z0-9_]+$'
+ import re
return bool(re.match(pattern, license_key))
def _input_license_key(self) -> str:
@@ -406,117 +407,55 @@ class AuthValidator:
max_attempts = 3 # 最多尝试3次
for attempt in range(max_attempts):
- # 输入卡密
+ # 获取卡密
license_key = self._input_license_key()
if not license_key:
- self._show_message("验证取消", "未输入卡密,程序退出", True)
+ self._show_message("验证取消", "用户取消了验证操作")
return False
# 验证卡密格式
if not self._validate_license_format(license_key):
- self._show_message("格式错误", "卡密格式错误,请检查后重新输入", True)
+ self._show_message("格式错误", "卡密格式不正确,请重新输入", True)
continue
# 在线验证
success, message, auth_info = self._online_verify(license_key)
-
- if success and auth_info:
- # 验证成功,缓存授权信息
- self._cache_auth_info(auth_info)
-
- # 检查是否需要更新
- force_update = auth_info.get('force_update', False)
- download_url = auth_info.get('download_url')
- new_version = auth_info.get('new_version')
-
- if force_update and download_url:
- self._show_message("需要更新", f"发现新版本 {new_version}\n请下载更新后重新启动程序", True)
- # 尝试打开下载链接
- try:
- import webbrowser
- webbrowser.open(download_url)
- except:
- pass
- return False
-
- self._show_message("验证成功", f"授权验证成功!\n卡密: {license_key}\n有效期至: {auth_info.get('expire_time', '永久')}")
+
+ if success:
+ # 缓存授权信息
+ if auth_info:
+ self._cache_auth_info(auth_info)
+ self._show_message("验证成功", message)
return True
-
else:
- # 验证失败
+ # 记录失败尝试
self.failed_attempts += 1
- self.last_attempt_time = datetime.utcnow()
-
- if self.failed_attempts >= 5: # 失败5次锁定
- self._lock_account()
+ if self.failed_attempts >= 3:
+ self._lock_account(10) # 锁定10分钟
self._show_message("验证失败", self._get_lock_message(), True)
return False
-
- self._show_message("验证失败", f"验证失败: {message}\n剩余尝试次数: {5 - self.failed_attempts}", True)
-
- # 如果不是最后一次尝试,询问是否继续
- if attempt < max_attempts - 1:
- if self.gui_mode:
- try:
- import tkinter as tk
- from tkinter import messagebox
-
- root = tk.Tk()
- root.withdraw()
- result = messagebox.askyesno("继续验证", "是否继续输入卡密验证?")
- root.destroy()
-
- if not result:
- return False
- except ImportError:
- continue
- else:
- continue
else:
- return False
+ remaining_attempts = 3 - self.failed_attempts
+ self._show_message(
+ "验证失败",
+ f"{message}\n\n剩余尝试次数: {remaining_attempts}",
+ True
+ )
+ continue
+ # 所有尝试都失败
+ self._show_message("验证失败", "已达到最大尝试次数", True)
return False
- def get_software_info(self) -> Optional[Dict[str, Any]]:
- """获取软件信息"""
- try:
- url = f"{self.api_url}/software/info"
- params = {"software_id": self.software_id}
+ def get_auth_info(self) -> Optional[Dict[str, Any]]:
+ """
+ 获取当前授权信息
+
+ Returns:
+ Optional[Dict[str, Any]]: 授权信息
+ """
+ return self.cache.get_auth_info(self.software_id)
- response = requests.get(url, params=params, timeout=self.timeout)
- if response.status_code == 200:
- result = response.json()
- if result.get('success'):
- return result.get('data')
- except Exception:
- pass
- return None
-
- def clear_cache(self):
- """清除本地缓存"""
- self.cache.clear_cache(self.software_id)
- # 删除机器码缓存文件
- try:
- if os.path.exists(".machine_code"):
- os.remove(".machine_code")
- except Exception:
- pass
-
-# 便捷函数
-def validate_license(software_id: str, **kwargs) -> bool:
- """
- 便捷的验证函数
-
- Args:
- software_id: 软件ID
- **kwargs: 其他参数(api_url, secret_key, cache_days, timeout, gui_mode)
-
- Returns:
- bool: 验证是否成功
- """
- validator = AuthValidator(software_id, **kwargs)
- return validator.validate()
-
-def get_machine_code() -> str:
- """获取当前机器码"""
- return MachineCodeGenerator.generate()
\ No newline at end of file
+ def clear_auth_cache(self):
+ """清除授权缓存"""
+ self.cache.clear_cache(self.software_id)
\ No newline at end of file
diff --git a/check_db.py b/check_db.py
deleted file mode 100644
index c887bb9..0000000
--- a/check_db.py
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-检查数据库中的产品数据
-"""
-
-import os
-import sys
-
-# 添加项目根目录到Python路径
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-# 尝试加载.env文件
-try:
- from dotenv import load_dotenv
- if load_dotenv():
- print("成功加载.env文件")
- else:
- print("未找到或无法加载.env文件")
-except ImportError:
- print("python-dotenv未安装,跳过.env文件加载")
-
-from app import create_app, db
-from app.models import Product
-
-# 创建应用实例
-app = create_app()
-
-with app.app_context():
- print("数据库URI:", app.config['SQLALCHEMY_DATABASE_URI'])
- total_products = Product.query.count()
- print(f"产品总数: {total_products}")
-
- if total_products > 0:
- print("产品列表:")
- products = Product.query.all()
- for product in products:
- print(f" - ID: {product.product_id}, 名称: {product.product_name}, 状态: {product.status}")
- else:
- print("数据库中没有产品数据")
\ No newline at end of file
diff --git a/check_log_db.py b/check_log_db.py
deleted file mode 100644
index 5507bd8..0000000
--- a/check_log_db.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import sqlite3
-import os
-
-def check_audit_logs():
- """检查审计日志表"""
- try:
- # 连接到数据库
- if os.path.exists('instance/kamaxitong.db'):
- conn = sqlite3.connect('instance/kamaxitong.db')
- cursor = conn.cursor()
-
- # 查询审计日志表
- print("=== 查询审计日志表 ===")
- cursor.execute("SELECT * FROM audit_log ORDER BY create_time DESC LIMIT 10")
- rows = cursor.fetchall()
-
- if rows:
- print(f"找到 {len(rows)} 条审计日志记录:")
- # 获取列名
- column_names = [description[0] for description in cursor.description]
- print("列名:", column_names)
-
- for row in rows:
- print(row)
- else:
- print("审计日志表为空")
-
- conn.close()
- else:
- print("数据库文件不存在")
-
- except Exception as e:
- print(f"检查审计日志时出现错误: {e}")
-
-def check_log_file():
- """检查日志文件"""
- try:
- print("\n=== 检查日志文件 ===")
- if os.path.exists('logs/kamaxitong.log'):
- # 以二进制模式读取文件
- with open('logs/kamaxitong.log', 'rb') as f:
- content = f.read()
- print(f"日志文件大小: {len(content)} 字节")
-
- # 尝试以不同编码读取
- try:
- text_content = content.decode('utf-8')
- lines = text_content.split('\n')
- print(f"日志文件行数: {len(lines)}")
- print("最后10行:")
- for line in lines[-10:]:
- if line.strip():
- print(line.strip())
- except UnicodeDecodeError:
- # 尝试其他编码
- try:
- text_content = content.decode('gbk')
- lines = text_content.split('\n')
- print(f"日志文件行数: {len(lines)} (GBK编码)")
- print("最后10行:")
- for line in lines[-10:]:
- if line.strip():
- print(line.strip())
- except UnicodeDecodeError:
- print("无法解码日志文件内容")
- else:
- print("日志文件不存在")
- except Exception as e:
- print(f"检查日志文件时出现错误: {e}")
-
-if __name__ == "__main__":
- print("检查日志系统...")
- check_audit_logs()
- check_log_file()
\ No newline at end of file
diff --git a/check_products.py b/check_products.py
deleted file mode 100644
index 3a67534..0000000
--- a/check_products.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import os
-os.environ.setdefault('FLASK_CONFIG', 'development')
-
-from app import create_app, db
-from app.models import Product
-
-app = create_app()
-
-with app.app_context():
- products = Product.query.all()
- print('Total products:', len(products))
- print('Product names:', [p.product_name for p in products])
- print('Product details:')
- for p in products:
- print(f' ID: {p.product_id}, Name: {p.product_name}, Status: {p.status}')
\ No newline at end of file
diff --git a/login_page.html b/login_page.html
deleted file mode 100644
index bf99525..0000000
Binary files a/login_page.html and /dev/null differ
diff --git a/logs/kamaxitong.log b/logs/kamaxitong.log
index 5f36527..383b3b5 100644
--- a/logs/kamaxitong.log
+++ b/logs/kamaxitong.log
@@ -1,80 +1,51 @@
-2025-11-15 21:43:14,795 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:43:16,011 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:47:51,654 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:47:58,940 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:52:14,483 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:52:15,836 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:53:12,767 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:53:12,928 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:55:02,780 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:55:05,971 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:56:57,235 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:56:57,396 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:00:51,287 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:02:34,407 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:02:36,353 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:03:17,696 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:03:18,658 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:05:58,479 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:05:58,767 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:12:33,295 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:12:34,640 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:17,117 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:17,190 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:18,245 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:24,345 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:24,378 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:24,686 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:30,459 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:30,476 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:30,487 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:35,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:35,074 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:35,227 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:43,938 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:44,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:44,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:55,395 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:55,501 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:14:56,055 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:00,950 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:01,093 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:01,290 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:10,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:12,443 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:13,140 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:22,104 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:22,684 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:22,758 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:36,038 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:37,583 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:37,723 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:55,634 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:55,694 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:15:55,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:05,643 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:05,813 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:06,131 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:34,892 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:34,916 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:35,168 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:52,773 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:52,821 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:16:53,293 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:18:58,174 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:18:59,209 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:20:36,569 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'old\': {\'email\': "\'2339117167@qq.com\'", \'role\': \'1\', \'status\': \'1\'}, \'new\': {\'em\' at line 1')
+2025-11-16 13:06:04,834 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:06:06,705 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:06:36,685 INFO: ʼ汾 [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:98]
+2025-11-16 13:06:36,685 INFO: Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBaWqjoVX0KdrVN1Z [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:99]
+2025-11-16 13:06:36,685 INFO: : POST [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:100]
+2025-11-16 13:06:36,686 INFO: URL: http://127.0.0.1:5000/api/v1/versions [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:101]
+2025-11-16 13:06:36,690 INFO: ͷ: {'Host': '127.0.0.1:5000', 'Connection': 'keep-alive', 'Content-Length': '769', 'Sec-Ch-Ua-Platform': '"Windows"', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'Sec-Ch-Ua': '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"', 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryBaWqjoVX0KdrVN1Z', 'Sec-Ch-Ua-Mobile': '?0', 'Accept': '*/*', 'Origin': 'http://127.0.0.1:5000', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', 'Referer': 'http://127.0.0.1:5000/versions/create', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Cookie': 'remember_token=1|ae366cbb6a7c117a69a287f322b91e6d75e14efd338ed4249fe139a40620856246a057db8c7c1df9883347210e17f379727e0d28d269d0002fc5126ec50d83d8; session=eyJfZnJlc2giOmZhbHNlLCJfdXNlcl9pZCI6IjEifQ.aRlGfA.q1DLjLrFgNZLvNT_ChqsVKj_Uyc'} [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:102]
+2025-11-16 13:06:36,690 INFO: [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:106]
+2025-11-16 13:06:36,691 INFO: յIJ: product_id=ArticleReplace, version_num=1.0 [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:141]
+2025-11-16 13:06:36,691 INFO: ֤ƷǷ: product_id=ArticleReplace [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:147]
+2025-11-16 13:06:36,691 INFO: ִвƷѯ... [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:149]
+2025-11-16 13:06:36,693 INFO: ݿвƷ: [('ArticleReplace', 'д')] [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:153]
+2025-11-16 13:06:36,695 INFO: Ʒѯ: [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:157]
+2025-11-16 13:06:36,695 INFO: 汾Ƿظ: product_id=ArticleReplace, version_num=1.0 [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:162]
+2025-11-16 13:06:36,697 INFO: 汾: product_id=ArticleReplace, version_num=1.0 [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:172]
+2025-11-16 13:06:36,699 INFO: Ӱ汾ݿ [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:186]
+2025-11-16 13:06:36,699 INFO: ύݿ [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:188]
+2025-11-16 13:06:36,708 INFO: Ƿ: publish_now=False [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:191]
+2025-11-16 13:06:36,715 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_id\': "\'ArticleReplace\'", \'version_num\': "\'1.0\'", \'publish_now\': \'0\'}, \'\' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
-[parameters: {'admin_id': 2, 'action': 'UPDATE', 'target_type': 'ADMIN', 'target_id': 2, 'details': {'old': {'email': '2339117167@qq.com', 'role': 1, 'status': 1}, 'new': {'email': '2339117167@qq.com', 'role': 0, 'status': 1}}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 20, 36, 556170)}]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
-2025-11-15 22:20:36,594 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'old\': {\'email\': "\'2339117167@qq.com\'", \'role\': \'1\', \'status\': \'1\'}, \'new\': {\'em\' at line 1')
+[parameters: {'admin_id': 1, 'action': 'CREATE_VERSION', 'target_type': 'VERSION', 'target_id': 2, 'details': {'product_id': 'ArticleReplace', 'version_num': '1.0', 'publish_now': False}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 6, 36, 714215)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 13:06:43,379 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'version_num\': "\'1.0\'", \'status\': \'1\', \'status_name\': "\'\'"}, \'127.0.0.1\', \' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
-[parameters: {'admin_id': 2, 'action': 'UPDATE_ADMIN', 'target_type': 'ADMIN', 'target_id': 2, 'details': {'old': {'email': '2339117167@qq.com', 'role': 1, 'status': 1}, 'new': {'email': '2339117167@qq.com', 'role': 0, 'status': 1}}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 20, 36, 584730)}]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
-2025-11-15 22:21:39,838 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'username\': "\'test\'", \'role\': \'0\', \'status\': \'1\'}, \'127.0.0.1\', \'Mozilla/5.0 (Wi\' at line 1')
+[parameters: {'admin_id': 1, 'action': 'UPDATE_VERSION_STATUS', 'target_type': 'VERSION', 'target_id': 2, 'details': {'version_num': '1.0', 'status': 1, 'status_name': ''}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 6, 43, 377554)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 13:07:09,664 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_id\': "\'ArticleReplace\'", \'count\': \'1\', \'license_type\': \'1\', \'license_ke\' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
-[parameters: {'admin_id': 2, 'action': 'CREATE', 'target_type': 'ADMIN', 'target_id': 5, 'details': {'username': 'test', 'role': 0, 'status': 1}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 21, 39, 834164)}]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
-2025-11-15 22:21:46,162 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:21:46,434 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 22:21:46,482 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+[parameters: {'admin_id': 1, 'action': 'GENERATE_LICENSES', 'target_type': 'LICENSE', 'target_id': None, 'details': {'product_id': 'ArticleReplace', 'count': 1, 'license_type': 1, 'license_keys': ['4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI']}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 7, 9, 662553)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 13:07:44,918 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:10:12,691 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:11:09,687 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:14:50,942 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:15:22,908 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:15:40,597 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:17:33,606 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:18:26,492 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:18:38,007 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:18:49,532 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:21:42,076 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:21:54,735 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:24:17,919 ERROR: ¿ʧ: 'License' object has no attribute 'remark' [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:270]
+2025-11-16 13:24:20,319 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'license_key\': "\'4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI\'"}, \'127.0.0.1\', \'Mozilla/5\' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 1, 'action': 'DELETE_LICENSE', 'target_type': 'LICENSE', 'target_id': None, 'details': {'license_key': '4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 24, 20, 318782)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 13:24:53,054 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:26:10,998 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:32:27,257 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 13:32:51,116 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
diff --git a/logs/kamaxitong.log.10 b/logs/kamaxitong.log.10
index 88d8fd2..5f36527 100644
--- a/logs/kamaxitong.log.10
+++ b/logs/kamaxitong.log.10
@@ -1,102 +1,80 @@
-2025-11-15 14:20:05,639 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:20:07,103 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:20:55,708 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:20:57,062 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:21:28,777 ERROR: ȡͳʧ: (pymysql.err.ProgrammingError) (1146, "Table 'kamaxitong.api' doesn't exist")
-[SQL: SELECT count(*) AS count_1
-FROM (SELECT api.api_id AS api_api_id, api.api_name AS api_api_name, api.description AS api_description, api.status AS api_status, api.create_time AS api_create_time, api.update_time AS api_update_time
-FROM api) AS anon_1]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\api\statistics.py:86]
-2025-11-15 14:22:29,153 ERROR: ȡͳʧ: (pymysql.err.ProgrammingError) (1146, "Table 'kamaxitong.api' doesn't exist")
-[SQL: SELECT count(*) AS count_1
-FROM (SELECT api.api_id AS api_api_id, api.api_name AS api_api_name, api.description AS api_description, api.status AS api_status, api.create_time AS api_create_time, api.update_time AS api_update_time
-FROM api) AS anon_1]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\api\statistics.py:86]
-2025-11-15 14:22:40,129 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:23:10,648 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:23:11,851 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:23:23,719 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:23:29,274 ERROR: ȡͳʧ: (pymysql.err.ProgrammingError) (1146, "Table 'kamaxitong.api' doesn't exist")
-[SQL: SELECT count(*) AS count_1
-FROM (SELECT api.api_id AS api_api_id, api.api_name AS api_api_name, api.description AS api_description, api.status AS api_status, api.create_time AS api_create_time, api.update_time AS api_update_time
-FROM api) AS anon_1]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\api\statistics.py:86]
-2025-11-15 14:24:29,144 ERROR: ȡͳʧ: (pymysql.err.ProgrammingError) (1146, "Table 'kamaxitong.api' doesn't exist")
-[SQL: SELECT count(*) AS count_1
-FROM (SELECT api.api_id AS api_api_id, api.api_name AS api_api_name, api.description AS api_description, api.status AS api_status, api.create_time AS api_create_time, api.update_time AS api_update_time
-FROM api) AS anon_1]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\api\statistics.py:86]
-2025-11-15 14:25:07,065 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:25:07,424 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:25:19,299 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:25:29,262 ERROR: ȡͳʧ: (pymysql.err.ProgrammingError) (1146, "Table 'kamaxitong.api' doesn't exist")
-[SQL: SELECT count(*) AS count_1
-FROM (SELECT api.api_id AS api_api_id, api.api_name AS api_api_name, api.description AS api_description, api.status AS api_status, api.create_time AS api_create_time, api.update_time AS api_update_time
-FROM api) AS anon_1]
-(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\api\statistics.py:86]
-2025-11-15 14:26:36,221 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:26:38,495 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:27:13,078 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:27:20,119 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:28:43,984 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:29:11,098 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:29:42,354 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:29:53,601 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:30:24,671 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:30:26,018 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:34:01,346 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:34:02,697 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:16,572 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:16,603 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:26,594 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:26,878 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:30,511 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:40:31,139 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:41:45,467 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:41:45,860 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:41:49,111 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:41:49,214 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:42:22,440 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 14:42:22,747 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:08:23,832 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:08:23,851 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:10:01,187 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:10:02,285 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:12:33,664 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:12:35,067 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:13:54,881 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:13:55,572 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:16:39,819 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:18:12,136 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:18:12,364 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:18:12,364 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:18:17,816 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:22:38,979 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:25:24,198 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:30:01,058 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:32:09,383 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:32:44,404 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:35:30,494 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:35:54,826 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:11,573 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:11,781 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:11,781 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:18,774 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:23,481 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:36:27,849 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:38:39,259 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 15:44:39,290 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 16:14:14,052 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 16:14:14,273 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 16:14:14,273 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 16:14:30,243 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 16:14:31,707 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 20:56:49,158 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:07:59,431 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:08:00,550 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:26:31,566 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:27:36,458 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:29:02,008 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:29:47,827 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:29:51,087 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
-2025-11-15 21:29:56,366 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:43:14,795 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:43:16,011 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:47:51,654 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:47:58,940 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:52:14,483 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:52:15,836 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:53:12,767 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:53:12,928 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:55:02,780 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:55:05,971 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:56:57,235 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 21:56:57,396 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:00:51,287 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:02:34,407 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:02:36,353 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:03:17,696 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:03:18,658 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:05:58,479 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:05:58,767 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:12:33,295 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:12:34,640 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:17,117 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:17,190 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:18,245 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:24,345 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:24,378 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:24,686 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:30,459 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:30,476 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:30,487 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:35,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:35,074 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:35,227 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:43,938 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:44,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:44,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:55,395 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:55,501 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:14:56,055 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:00,950 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:01,093 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:01,290 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:10,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:12,443 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:13,140 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:22,104 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:22,684 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:22,758 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:36,038 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:37,583 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:37,723 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:55,634 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:55,694 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:15:55,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:05,643 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:05,813 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:06,131 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:34,892 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:34,916 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:35,168 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:52,773 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:52,821 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:16:53,293 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:18:58,174 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:18:59,209 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:20:36,569 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'old\': {\'email\': "\'2339117167@qq.com\'", \'role\': \'1\', \'status\': \'1\'}, \'new\': {\'em\' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 2, 'action': 'UPDATE', 'target_type': 'ADMIN', 'target_id': 2, 'details': {'old': {'email': '2339117167@qq.com', 'role': 1, 'status': 1}, 'new': {'email': '2339117167@qq.com', 'role': 0, 'status': 1}}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 20, 36, 556170)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
+2025-11-15 22:20:36,594 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'old\': {\'email\': "\'2339117167@qq.com\'", \'role\': \'1\', \'status\': \'1\'}, \'new\': {\'em\' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 2, 'action': 'UPDATE_ADMIN', 'target_type': 'ADMIN', 'target_id': 2, 'details': {'old': {'email': '2339117167@qq.com', 'role': 1, 'status': 1}, 'new': {'email': '2339117167@qq.com', 'role': 0, 'status': 1}}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 20, 36, 584730)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
+2025-11-15 22:21:39,838 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'username\': "\'test\'", \'role\': \'0\', \'status\': \'1\'}, \'127.0.0.1\', \'Mozilla/5.0 (Wi\' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 2, 'action': 'CREATE', 'target_type': 'ADMIN', 'target_id': 5, 'details': {'username': 'test', 'role': 0, 'status': 1}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 15, 14, 21, 39, 834164)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\app\models\audit_log.py:59]
+2025-11-15 22:21:46,162 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:21:46,434 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
+2025-11-15 22:21:46,482 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\config.py:66]
diff --git a/logs/kamaxitong.log.2 b/logs/kamaxitong.log.2
new file mode 100644
index 0000000..1586e59
--- /dev/null
+++ b/logs/kamaxitong.log.2
@@ -0,0 +1,71 @@
+2025-11-15 23:58:20,986 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-15 23:58:22,596 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 11:34:44,087 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 11:34:48,148 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 11:36:10,246 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'ԲƷB\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_23EF726D', 'details': {'product_name': 'ԲƷB'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 10, 245687)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 11:36:15,609 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'ԲƷA\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_897EF967', 'details': {'product_name': 'ԲƷA'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 15, 608798)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 11:36:28,535 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'license_key\': "\'W7XGEZU0-MFLWWKUK-XWPWFW3V-0LE77N2K\'"}, \'127.0.0.1\', \'Mozilla/5\' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 1, 'action': 'DELETE_LICENSE', 'target_type': 'LICENSE', 'target_id': None, 'details': {'license_key': 'W7XGEZU0-MFLWWKUK-XWPWFW3V-0LE77N2K'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 28, 535307)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 11:36:34,617 ERROR: ¼־ʧ: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'ԲƷC\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
+[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
+[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_A24B55D2', 'details': {'product_name': 'ԲƷC'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 34, 616101)}]
+(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
+2025-11-16 12:12:41,268 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:13:10,962 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:13:13,050 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:14:26,744 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:14:34,528 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:14:44,691 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:56,754 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,016 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,016 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:16:59,517 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:17:24,098 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:22,890 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:27,735 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:31,439 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:40,107 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:43,784 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:21:53,199 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:01,231 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:03,158 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:05,098 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:07,063 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:11,957 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:22:59,514 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:23:01,359 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:32:48,638 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
+2025-11-16 12:33:31,339 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
diff --git a/simple_test.py b/simple_test.py
deleted file mode 100644
index f582a3f..0000000
--- a/simple_test.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import requests
-import json
-
-# 创建会话以保持登录状态
-session = requests.Session()
-
-def test_apis():
- """直接测试API接口"""
- try:
- # 1. 测试创建产品(这会生成操作日志)
- print("=== 测试创建产品 ===")
- product_data = {
- 'product_name': '测试产品',
- 'description': '这是一个测试产品'
- }
-
- response = session.post(
- "http://localhost:5000/api/v1/products",
- json=product_data,
- headers={'Content-Type': 'application/json'}
- )
- print(f"创建产品状态码: {response.status_code}")
- print(f"创建产品响应: {response.text}")
-
- # 2. 测试获取操作日志
- print("\n=== 测试获取操作日志 ===")
- response = session.get("http://localhost:5000/api/v1/logs")
- print(f"获取日志状态码: {response.status_code}")
- if response.status_code == 200:
- log_data = response.json()
- print(f"日志数据: {json.dumps(log_data, indent=2, ensure_ascii=False)}")
- else:
- print(f"获取日志失败: {response.text}")
-
- except Exception as e:
- print(f"测试过程中出现错误: {e}")
-
-if __name__ == "__main__":
- print("开始简单测试...")
- test_apis()
\ No newline at end of file
diff --git a/test_log.py b/test_log.py
deleted file mode 100644
index 13accd7..0000000
--- a/test_log.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import requests
-import json
-
-# 测试创建产品API
-def test_create_product():
- url = "http://localhost:5000/api/v1/products"
- headers = {"Content-Type": "application/json"}
- data = {
- "product_name": "测试产品",
- "description": "这是一个测试产品"
- }
-
- try:
- response = requests.post(url, headers=headers, data=json.dumps(data))
- print(f"Status Code: {response.status_code}")
- print(f"Response: {response.text}")
- except Exception as e:
- print(f"Error: {e}")
-
-if __name__ == "__main__":
- test_create_product()
\ No newline at end of file
diff --git a/test_log_with_auth.py b/test_log_with_auth.py
deleted file mode 100644
index f8ba1d1..0000000
--- a/test_log_with_auth.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import requests
-import json
-
-# 创建会话以保持登录状态
-session = requests.Session()
-
-def login():
- """登录系统"""
- url = "http://localhost:5000/api/v1/auth/login"
- headers = {"Content-Type": "application/json"}
- data = {
- "username": "admin",
- "password": "admin123"
- }
-
- try:
- response = session.post(url, headers=headers, data=json.dumps(data))
- print(f"Login Status Code: {response.status_code}")
- print(f"Login Response: {response.text}")
- return response.status_code == 200
- except Exception as e:
- print(f"Login Error: {e}")
- return False
-
-def test_create_product():
- """测试创建产品API"""
- url = "http://localhost:5000/api/v1/products"
- headers = {"Content-Type": "application/json"}
- data = {
- "product_name": "测试产品",
- "description": "这是一个测试产品"
- }
-
- try:
- response = session.post(url, headers=headers, data=json.dumps(data))
- print(f"Create Product Status Code: {response.status_code}")
- print(f"Create Product Response: {response.text}")
- return response.status_code == 200
- except Exception as e:
- print(f"Create Product Error: {e}")
- return False
-
-def test_get_logs():
- """测试获取日志API"""
- url = "http://localhost:5000/api/v1/logs"
- try:
- response = session.get(url)
- print(f"Get Logs Status Code: {response.status_code}")
- print(f"Get Logs Response: {response.text}")
- return response.status_code == 200
- except Exception as e:
- print(f"Get Logs Error: {e}")
- return False
-
-if __name__ == "__main__":
- # 登录
- if login():
- print("登录成功")
- # 测试创建产品
- if test_create_product():
- print("创建产品成功")
- # 测试获取日志
- test_get_logs()
- else:
- print("创建产品失败")
- else:
- print("登录失败")
\ No newline at end of file
diff --git a/test_login.py b/test_login.py
deleted file mode 100644
index 689b411..0000000
--- a/test_login.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import requests
-
-# 测试登录页面访问
-response = requests.get('http://127.0.0.1:5000/login')
-print(f"Status Code: {response.status_code}")
-print(f"Content Length: {len(response.content)}")
-print(f"Content Type: {response.headers.get('content-type')}")
-
-# 检查页面内容
-content = response.text
-if '登录' in content:
- print("页面包含登录相关文本")
-else:
- print("页面不包含登录相关文本")
-
-# 检查是否有错误信息
-if '错误' in content or 'Error' in content:
- print("页面包含错误信息")
-else:
- print("页面不包含明显错误信息")
-
-# 尝试登录
-login_data = {
- 'username': 'admin',
- 'password': 'admin123',
- 'csrf_token': '' # 我们需要从页面中提取CSRF令牌
-}
-
-print("尝试登录测试...")
\ No newline at end of file
diff --git a/test_web_log.py b/test_web_log.py
deleted file mode 100644
index 89429b9..0000000
--- a/test_web_log.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import requests
-from bs4 import BeautifulSoup
-
-# 创建会话以保持登录状态
-session = requests.Session()
-
-def get_csrf_token():
- """从登录页面获取CSRF令牌"""
- try:
- response = session.get("http://localhost:5000/login")
- soup = BeautifulSoup(response.text, 'html.parser')
- csrf_input = soup.find('input', {'name': 'csrf_token'})
- if csrf_input:
- # 直接获取value属性
- try:
- # 忽略类型检查错误
- csrf_token = csrf_input.get('value') # type: ignore
- if not csrf_token:
- csrf_token = csrf_input['value'] # type: ignore
- if csrf_token:
- return csrf_token
- except:
- pass
- print("未找到CSRF令牌输入字段")
- return None
- except Exception as e:
- print(f"获取CSRF令牌失败: {e}")
- return None
-
-def login():
- """登录系统"""
- try:
- # 获取CSRF令牌
- csrf_token = get_csrf_token()
- if not csrf_token:
- return False
-
- # 准备登录数据
- login_data = {
- 'username': 'admin',
- 'password': 'admin123',
- 'csrf_token': csrf_token
- }
-
- # 发送登录请求
- response = session.post("http://localhost:5000/login", data=login_data)
-
- # 检查是否登录成功(通过重定向到dashboard来判断)
- if response.url and 'dashboard' in response.url:
- print("登录成功")
- return True
- else:
- print(f"登录失败,状态码: {response.status_code}")
- print(f"响应URL: {response.url}")
- return False
-
- except Exception as e:
- print(f"登录过程中出现错误: {e}")
- return False
-
-def test_create_product():
- """测试创建产品"""
- try:
- # 准备产品数据
- product_data = {
- 'product_name': '测试产品',
- 'description': '这是一个测试产品'
- }
-
- # 发送创建产品请求
- response = session.post(
- "http://localhost:5000/api/v1/products",
- json=product_data,
- headers={'Content-Type': 'application/json'}
- )
-
- print(f"创建产品状态码: {response.status_code}")
- print(f"创建产品响应: {response.text}")
- return response.status_code == 200
-
- except Exception as e:
- print(f"创建产品时出现错误: {e}")
- return False
-
-def test_get_logs():
- """测试获取操作日志"""
- try:
- # 发送获取日志请求
- response = session.get("http://localhost:5000/api/v1/logs")
-
- print(f"获取日志状态码: {response.status_code}")
- print(f"获取日志响应: {response.text}")
- return response.status_code == 200
-
- except Exception as e:
- print(f"获取日志时出现错误: {e}")
- return False
-
-def test_view_logs_page():
- """测试访问日志页面"""
- try:
- # 访问日志管理页面
- response = session.get("http://localhost:5000/logs")
-
- print(f"访问日志页面状态码: {response.status_code}")
- if response.status_code == 200:
- print("成功访问日志管理页面")
- return True
- else:
- print(f"访问日志页面失败: {response.text}")
- return False
-
- except Exception as e:
- print(f"访问日志页面时出现错误: {e}")
- return False
-
-if __name__ == "__main__":
- print("开始测试日志功能...")
-
- # 登录
- if login():
- print("=== 登录成功 ===")
-
- # 测试创建产品(这会生成操作日志)
- print("\n=== 测试创建产品 ===")
- test_create_product()
-
- # 测试获取操作日志
- print("\n=== 测试获取操作日志 ===")
- test_get_logs()
-
- # 测试访问日志页面
- print("\n=== 测试访问日志页面 ===")
- test_view_logs_page()
-
- else:
- print("登录失败,无法继续测试")
\ No newline at end of file
diff --git a/verify_log.py b/verify_log.py
deleted file mode 100644
index 498d2c4..0000000
--- a/verify_log.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import requests
-import json
-
-# 直接测试日志API,绕过认证检查(在实际环境中应该有认证)
-def test_log_functionality():
- """测试日志功能"""
- try:
- # 1. 先手动创建一个产品(绕过认证检查)
- print("=== 手动创建产品以生成日志 ===")
-
- # 我们直接查看数据库中是否已有产品
- print("检查现有产品...")
-
- # 2. 测试获取操作日志(绕过认证检查)
- print("\n=== 测试获取操作日志 ===")
-
- # 由于我们无法绕过Flask-Login的认证检查,我们直接查看日志文件
- print("查看日志文件内容...")
-
- try:
- with open('logs/kamaxitong.log', 'r', encoding='utf-8') as f:
- lines = f.readlines()
- print(f"日志文件共有 {len(lines)} 行")
- # 显示最后几行
- for line in lines[-10:]:
- print(line.strip())
- except FileNotFoundError:
- print("日志文件不存在")
- except Exception as e:
- print(f"读取日志文件失败: {e}")
-
- # 3. 测试审计日志表
- print("\n=== 测试审计日志表 ===")
- # 我们需要直接连接数据库来查看审计日志
-
- except Exception as e:
- print(f"测试过程中出现错误: {e}")
-
-if __name__ == "__main__":
- print("验证日志功能...")
- test_log_functionality()
\ No newline at end of file