添加解绑功能
This commit is contained in:
parent
74405da203
commit
9f260f3f29
172
app/api/auth.py
172
app/api/auth.py
@ -9,11 +9,19 @@ from . import api_bp
|
||||
def verify_license():
|
||||
"""验证卡密接口"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
# 检查Content-Type
|
||||
content_type = request.content_type
|
||||
if content_type and 'application/json' not in content_type:
|
||||
current_app.logger.warning(f"验证请求:Content-Type不正确 - {content_type}")
|
||||
|
||||
data = request.get_json(force=True) # 强制解析JSON,即使Content-Type不正确
|
||||
if not data:
|
||||
# 尝试获取原始数据用于调试
|
||||
raw_data = request.get_data(as_text=True)
|
||||
current_app.logger.warning(f"验证请求:请求数据为空或无法解析 - Content-Type: {content_type}, 原始数据: {raw_data[:200] if raw_data else 'None'}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
'message': '请求数据为空或格式错误'
|
||||
}), 400
|
||||
|
||||
# 获取请求参数
|
||||
@ -23,27 +31,47 @@ def verify_license():
|
||||
timestamp = data.get('timestamp')
|
||||
signature = data.get('signature')
|
||||
|
||||
# 验证必填参数
|
||||
if not all([software_id, license_key, machine_code, timestamp, signature]):
|
||||
# 验证必填参数,并记录缺失的参数
|
||||
missing_params = []
|
||||
if not software_id:
|
||||
missing_params.append('software_id')
|
||||
if not license_key:
|
||||
missing_params.append('license_key')
|
||||
if not machine_code:
|
||||
missing_params.append('machine_code')
|
||||
if timestamp is None:
|
||||
missing_params.append('timestamp')
|
||||
if not signature:
|
||||
missing_params.append('signature')
|
||||
|
||||
if missing_params:
|
||||
current_app.logger.warning(f"验证请求:缺少必要参数 - {', '.join(missing_params)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数'
|
||||
'message': f'缺少必要参数: {", ".join(missing_params)}'
|
||||
}), 400
|
||||
|
||||
# 验证时间戳(防止重放攻击)
|
||||
try:
|
||||
request_time = datetime.fromtimestamp(int(timestamp))
|
||||
# 增加时间验证的宽容度到5分钟(300秒)
|
||||
time_diff = abs((datetime.utcnow() - request_time).total_seconds())
|
||||
# 确保timestamp是数字类型
|
||||
if isinstance(timestamp, str):
|
||||
timestamp = int(timestamp)
|
||||
# 使用utcfromtimestamp确保时间戳被解析为UTC时间(与time.time()生成的UTC时间戳一致)
|
||||
request_time = datetime.utcfromtimestamp(timestamp)
|
||||
# 使用UTC时间进行比较
|
||||
current_time = datetime.utcnow()
|
||||
time_diff = abs((current_time - request_time).total_seconds())
|
||||
if time_diff > 300: # 5分钟有效期
|
||||
current_app.logger.warning(f"验证请求:请求已过期 - 时间差: {time_diff}秒, 当前时间: {current_time}, 请求时间: {request_time}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求已过期'
|
||||
}), 400
|
||||
except (ValueError, TypeError):
|
||||
except (ValueError, TypeError) as e:
|
||||
current_app.logger.warning(f"验证请求:时间戳格式错误 - timestamp: {timestamp}, 错误: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '时间戳格式错误'
|
||||
'message': f'时间戳格式错误: {str(e)}'
|
||||
}), 400
|
||||
|
||||
# 验证签名
|
||||
@ -51,6 +79,9 @@ def verify_license():
|
||||
signature_data = f"{software_id}{license_key}{machine_code}{timestamp}"
|
||||
expected_signature = generate_signature(signature_data, secret_key)
|
||||
|
||||
# 记录请求信息(不记录敏感信息)
|
||||
current_app.logger.info(f"验证请求 - software_id: {software_id}, machine_code: {machine_code[:8]}..., timestamp: {timestamp}")
|
||||
|
||||
# 调试信息(仅在调试模式记录,且不输出密钥)
|
||||
if current_app.debug:
|
||||
current_app.logger.debug(f"签名数据: {signature_data}")
|
||||
@ -58,6 +89,7 @@ def verify_license():
|
||||
current_app.logger.debug(f"服务端签名匹配: {signature == expected_signature}")
|
||||
|
||||
if signature != expected_signature:
|
||||
current_app.logger.warning(f"验证请求:签名验证失败 - software_id: {software_id}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '签名验证失败'
|
||||
@ -120,7 +152,7 @@ def verify_license():
|
||||
latest_version = Version.query.filter_by(
|
||||
product_id=software_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).first()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
|
||||
response_data = {
|
||||
'license_key': license_obj.license_key,
|
||||
@ -150,11 +182,36 @@ def verify_license():
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"卡密验证失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '服务器内部错误'
|
||||
}), 500
|
||||
# 记录详细的错误信息,包括堆栈跟踪
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
current_app.logger.error(f"卡密验证失败: {str(e)}\n{error_trace}")
|
||||
|
||||
# 尝试回滚数据库事务
|
||||
try:
|
||||
db.session.rollback()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 检查是否是数据库连接错误
|
||||
error_str = str(e).lower()
|
||||
error_type = type(e).__name__
|
||||
|
||||
if 'operationalerror' in error_str or 'connection' in error_str or 'database' in error_str or 'OperationalError' in error_type:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '数据库连接失败,请稍后重试'
|
||||
}), 503
|
||||
elif 'timeout' in error_str or 'Timeout' in error_type:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求处理超时,请稍后重试'
|
||||
}), 503
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '服务器内部错误'
|
||||
}), 500
|
||||
|
||||
@api_bp.route('/auth/activate', methods=['POST'])
|
||||
def activate_license():
|
||||
@ -309,3 +366,86 @@ def heartbeat():
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"心跳处理失败: {str(e)}")
|
||||
return jsonify({'success': False}), 500
|
||||
|
||||
|
||||
@api_bp.route('/auth/unbind', methods=['POST'])
|
||||
def unbind_license():
|
||||
"""用户端解绑卡密接口"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请求数据为空'
|
||||
}), 400
|
||||
|
||||
software_id = data.get('software_id')
|
||||
license_key = data.get('license_key')
|
||||
machine_code = data.get('machine_code')
|
||||
timestamp = data.get('timestamp')
|
||||
signature = data.get('signature')
|
||||
|
||||
# 验证必填参数
|
||||
if not all([software_id, license_key, machine_code, timestamp, signature]):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '缺少必要参数'
|
||||
}), 400
|
||||
|
||||
# 验证签名
|
||||
secret_key = current_app.config.get('AUTH_SECRET_KEY', 'default-secret-key')
|
||||
signature_data = f"{software_id}{license_key}{machine_code}{timestamp}"
|
||||
expected_signature = generate_signature(signature_data, secret_key)
|
||||
|
||||
if signature != expected_signature:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '签名验证失败'
|
||||
}), 401
|
||||
|
||||
# 查找产品
|
||||
product = Product.query.filter_by(product_id=software_id).first()
|
||||
if not product:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '产品不存在'
|
||||
}), 404
|
||||
|
||||
# 查找卡密
|
||||
license_obj = License.query.filter_by(
|
||||
license_key=license_key,
|
||||
product_id=software_id
|
||||
).first()
|
||||
|
||||
if not license_obj:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '卡密不存在'
|
||||
}), 404
|
||||
|
||||
# 检查卡密是否绑定到当前机器码
|
||||
if license_obj.bind_machine_code != machine_code:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '卡密未绑定到当前设备'
|
||||
}), 400
|
||||
|
||||
# 执行解绑操作
|
||||
success, message = license_obj.unbind()
|
||||
if not success:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': message
|
||||
}), 400
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '解绑成功'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"卡密解绑失败: {str(e)}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '服务器内部错误'
|
||||
}), 500
|
||||
|
||||
@ -100,11 +100,12 @@ def get_products():
|
||||
version_stats = db.session.query(
|
||||
Version.product_id,
|
||||
Version.version_num,
|
||||
Version.update_time,
|
||||
Version.create_time
|
||||
).filter(
|
||||
Version.product_id.in_(product_ids),
|
||||
Version.publish_status == 1
|
||||
).order_by(Version.create_time.desc()).all()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).all()
|
||||
|
||||
version_dict = {}
|
||||
for v in version_stats:
|
||||
|
||||
@ -57,7 +57,7 @@ def get_user_products():
|
||||
latest_version = Version.query.filter_by(
|
||||
product_id=product.product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).first()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
|
||||
product_dict['latest_version'] = latest_version.version_num if latest_version else None
|
||||
# 移除不存在的is_paid字段
|
||||
@ -103,7 +103,7 @@ def get_user_product(product_id):
|
||||
latest_version = Version.query.filter_by(
|
||||
product_id=product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).first()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
|
||||
if latest_version:
|
||||
product_dict['latest_version'] = latest_version.version_num
|
||||
@ -112,7 +112,7 @@ def get_user_product(product_id):
|
||||
recent_versions = Version.query.filter_by(
|
||||
product_id=product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).limit(3).all()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).limit(3).all()
|
||||
|
||||
product_dict['recent_updates'] = [
|
||||
{
|
||||
@ -237,7 +237,7 @@ def user_verify_license():
|
||||
latest_version = Version.query.filter_by(
|
||||
product_id=product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).first()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
@ -564,7 +564,7 @@ def check_download_permission():
|
||||
latest_version = Version.query.filter_by(
|
||||
product_id=product_id,
|
||||
publish_status=1
|
||||
).order_by(Version.create_time.desc()).first()
|
||||
).order_by(Version.update_time.desc(), Version.create_time.desc()).first()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
|
||||
@ -50,7 +50,7 @@ class Product(db.Model):
|
||||
def get_latest_version(self):
|
||||
"""获取最新版本"""
|
||||
latest_version = self.versions.filter_by(publish_status=1).order_by(
|
||||
db.desc(Version.create_time)
|
||||
db.desc(Version.update_time), db.desc(Version.create_time)
|
||||
).first()
|
||||
return latest_version.version_num if latest_version else None
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"remember_DEMO_SOFTWARE_2024": true
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
843665BD5F1FC5FE8946763877F8E457
|
||||
@ -1 +0,0 @@
|
||||
E6684431683D6EAB7ED7FA9CE6C8BB9B
|
||||
@ -2,19 +2,26 @@
|
||||
"""
|
||||
Python软件授权验证器 (现代化UI版)
|
||||
功能:
|
||||
1. 支持在线/离线验证
|
||||
1. 在线验证(每次启动都进行服务器验证)
|
||||
2. 自动保存/读取历史卡密
|
||||
3. 现代化深色主题 UI
|
||||
4. 机器码一键复制
|
||||
5. 防止后台禁用卡密后仍能使用
|
||||
|
||||
使用方法 (完全兼容旧版):
|
||||
from auth_validator import AuthValidator
|
||||
|
||||
validator = AuthValidator(software_id="your_software_id")
|
||||
validator = AuthValidator(
|
||||
software_id="your_software_id",
|
||||
api_url="http://your-server.com/api/v1",
|
||||
secret_key="your_secret_key"
|
||||
)
|
||||
if not validator.validate():
|
||||
exit()
|
||||
sys.exit()
|
||||
"""
|
||||
|
||||
import sys # 加在文件开头,比如其他 import 语句后面
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
@ -26,6 +33,16 @@ import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Tuple, Dict, Any
|
||||
|
||||
# PyInstaller打包环境下的SSL证书处理
|
||||
# 尝试导入certifi来解决打包后SSL证书问题
|
||||
try:
|
||||
import certifi
|
||||
# 设置requests使用certifi的证书
|
||||
os.environ['REQUESTS_CA_BUNDLE'] = certifi.where()
|
||||
os.environ['SSL_CERT_FILE'] = certifi.where()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# 尝试导入现代化UI库,如果未安装则提示
|
||||
try:
|
||||
import customtkinter as ctk
|
||||
@ -33,7 +50,7 @@ try:
|
||||
from tkinter import messagebox
|
||||
except ImportError:
|
||||
print("错误: 请先安装UI库 -> pip install customtkinter")
|
||||
exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ==========================================
|
||||
@ -131,68 +148,153 @@ class AuthCore:
|
||||
self.machine_code = MachineCodeGenerator.get()
|
||||
self.token_file = f".auth_{software_id}.token"
|
||||
|
||||
def check_local_cache(self) -> bool:
|
||||
"""检查本地缓存是否有效(静默验证)"""
|
||||
if not os.path.exists(self.token_file):
|
||||
return False
|
||||
def test_connection(self) -> Tuple[bool, str]:
|
||||
"""测试服务器连接"""
|
||||
try:
|
||||
with open(self.token_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
# 尝试访问一个简单的端点(如果存在)或直接测试连接
|
||||
test_url = f"{self.api_url}/auth/verify"
|
||||
# 发送一个简单的HEAD请求测试连接(如果服务器支持)
|
||||
# 否则发送一个最小化的POST请求
|
||||
test_data = {
|
||||
"software_id": self.software_id,
|
||||
"license_key": "TEST",
|
||||
"machine_code": self.machine_code,
|
||||
"timestamp": int(time.time()),
|
||||
"signature": "test"
|
||||
}
|
||||
resp = requests.post(test_url, json=test_data, timeout=3)
|
||||
# 即使返回错误,只要不是连接错误,说明服务器可达
|
||||
return True, "服务器连接正常"
|
||||
except requests.exceptions.Timeout:
|
||||
return False, f"连接超时,服务器可能无响应: {self.api_url}"
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
error_detail = str(e)
|
||||
if "Name or service not known" in error_detail:
|
||||
return False, f"无法解析服务器地址: {self.api_url}"
|
||||
elif "Connection refused" in error_detail:
|
||||
return False, f"服务器拒绝连接,请确认服务器是否运行: {self.api_url}"
|
||||
else:
|
||||
return False, f"无法连接到服务器: {self.api_url}"
|
||||
except Exception as e:
|
||||
return False, f"连接测试失败: {str(e)}"
|
||||
|
||||
# 校验机器码
|
||||
if data.get('machine_code') != self.machine_code:
|
||||
return False
|
||||
|
||||
# 校验过期时间
|
||||
expire_str = data.get('expire_time')
|
||||
if expire_str and expire_str != "永久":
|
||||
expire_time = datetime.fromisoformat(expire_str)
|
||||
if datetime.utcnow() > expire_time:
|
||||
return False
|
||||
|
||||
# 校验本地缓存时效 (例如每7天必须联网一次)
|
||||
last_check = datetime.fromisoformat(data.get('last_check', '2000-01-01'))
|
||||
if datetime.utcnow() - last_check > timedelta(days=7):
|
||||
return False
|
||||
|
||||
return True
|
||||
def clear_token(self):
|
||||
"""清除本地Token缓存"""
|
||||
try:
|
||||
if os.path.exists(self.token_file):
|
||||
os.remove(self.token_file)
|
||||
except:
|
||||
return False
|
||||
pass
|
||||
|
||||
def verify_online(self, license_key: str) -> Tuple[bool, str, dict]:
|
||||
"""在线验证"""
|
||||
try:
|
||||
# 这里模拟网络请求,请替换为真实的 request.post
|
||||
# 真实代码示例:
|
||||
"""
|
||||
# 生成时间戳
|
||||
timestamp = int(time.time())
|
||||
sign = hashlib.sha256(f"{self.software_id}{license_key}{self.machine_code}{timestamp}{self.secret_key}".encode()).hexdigest()
|
||||
resp = requests.post(f"{self.api_url}/verify", json={...}, timeout=self.timeout)
|
||||
result = resp.json()
|
||||
"""
|
||||
|
||||
# === 模拟后端返回 (仅供测试,请根据实际API修改) ===
|
||||
import time
|
||||
time.sleep(0.8) # 模拟网络延迟
|
||||
# 生成签名数据
|
||||
signature_data = f"{self.software_id}{license_key}{self.machine_code}{timestamp}"
|
||||
|
||||
# 模拟: 只要输入不为空且不含 'FAIL' 就算成功
|
||||
if not license_key or "FAIL" in license_key.upper():
|
||||
return False, "无效的卡密或订阅已过期", {}
|
||||
# 生成签名
|
||||
combined = f"{signature_data}{self.secret_key}".encode('utf-8')
|
||||
signature = hashlib.sha256(combined).hexdigest()
|
||||
|
||||
fake_response = {
|
||||
"success": True,
|
||||
"msg": "验证成功",
|
||||
"data": {
|
||||
"expire_time": (datetime.utcnow() + timedelta(days=30)).isoformat(),
|
||||
"machine_code": self.machine_code,
|
||||
"last_check": datetime.utcnow().isoformat()
|
||||
}
|
||||
# 构建请求数据
|
||||
request_data = {
|
||||
"software_id": self.software_id,
|
||||
"license_key": license_key,
|
||||
"machine_code": self.machine_code,
|
||||
"timestamp": timestamp,
|
||||
"signature": signature,
|
||||
"software_version": "1.0.0" # 可以后续从配置中读取
|
||||
}
|
||||
return True, "验证成功", fake_response["data"]
|
||||
# ============================================
|
||||
|
||||
# 发送POST请求
|
||||
verify_url = f"{self.api_url}/auth/verify"
|
||||
|
||||
# 添加调试信息
|
||||
debug_info = f"请求URL: {verify_url}\n请求数据: {request_data}"
|
||||
|
||||
try:
|
||||
# 在PyInstaller打包环境中可能需要特殊处理SSL验证
|
||||
# 如果是打包环境,尝试禁用SSL验证(仅用于测试)
|
||||
is_frozen = getattr(sys, 'frozen', False)
|
||||
if is_frozen:
|
||||
# 打包环境,可能需要特殊处理
|
||||
resp = requests.post(verify_url, json=request_data, timeout=self.timeout, verify=False)
|
||||
else:
|
||||
resp = requests.post(verify_url, json=request_data, timeout=self.timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
return False, f"连接超时({self.timeout}秒),请检查网络连接或服务器地址: {self.api_url}", {}
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
# 提供更详细的连接错误信息
|
||||
error_detail = str(e)
|
||||
if "Name or service not known" in error_detail or "nodename nor servname provided" in error_detail:
|
||||
return False, f"无法解析服务器地址,请检查API地址是否正确: {self.api_url}", {}
|
||||
elif "Connection refused" in error_detail:
|
||||
return False, f"服务器拒绝连接,请确认服务器是否运行在: {self.api_url}", {}
|
||||
elif "No route to host" in error_detail:
|
||||
return False, f"无法到达服务器,请检查网络连接: {self.api_url}", {}
|
||||
else:
|
||||
return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态\n详细错误: {error_detail}", {}
|
||||
except Exception as e:
|
||||
return False, f"网络请求异常: {str(e)}\n{debug_info}", {}
|
||||
|
||||
# 检查HTTP状态码
|
||||
if resp.status_code != 200:
|
||||
# 处理特定的HTTP状态码
|
||||
if resp.status_code == 503:
|
||||
return False, "服务器暂时不可用,请稍后重试", {}
|
||||
elif resp.status_code == 500:
|
||||
return False, "服务器内部错误,请联系管理员", {}
|
||||
elif resp.status_code == 404:
|
||||
return False, "API接口不存在,请检查API地址", {}
|
||||
elif resp.status_code == 401:
|
||||
return False, "签名验证失败,请检查密钥配置", {}
|
||||
else:
|
||||
try:
|
||||
error_data = resp.json()
|
||||
error_msg = error_data.get('message', f'服务器返回错误: {resp.status_code}')
|
||||
except:
|
||||
error_msg = f'服务器返回错误: {resp.status_code}'
|
||||
return False, error_msg, {}
|
||||
|
||||
# 解析响应
|
||||
try:
|
||||
result = resp.json()
|
||||
except Exception as e:
|
||||
return False, f"服务器响应格式错误: {str(e)}\n响应内容: {resp.text}", {}
|
||||
|
||||
# 检查验证结果
|
||||
if not result.get('success', False):
|
||||
error_msg = result.get('message', '验证失败')
|
||||
return False, error_msg, {}
|
||||
|
||||
# 验证成功,提取数据
|
||||
data = result.get('data', {})
|
||||
|
||||
# 构建返回数据(兼容原有格式)
|
||||
response_data = {
|
||||
"expire_time": data.get('expire_time'),
|
||||
"machine_code": self.machine_code,
|
||||
"last_check": datetime.utcnow().isoformat(),
|
||||
"license_key": data.get('license_key', license_key),
|
||||
"type": data.get('type'),
|
||||
"type_name": data.get('type_name', ''),
|
||||
"remaining_days": data.get('remaining_days'),
|
||||
"product_name": data.get('product_name', '')
|
||||
}
|
||||
|
||||
return True, result.get('message', '验证成功'), response_data
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return False, "连接超时,请检查网络连接", {}
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
return False, f"无法连接到服务器: {str(e)},请检查网络连接和服务器地址", {}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return False, f"网络请求失败: {str(e)}", {}
|
||||
except Exception as e:
|
||||
return False, f"网络连接失败: {str(e)}", {}
|
||||
return False, f"验证过程出错: {str(e)}", {}
|
||||
|
||||
def save_token(self, data: dict):
|
||||
"""验证成功后保存Token"""
|
||||
@ -202,6 +304,96 @@ class AuthCore:
|
||||
except:
|
||||
pass
|
||||
|
||||
def unbind_license(self, license_key: str) -> Tuple[bool, str]:
|
||||
"""解绑卡密与机器码的绑定"""
|
||||
try:
|
||||
# 生成时间戳
|
||||
timestamp = int(time.time())
|
||||
|
||||
# 生成签名数据
|
||||
signature_data = f"{self.software_id}{license_key}{self.machine_code}{timestamp}"
|
||||
|
||||
# 生成签名
|
||||
combined = f"{signature_data}{self.secret_key}".encode('utf-8')
|
||||
signature = hashlib.sha256(combined).hexdigest()
|
||||
|
||||
# 构建请求数据
|
||||
request_data = {
|
||||
"software_id": self.software_id,
|
||||
"license_key": license_key,
|
||||
"machine_code": self.machine_code,
|
||||
"timestamp": timestamp,
|
||||
"signature": signature
|
||||
}
|
||||
|
||||
# 发送POST请求到解绑接口
|
||||
unbind_url = f"{self.api_url}/auth/unbind"
|
||||
|
||||
try:
|
||||
# 在PyInstaller打包环境中可能需要特殊处理SSL验证
|
||||
is_frozen = getattr(sys, 'frozen', False)
|
||||
if is_frozen:
|
||||
# 打包环境,可能需要特殊处理
|
||||
resp = requests.post(unbind_url, json=request_data, timeout=self.timeout, verify=False)
|
||||
else:
|
||||
resp = requests.post(unbind_url, json=request_data, timeout=self.timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
return False, f"连接超时({self.timeout}秒),请检查网络连接或服务器地址: {self.api_url}"
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
# 提供更详细的连接错误信息
|
||||
error_detail = str(e)
|
||||
if "Name or service not known" in error_detail or "nodename nor servname provided" in error_detail:
|
||||
return False, f"无法解析服务器地址,请检查API地址是否正确: {self.api_url}"
|
||||
elif "Connection refused" in error_detail:
|
||||
return False, f"服务器拒绝连接,请确认服务器是否运行在: {self.api_url}"
|
||||
elif "No route to host" in error_detail:
|
||||
return False, f"无法到达服务器,请检查网络连接: {self.api_url}"
|
||||
else:
|
||||
return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态\n详细错误: {error_detail}"
|
||||
except Exception as e:
|
||||
return False, f"网络请求异常: {str(e)}"
|
||||
|
||||
# 检查HTTP状态码
|
||||
if resp.status_code != 200:
|
||||
# 处理特定的HTTP状态码
|
||||
if resp.status_code == 503:
|
||||
return False, "服务器暂时不可用,请稍后重试"
|
||||
elif resp.status_code == 500:
|
||||
return False, "服务器内部错误,请联系管理员"
|
||||
elif resp.status_code == 404:
|
||||
return False, "API接口不存在,请检查API地址"
|
||||
elif resp.status_code == 401:
|
||||
return False, "签名验证失败,请检查密钥配置"
|
||||
else:
|
||||
try:
|
||||
error_data = resp.json()
|
||||
error_msg = error_data.get('message', f'服务器返回错误: {resp.status_code}')
|
||||
except:
|
||||
error_msg = f'服务器返回错误: {resp.status_code}'
|
||||
return False, error_msg
|
||||
|
||||
# 解析响应
|
||||
try:
|
||||
result = resp.json()
|
||||
except Exception as e:
|
||||
return False, f"服务器响应格式错误: {str(e)}\n响应内容: {resp.text}"
|
||||
|
||||
# 检查解绑结果
|
||||
if not result.get('success', False):
|
||||
error_msg = result.get('message', '解绑失败')
|
||||
return False, error_msg
|
||||
|
||||
return True, result.get('message', '解绑成功')
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return False, "连接超时,请检查网络连接"
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
return False, f"无法连接到服务器: {str(e)},请检查网络连接和服务器地址"
|
||||
except requests.exceptions.RequestException as e:
|
||||
return False, f"网络请求失败: {str(e)}"
|
||||
except Exception as e:
|
||||
return False, f"解绑过程出错: {str(e)}"
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 3. 现代化 UI 层 (View)
|
||||
@ -214,21 +406,52 @@ class AuthWindow(ctk.CTk):
|
||||
super().__init__()
|
||||
self.auth_core = auth_core
|
||||
self.is_verified = False # 验证结果状态
|
||||
self.auto_verify = False # 是否自动验证标志
|
||||
self.is_destroyed = False # 窗口是否已销毁标志
|
||||
self.pending_callbacks = [] # 待执行的after回调ID列表
|
||||
self.verifying = False # 是否正在验证中
|
||||
|
||||
# 窗口基础设置
|
||||
self.title("软件授权验证")
|
||||
self.geometry("420x550")
|
||||
self.resizable(False, False)
|
||||
self.title("软件授权验证(有问题联系V:taiyi1224)")
|
||||
self.geometry("400x550") # 增加高度以容纳新按钮
|
||||
self.minsize(300, 450) # 更新最小窗口大小
|
||||
self.resizable(True, True) # 启用窗口大小调整
|
||||
ctk.set_appearance_mode("Dark")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
# 绑定窗口关闭事件
|
||||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
|
||||
# 绑定窗口大小变化事件
|
||||
self.bind("<Configure>", self._on_window_resize)
|
||||
|
||||
# 初始化布局参数
|
||||
self.window_width = 400
|
||||
self.window_height = 550
|
||||
|
||||
# 居中显示
|
||||
self._center_window()
|
||||
self._setup_ui()
|
||||
|
||||
# 自动填入上次卡密
|
||||
# 自动填入上次卡密,如果有则自动验证
|
||||
self._load_history()
|
||||
|
||||
def _on_closing(self):
|
||||
"""窗口关闭时的处理(用户点击X关闭)"""
|
||||
# 如果用户手动关闭窗口,且验证未完成或正在验证中,则视为验证失败
|
||||
if self.verifying or not self.is_verified:
|
||||
self.is_verified = False
|
||||
self.is_destroyed = True
|
||||
self.verifying = False
|
||||
# 取消所有pending的after回调
|
||||
for callback_id in self.pending_callbacks:
|
||||
try:
|
||||
self.after_cancel(callback_id)
|
||||
except:
|
||||
pass
|
||||
self.pending_callbacks.clear()
|
||||
self.destroy()
|
||||
|
||||
def _center_window(self):
|
||||
self.update_idletasks()
|
||||
width = self.winfo_width()
|
||||
@ -237,13 +460,57 @@ class AuthWindow(ctk.CTk):
|
||||
y = (self.winfo_screenheight() // 2) - (height // 2)
|
||||
self.geometry(f'{width}x{height}+{x}+{y}')
|
||||
|
||||
def _on_window_resize(self, event):
|
||||
"""窗口大小变化事件处理"""
|
||||
# 只有当窗口确实是当前窗口且大小发生变化时才处理
|
||||
if event.widget == self and (self.window_width != event.width or self.window_height != event.height):
|
||||
self.window_width = event.width
|
||||
self.window_height = event.height
|
||||
self._update_layout()
|
||||
|
||||
def _update_layout(self):
|
||||
"""动态更新布局"""
|
||||
# 更新各组件的尺寸和位置
|
||||
padding_x = max(20, int(self.window_width * 0.05))
|
||||
|
||||
# 更新头部区域
|
||||
self.header.pack_configure(pady=(max(20, int(self.window_height * 0.04)), max(10, int(self.window_height * 0.02))))
|
||||
|
||||
# 动态调整标题字体大小
|
||||
title_font_size = max(16, min(22, int(self.window_width * 0.04)))
|
||||
self.title_label.configure(font=("Microsoft YaHei UI", title_font_size, "bold"))
|
||||
|
||||
# 更新机器码区域
|
||||
self.mc_frame.pack_configure(padx=padding_x, pady=max(5, int(self.window_height * 0.01)))
|
||||
|
||||
# 更新输入区域
|
||||
self.input_frame.pack_configure(padx=padding_x, pady=max(5, int(self.window_height * 0.01)))
|
||||
|
||||
# 更新服务器地址标签
|
||||
self.lbl_server.pack_configure(pady=(max(5, int(self.window_height * 0.01)), 0))
|
||||
|
||||
# 更新状态标签
|
||||
self.lbl_status.pack_configure(pady=(max(5, int(self.window_height * 0.01)), max(2, int(self.window_height * 0.005))))
|
||||
|
||||
# 更新验证按钮
|
||||
self.btn_verify.pack_configure(padx=padding_x, pady=max(10, int(self.window_height * 0.02)))
|
||||
|
||||
# 动态调整状态标签的换行宽度
|
||||
wrap_length = max(200, int(self.window_width * 0.8))
|
||||
self.lbl_status.configure(wraplength=wrap_length)
|
||||
|
||||
# 动态调整底部标签的位置
|
||||
self.footer_label.pack_configure(pady=max(5, int(self.window_height * 0.01)))
|
||||
|
||||
def _setup_ui(self):
|
||||
# 1. 头部图标与标题
|
||||
self.header = ctk.CTkFrame(self, fg_color="transparent")
|
||||
self.header.pack(pady=(40, 20))
|
||||
|
||||
ctk.CTkLabel(self.header, text="🔐", font=("Segoe UI Emoji", 56)).pack()
|
||||
ctk.CTkLabel(self.header, text="用户授权系统", font=("Microsoft YaHei UI", 22, "bold")).pack(pady=5)
|
||||
self.icon_label = ctk.CTkLabel(self.header, text="🔐", font=("Segoe UI Emoji", 56))
|
||||
self.icon_label.pack()
|
||||
self.title_label = ctk.CTkLabel(self.header, text="用户授权系统", font=("Microsoft YaHei UI", 22, "bold"))
|
||||
self.title_label.pack(pady=5)
|
||||
|
||||
# 2. 机器码显示区
|
||||
self.mc_frame = ctk.CTkFrame(self, fg_color="#2B2B2B", corner_radius=8)
|
||||
@ -279,36 +546,90 @@ class AuthWindow(ctk.CTk):
|
||||
)
|
||||
self.entry_key.pack(fill="x")
|
||||
|
||||
# 4. 状态提示信息
|
||||
self.lbl_status = ctk.CTkLabel(self, text="等待验证...", text_color="gray", font=("Microsoft YaHei UI", 12))
|
||||
self.lbl_status.pack(pady=(20, 5))
|
||||
# 4. 操作按钮区
|
||||
self.button_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
self.button_frame.pack(padx=30, pady=10, fill="x")
|
||||
|
||||
# 5. 验证按钮
|
||||
# 验证按钮
|
||||
self.btn_verify = ctk.CTkButton(
|
||||
self,
|
||||
self.button_frame,
|
||||
text="立即验证授权",
|
||||
height=50,
|
||||
font=("Microsoft YaHei UI", 16, "bold"),
|
||||
height=40,
|
||||
font=("Microsoft YaHei UI", 14, "bold"),
|
||||
command=self._handle_verify
|
||||
)
|
||||
self.btn_verify.pack(padx=30, pady=20, fill="x")
|
||||
self.btn_verify.pack(fill="x", pady=(0, 10))
|
||||
|
||||
# 解绑按钮
|
||||
self.btn_unbind = ctk.CTkButton(
|
||||
self.button_frame,
|
||||
text="解绑当前卡密",
|
||||
height=40,
|
||||
font=("Microsoft YaHei UI", 14),
|
||||
fg_color="transparent",
|
||||
border_width=2,
|
||||
command=self._handle_unbind
|
||||
)
|
||||
self.btn_unbind.pack(fill="x")
|
||||
|
||||
# 5. 服务器地址显示(小字,灰色)
|
||||
self.lbl_server = ctk.CTkLabel(
|
||||
self,
|
||||
text=f"服务器: {self.auth_core.api_url}",
|
||||
text_color="#666",
|
||||
font=("Microsoft YaHei UI", 9)
|
||||
)
|
||||
self.lbl_server.pack(pady=(10, 0))
|
||||
|
||||
# 6. 状态提示信息(支持多行)
|
||||
self.lbl_status = ctk.CTkLabel(
|
||||
self,
|
||||
text="等待验证...",
|
||||
text_color="gray",
|
||||
font=("Microsoft YaHei UI", 12),
|
||||
wraplength=360, # 允许自动换行
|
||||
justify="left"
|
||||
)
|
||||
self.lbl_status.pack(pady=(10, 5))
|
||||
|
||||
# 底部版权
|
||||
ctk.CTkLabel(self, text="Powered by AuthValidator", font=("Arial", 10), text_color="#444").pack(side="bottom",
|
||||
pady=10)
|
||||
self.footer_label = ctk.CTkLabel(self, text="Powered by AuthValidator", font=("Arial", 10), text_color="#444")
|
||||
self.footer_label.pack(side="bottom", pady=10)
|
||||
|
||||
def _load_history(self):
|
||||
"""读取历史卡密"""
|
||||
"""读取历史卡密,如果有则自动验证"""
|
||||
last_key = ConfigManager.get_last_key()
|
||||
if last_key:
|
||||
self.entry_key.insert(0, last_key)
|
||||
self.lbl_status.configure(text="已自动填入上次卡密,请点击验证", text_color="#888")
|
||||
self.lbl_status.configure(text="已自动填入上次卡密,正在验证中...", text_color="#2196F3")
|
||||
# 延迟100ms后自动触发验证,确保UI已完全加载
|
||||
callback_id = self.after(100, self._safe_auto_verify)
|
||||
self.pending_callbacks.append(callback_id)
|
||||
else:
|
||||
self.lbl_status.configure(text="请输入卡密并点击验证", text_color="gray")
|
||||
|
||||
def _safe_auto_verify(self):
|
||||
"""安全地自动验证保存的卡密"""
|
||||
if self.is_destroyed:
|
||||
return
|
||||
key = self.entry_key.get().strip()
|
||||
if key:
|
||||
self.auto_verify = True
|
||||
self._handle_verify()
|
||||
|
||||
def _copy_machine_code(self):
|
||||
if self.is_destroyed:
|
||||
return
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self.auth_core.machine_code)
|
||||
self.lbl_status.configure(text="✅ 机器码已复制到剪贴板", text_color="#4CAF50")
|
||||
self.after(2000, lambda: self.lbl_status.configure(text="等待验证...", text_color="gray"))
|
||||
callback_id = self.after(2000, self._safe_reset_status)
|
||||
self.pending_callbacks.append(callback_id)
|
||||
|
||||
def _safe_reset_status(self):
|
||||
"""安全地重置状态提示"""
|
||||
if not self.is_destroyed:
|
||||
self.lbl_status.configure(text="等待验证...", text_color="gray")
|
||||
|
||||
def _handle_verify(self):
|
||||
key = self.entry_key.get().strip()
|
||||
@ -316,6 +637,14 @@ class AuthWindow(ctk.CTk):
|
||||
self.lbl_status.configure(text="❌ 卡密不能为空", text_color="#F44336")
|
||||
return
|
||||
|
||||
# 如果正在验证中,忽略重复请求
|
||||
if self.verifying:
|
||||
return
|
||||
|
||||
# 标记为正在验证
|
||||
self.verifying = True
|
||||
self.is_verified = False # 重置验证状态
|
||||
|
||||
# 锁定UI
|
||||
self.btn_verify.configure(state="disabled", text="正在连接服务器...")
|
||||
self.entry_key.configure(state="disabled")
|
||||
@ -327,27 +656,139 @@ class AuthWindow(ctk.CTk):
|
||||
def _verify_thread(self, key):
|
||||
"""后台验证逻辑"""
|
||||
success, msg, data = self.auth_core.verify_online(key)
|
||||
# 回到主线程更新UI
|
||||
self.after(0, lambda: self._on_verify_result(success, msg, key, data))
|
||||
# 回到主线程更新UI,使用安全的方式
|
||||
if not self.is_destroyed:
|
||||
callback_id = self.after(0, lambda: self._on_verify_result(success, msg, key, data))
|
||||
self.pending_callbacks.append(callback_id)
|
||||
|
||||
def _on_verify_result(self, success, msg, key, data):
|
||||
# 如果窗口已销毁,直接返回
|
||||
if self.is_destroyed:
|
||||
return
|
||||
|
||||
# 标记验证完成
|
||||
self.verifying = False
|
||||
|
||||
self.btn_verify.configure(state="normal", text="立即验证授权")
|
||||
self.entry_key.configure(state="normal")
|
||||
|
||||
if success:
|
||||
# 验证成功
|
||||
self.lbl_status.configure(text=f"✅ {msg}", text_color="#4CAF50")
|
||||
# 验证成功 - 必须先设置 is_verified,再关闭窗口
|
||||
self.is_verified = True
|
||||
self.lbl_status.configure(text=f"✅ {msg}", text_color="#4CAF50")
|
||||
|
||||
# 保存卡密和Token
|
||||
ConfigManager.save_last_key(key)
|
||||
self.auth_core.save_token(data)
|
||||
|
||||
# 延迟关闭窗口
|
||||
self.after(1000, self.destroy)
|
||||
# 如果是自动验证,延迟关闭窗口;如果是手动验证,给用户1秒查看结果后关闭
|
||||
delay = 800 if self.auto_verify else 1000
|
||||
callback_id = self.after(delay, self._safe_close_window)
|
||||
self.pending_callbacks.append(callback_id)
|
||||
else:
|
||||
# 验证失败
|
||||
self.lbl_status.configure(text=f"❌ {msg}", text_color="#F44336")
|
||||
# 验证失败,确保 is_verified 为 False
|
||||
self.is_verified = False
|
||||
# 清除本地缓存
|
||||
self.auth_core.clear_token()
|
||||
|
||||
# 格式化错误消息,如果是连接错误,显示API地址
|
||||
error_msg = msg
|
||||
if "无法连接" in msg or "连接超时" in msg or "服务器" in msg:
|
||||
# 在错误消息中已经包含了API地址,直接显示
|
||||
pass
|
||||
elif len(msg) > 80:
|
||||
# 如果错误消息太长,截断并添加提示
|
||||
error_msg = msg[:80] + "..."
|
||||
|
||||
# 如果是自动验证失败,允许用户修改卡密后重新验证
|
||||
if self.auto_verify:
|
||||
self.lbl_status.configure(text=f"❌ {error_msg}\n请检查网络连接或重新输入卡密", text_color="#F44336")
|
||||
self.auto_verify = False # 重置标志,允许手动验证
|
||||
else:
|
||||
self.lbl_status.configure(text=f"❌ {error_msg}", text_color="#F44336")
|
||||
|
||||
def _safe_close_window(self):
|
||||
"""安全地关闭窗口"""
|
||||
if not self.is_destroyed:
|
||||
self.is_destroyed = True
|
||||
# 取消所有pending的after回调
|
||||
for callback_id in self.pending_callbacks:
|
||||
try:
|
||||
self.after_cancel(callback_id)
|
||||
except:
|
||||
pass
|
||||
self.pending_callbacks.clear()
|
||||
self.destroy()
|
||||
|
||||
def _handle_unbind(self):
|
||||
"""处理解绑操作"""
|
||||
key = self.entry_key.get().strip()
|
||||
if not key:
|
||||
self.lbl_status.configure(text="❌ 卡密不能为空", text_color="#F44336")
|
||||
return
|
||||
|
||||
# 如果正在验证中,忽略重复请求
|
||||
if self.verifying:
|
||||
return
|
||||
|
||||
# 标记为正在验证
|
||||
self.verifying = True
|
||||
self.is_verified = False # 重置验证状态
|
||||
|
||||
# 锁定UI
|
||||
self.btn_verify.configure(state="disabled", text="正在连接服务器...")
|
||||
self.btn_unbind.configure(state="disabled")
|
||||
self.entry_key.configure(state="disabled")
|
||||
self.lbl_status.configure(text="⏳ 正在解绑中,请稍候...", text_color="#2196F3")
|
||||
|
||||
# 开启线程进行解绑
|
||||
threading.Thread(target=self._unbind_thread, args=(key,), daemon=True).start()
|
||||
|
||||
def _unbind_thread(self, key):
|
||||
"""后台解绑逻辑"""
|
||||
success, msg = self.auth_core.unbind_license(key)
|
||||
# 回到主线程更新UI,使用安全的方式
|
||||
if not self.is_destroyed:
|
||||
callback_id = self.after(0, lambda: self._on_unbind_result(success, msg))
|
||||
self.pending_callbacks.append(callback_id)
|
||||
|
||||
def _on_unbind_result(self, success, msg):
|
||||
# 如果窗口已销毁,直接返回
|
||||
if self.is_destroyed:
|
||||
return
|
||||
|
||||
# 标记验证完成
|
||||
self.verifying = False
|
||||
|
||||
self.btn_verify.configure(state="normal", text="立即验证授权")
|
||||
self.btn_unbind.configure(state="normal")
|
||||
self.entry_key.configure(state="normal")
|
||||
|
||||
if success:
|
||||
# 解绑成功 - 必须先设置 is_verified 为 False,再关闭窗口
|
||||
self.is_verified = False
|
||||
self.lbl_status.configure(text=f"✅ {msg}", text_color="#4CAF50")
|
||||
|
||||
# 清除本地缓存
|
||||
self.auth_core.clear_token()
|
||||
|
||||
# 延迟关闭窗口
|
||||
callback_id = self.after(800, self._safe_close_window)
|
||||
self.pending_callbacks.append(callback_id)
|
||||
else:
|
||||
# 解绑失败,确保 is_verified 为 False
|
||||
self.is_verified = False
|
||||
|
||||
# 格式化错误消息,如果是连接错误,显示API地址
|
||||
error_msg = msg
|
||||
if "无法连接" in msg or "连接超时" in msg or "服务器" in msg:
|
||||
# 在错误消息中已经包含了API地址,直接显示
|
||||
pass
|
||||
elif len(msg) > 80:
|
||||
# 如果错误消息太长,截断并添加提示
|
||||
error_msg = msg[:80] + "..."
|
||||
|
||||
self.lbl_status.configure(text=f"❌ {error_msg}", text_color="#F44336")
|
||||
|
||||
|
||||
# ==========================================
|
||||
@ -377,22 +818,20 @@ class AuthValidator:
|
||||
def validate(self) -> bool:
|
||||
"""
|
||||
执行验证流程 (阻塞式)
|
||||
1. 优先尝试静默验证(本地缓存)
|
||||
2. 失败则弹出现代化UI窗口
|
||||
每次打开都必须进行在线验证,防止后台禁用卡密后用户仍能使用
|
||||
1. 读取保存的卡密(如果有)
|
||||
2. 弹出现代化UI窗口并自动验证
|
||||
3. 验证失败则要求用户重新输入
|
||||
Returns:
|
||||
bool: 是否验证成功
|
||||
"""
|
||||
# 1. 尝试静默验证 (本地Token有效)
|
||||
if self.core.check_local_cache():
|
||||
return True
|
||||
|
||||
# 2. 启动 UI 窗口
|
||||
# 启动 UI 窗口(会自动读取保存的卡密并验证)
|
||||
app = AuthWindow(self.core)
|
||||
|
||||
# 运行主循环 (这会阻塞代码执行,直到窗口关闭)
|
||||
app.mainloop()
|
||||
|
||||
# 3. 窗口关闭后,检查是否验证成功
|
||||
# 窗口关闭后,检查是否验证成功
|
||||
return app.is_verified
|
||||
|
||||
# --- END OF FILE ---
|
||||
@ -127,21 +127,42 @@ function exportLicenses() {
|
||||
// 显示加载状态
|
||||
submitBtn.disabled = true;
|
||||
submitText.textContent = '导出中...';
|
||||
showLoading();
|
||||
|
||||
// 使用apiRequest函数处理API请求,确保域名配置正确
|
||||
const apiUrl = '/api/v1/licenses/export';
|
||||
// 构建API URL(复用base.html中的URL构建逻辑)
|
||||
let apiUrl = '/api/v1/licenses/export';
|
||||
const frontendDomain = window.FRONTEND_DOMAIN || '';
|
||||
|
||||
apiRequest(apiUrl, {
|
||||
if (apiUrl.startsWith('/')) {
|
||||
if (frontendDomain && !apiUrl.startsWith(frontendDomain)) {
|
||||
let cleanDomain = frontendDomain;
|
||||
try {
|
||||
const urlObj = new URL(frontendDomain.startsWith('http') ? frontendDomain : 'http://' + frontendDomain);
|
||||
cleanDomain = urlObj.origin;
|
||||
} catch (e) {
|
||||
if (frontendDomain.includes('/')) {
|
||||
cleanDomain = frontendDomain.split('/')[0];
|
||||
}
|
||||
}
|
||||
apiUrl = cleanDomain + apiUrl;
|
||||
} else if (!frontendDomain) {
|
||||
apiUrl = window.location.origin + apiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// 直接使用fetch处理文件下载,不使用apiRequest(因为apiRequest会尝试解析JSON)
|
||||
fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
// 处理错误响应
|
||||
if (response.status === 401) {
|
||||
@ -154,21 +175,36 @@ function exportLicenses() {
|
||||
return response.json().then(errorData => {
|
||||
showNotification(errorData.message || '权限不足,无法执行此操作', 'error');
|
||||
throw new Error(`403: ${errorData.message || '权限不足'}`);
|
||||
}).catch(() => {
|
||||
showNotification('权限不足,无法执行此操作', 'error');
|
||||
throw new Error('403: 权限不足');
|
||||
});
|
||||
} else {
|
||||
// 尝试解析错误信息
|
||||
return response.json().then(errorData => {
|
||||
showNotification(errorData.message || `导出失败: ${response.statusText}`, 'error');
|
||||
throw new Error(`${response.status}: ${errorData.message || response.statusText}`);
|
||||
}).catch(() => {
|
||||
showNotification(`导出失败: ${response.statusText}`, 'error');
|
||||
throw new Error(`${response.status}: ${response.statusText}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 成功响应,处理文件下载
|
||||
// 获取文件名
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
let filename = 'licenses.xlsx';
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
|
||||
if (filenameMatch && filenameMatch.length === 2) {
|
||||
filename = filenameMatch[1];
|
||||
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||
if (filenameMatch && filenameMatch[1]) {
|
||||
filename = filenameMatch[1].replace(/['"]/g, '');
|
||||
// 处理URL编码的文件名
|
||||
try {
|
||||
filename = decodeURIComponent(filename);
|
||||
} catch (e) {
|
||||
// 如果解码失败,使用原始文件名
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,10 +223,14 @@ function exportLicenses() {
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
console.error('Failed to export licenses:', error);
|
||||
showNotification(error.message || '导出失败', 'error');
|
||||
// 如果错误消息不是我们自定义的,显示通用错误消息
|
||||
if (error.message && !error.message.includes(':')) {
|
||||
showNotification('导出失败: ' + error.message, 'error');
|
||||
} else if (!error.message.includes('未授权') && !error.message.includes('权限不足')) {
|
||||
showNotification('导出失败,请稍后重试', 'error');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 恢复按钮状态
|
||||
|
||||
@ -1,512 +1,30 @@
|
||||
2025-11-22 13:55:38,804 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 13:55:38,805 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:38,805 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,522 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,522 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,522 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 13:55:39,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 13:55:39,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 13:55:39,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 13:55:39,717 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,717 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,717 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,717 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,717 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 13:55:39,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:01:08,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:01:08,269 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:01:08,269 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:01:08,772 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:01:08,772 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:01:08,772 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:16:26,892 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:16:28,910 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:16:35,620 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:16:38,916 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:17:58,265 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,569 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,569 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,569 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,672 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,672 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,672 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,678 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:18:34,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:19:05,591 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,526 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,526 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,526 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,713 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,713 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,713 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,718 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:09,768 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:28,147 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:44,899 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:44,900 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:44,900 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,004 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,816 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:45,816 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:45,816 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:45,816 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:20:45,817 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,817 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,817 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,817 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,817 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:124]
|
||||
2025-11-22 14:20:45,863 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,863 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,863 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,863 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:45,863 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:97]
|
||||
2025-11-22 14:20:48,483 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:22:06,425 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:14,494 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:17,463 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:21,236 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:27,394 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:42,403 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:45,366 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:48,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:54,112 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:33:57,389 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:00,308 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:05,899 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:09,907 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:12,749 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:17,656 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:34:21,503 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:35:11,341 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:36:11,672 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:39:01,872 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:49:36,143 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:51:13,517 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:52:10,815 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:90]
|
||||
2025-11-22 14:52:41,540 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:53:40,921 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:53:53,684 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:54:34,239 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:55:10,790 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:57:43,127 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 14:58:08,288 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:02:04,360 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:04:04,791 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:05:01,631 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:14:18,452 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:14:27,680 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:14:32,172 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:32:08,417 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:32:10,523 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:37:03,632 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:37:04,201 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:39:03,782 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:51:13,296 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:52:56,460 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 15:53:03,081 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:05:58,969 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:00,353 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:02,601 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:05,618 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:37,150 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:39,315 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:49,441 ERROR: 获取卡密列表失败: (pymysql.err.OperationalError) (1054, "Unknown column 'device.ip_address' in 'field list'")
|
||||
[SQL: SELECT device.device_id AS device_device_id, device.machine_code AS device_machine_code, device.license_id AS device_license_id, device.product_id AS device_product_id, device.software_version AS device_software_version, device.ip_address AS device_ip_address, device.status AS device_status, device.activate_time AS device_activate_time, device.last_verify_time AS device_last_verify_time, device.create_time AS device_create_time, device.update_time AS device_update_time
|
||||
FROM device
|
||||
WHERE %(param_1)s = device.license_id
|
||||
LIMIT %(param_2)s]
|
||||
[parameters: {'param_1': 2, 'param_2': 1}]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8) [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:86]
|
||||
2025-11-22 17:19:49,446 ERROR: 获取工单列表失败: (pymysql.err.OperationalError) (1054, "Unknown column 'product.image_path' in 'field list'")
|
||||
[SQL: SELECT product.product_id AS product_product_id, product.product_name AS product_product_name, product.description AS product_description, product.image_path AS product_image_path, product.status AS product_status, product.create_time AS product_create_time, product.update_time AS product_update_time
|
||||
FROM product
|
||||
WHERE product.product_id = %(pk_1)s]
|
||||
[parameters: {'pk_1': 'KMX002'}]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8) [in D:\work\code\python\KaMiXiTong\master\app\api\ticket.py:51]
|
||||
2025-11-22 17:19:51,613 ERROR: 获取产品列表失败: (pymysql.err.OperationalError) (1054, "Unknown column 'product.image_path' in 'field list'")
|
||||
[SQL: SELECT product.product_id AS product_product_id, product.product_name AS product_product_name, product.description AS product_description, product.image_path AS product_image_path, product.status AS product_status, product.create_time AS product_create_time, product.update_time AS product_update_time
|
||||
FROM product ORDER BY product.create_time DESC
|
||||
LIMIT %(param_1)s, %(param_2)s]
|
||||
[parameters: {'param_1': 0, 'param_2': 10}]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8) [in D:\work\code\python\KaMiXiTong\master\app\api\product.py:114]
|
||||
2025-11-22 17:19:51,614 ERROR: 错误类型: <class 'sqlalchemy.exc.OperationalError'> [in D:\work\code\python\KaMiXiTong\master\app\api\product.py:115]
|
||||
2025-11-22 17:19:51,640 ERROR: 错误堆栈: Traceback (most recent call last):
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context
|
||||
self.dialect.do_execute(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\default.py", line 951, in do_execute
|
||||
cursor.execute(statement, parameters)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\cursors.py", line 153, in execute
|
||||
result = self._query(query)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\cursors.py", line 322, in _query
|
||||
conn.query(q)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 558, in query
|
||||
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 822, in _read_query_result
|
||||
result.read()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 1200, in read
|
||||
first_packet = self.connection._read_packet()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 772, in _read_packet
|
||||
packet.raise_for_error()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\protocol.py", line 221, in raise_for_error
|
||||
err.raise_mysql_exception(self._data)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\err.py", line 143, in raise_mysql_exception
|
||||
raise errorclass(errno, errval)
|
||||
pymysql.err.OperationalError: (1054, "Unknown column 'product.image_path' in 'field list'")
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "D:\work\code\python\KaMiXiTong\master\app\api\product.py", line 35, in get_products
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\flask_sqlalchemy\query.py", line 98, in paginate
|
||||
return QueryPagination(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\flask_sqlalchemy\pagination.py", line 72, in __init__
|
||||
items = self._query_items()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\flask_sqlalchemy\pagination.py", line 358, in _query_items
|
||||
out = query.limit(self.per_page).offset(self._query_offset).all()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\orm\query.py", line 2704, in all
|
||||
return self._iter().all() # type: ignore
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\orm\query.py", line 2857, in _iter
|
||||
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\orm\session.py", line 2365, in execute
|
||||
return self._execute_internal(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\orm\session.py", line 2251, in _execute_internal
|
||||
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\orm\context.py", line 306, in orm_execute_statement
|
||||
result = conn.execute(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute
|
||||
return meth(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\sql\elements.py", line 526, in _execute_on_connection
|
||||
return connection._execute_clauseelement(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement
|
||||
ret = self._execute_context(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context
|
||||
return self._exec_single_context(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context
|
||||
self._handle_dbapi_exception(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 2355, in _handle_dbapi_exception
|
||||
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context
|
||||
self.dialect.do_execute(
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\sqlalchemy\engine\default.py", line 951, in do_execute
|
||||
cursor.execute(statement, parameters)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\cursors.py", line 153, in execute
|
||||
result = self._query(query)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\cursors.py", line 322, in _query
|
||||
conn.query(q)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 558, in query
|
||||
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 822, in _read_query_result
|
||||
result.read()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 1200, in read
|
||||
first_packet = self.connection._read_packet()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\connections.py", line 772, in _read_packet
|
||||
packet.raise_for_error()
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\protocol.py", line 221, in raise_for_error
|
||||
err.raise_mysql_exception(self._data)
|
||||
File "D:\IDE\Language\Python3.10\lib\site-packages\pymysql\err.py", line 143, in raise_mysql_exception
|
||||
raise errorclass(errno, errval)
|
||||
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1054, "Unknown column 'product.image_path' in 'field list'")
|
||||
[SQL: SELECT product.product_id AS product_product_id, product.product_name AS product_product_name, product.description AS product_description, product.image_path AS product_image_path, product.status AS product_status, product.create_time AS product_create_time, product.update_time AS product_update_time
|
||||
FROM product ORDER BY product.create_time DESC
|
||||
LIMIT %(param_1)s, %(param_2)s]
|
||||
[parameters: {'param_1': 0, 'param_2': 10}]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8)
|
||||
[in D:\work\code\python\KaMiXiTong\master\app\api\product.py:116]
|
||||
2025-11-22 17:19:53,334 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:19:54,293 ERROR: 获取版本列表失败: (pymysql.err.OperationalError) (1054, "Unknown column 'product.image_path' in 'field list'")
|
||||
[SQL: SELECT product.product_id AS product_product_id, product.product_name AS product_product_name, product.description AS product_description, product.image_path AS product_image_path, product.status AS product_status, product.create_time AS product_create_time, product.update_time AS product_update_time
|
||||
FROM product
|
||||
WHERE product.product_id = %(pk_1)s]
|
||||
[parameters: {'pk_1': 'KMX001'}]
|
||||
(Background on this error at: https://sqlalche.me/e/20/e3q8) [in D:\work\code\python\KaMiXiTong\master\app\api\version.py:95]
|
||||
2025-11-22 17:49:42,406 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:93]
|
||||
2025-11-22 17:54:30,685 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 17:54:30,685 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 17:54:30,685 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 17:54:30,761 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:102]
|
||||
2025-11-22 17:54:30,761 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:102]
|
||||
2025-11-22 18:07:30,522 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:07:52,962 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:08:18,771 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:08:52,526 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:09:23,276 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:11:24,010 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:11:26,101 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:12:16,035 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:14:23,758 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:15:27,120 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:17:18,450 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:17:36,021 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:17:55,799 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:20:25,143 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:20:30,476 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:20:42,085 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:25:16,676 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:25:50,791 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:25:59,449 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:25:59,450 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 18:25:59,450 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 18:25:59,530 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 18:25:59,530 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 18:26:01,777 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:33:53,761 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:34:38,040 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:34:44,403 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:34:55,336 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:38:45,350 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:38:58,823 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:39:14,999 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:39:22,581 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:39:26,766 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:39:36,244 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:39:50,033 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:40:08,697 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:41:26,671 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:41:57,721 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:42:02,929 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:42:09,889 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:42:37,991 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:49:36,795 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:50:27,639 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:53:10,619 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 18:56:28,784 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:01:21,164 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:05:32,038 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:08:32,055 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:09:24,505 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:09:26,624 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:29,947 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:31,966 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:43,157 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:43,157 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:13:43,157 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:13:43,231 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:13:43,231 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:13:45,063 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:45,608 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:13:45,626 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:32,074 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:32,107 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:48,947 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:50,939 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:56,703 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:14:56,703 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:14:56,703 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:14:56,781 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:14:56,781 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:15:22,725 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:15:24,710 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:15:30,503 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:15:30,504 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:15:30,504 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:15:30,590 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:15:30,590 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:16:51,599 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:16:53,602 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:16:59,280 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:16:59,280 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:16:59,280 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:16:59,358 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:16:59,358 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:17:28,106 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:17:30,084 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:17:30,106 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:17:57,729 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:17:57,741 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:18:02,013 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:18:02,041 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:18:06,657 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:18:38,212 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:19:14,854 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:19:37,065 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:19:37,093 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:19:41,235 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:09,482 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:09,504 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:28,322 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:28,528 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:55,112 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:55,550 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:56,165 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:20:59,715 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:01,348 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:06,074 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:28,533 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:28,588 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:33,756 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:33,801 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:21:36,916 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:22:13,034 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:22:13,198 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:22:22,864 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:22:23,782 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:22:57,380 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:23:39,526 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:23:40,026 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:23:43,677 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:25:01,614 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:25:01,636 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:25:03,898 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:25:20,641 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:25:20,646 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:26:03,235 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:26:03,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:27:27,259 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:27:29,281 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:28:42,916 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:28:44,890 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:29:00,912 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:29:00,913 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:29:00,913 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:29:01,013 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:29:01,013 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:29:13,360 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:29:36,518 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:01,137 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:07,077 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:37,634 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:37,635 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:30:37,635 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:30:37,799 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:30:37,799 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:30:47,683 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:47,683 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:30:47,683 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:135]
|
||||
2025-11-22 19:30:47,771 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:30:47,771 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\app\__init__.py:112]
|
||||
2025-11-22 19:30:49,172 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:50,516 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:30:50,616 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:31:24,007 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:31:41,206 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:31:43,668 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:31:43,684 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:03,384 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:09,346 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:12,820 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:12,844 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:18,646 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:20,713 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:32:20,778 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:34:03,452 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:34:03,808 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:34:58,912 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:34:58,920 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:35:09,603 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:35:10,698 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:35:11,877 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:35:22,244 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:36:09,190 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:36:11,209 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:36:21,061 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:37:19,952 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:37:22,358 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:08,328 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:08,969 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:27,331 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:28,654 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:53,412 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:38:54,347 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:39:34,896 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:39:36,570 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:39:42,704 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:04,082 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:04,242 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:35,434 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:36,068 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:47,459 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:40:48,891 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:41:08,666 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:41:11,486 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:41:34,821 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:41:36,574 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:41:42,050 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:42:04,086 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:42:05,550 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:42:29,413 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:42:31,687 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:46:28,599 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:101]
|
||||
2025-11-22 19:46:39,323 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:46:51,491 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:47:05,130 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:48:24,813 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:48:31,647 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:50:37,904 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:52:10,038 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:52:32,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:53:00,764 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:53:20,558 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:53:32,123 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:53:47,730 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:54:07,270 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:56:31,516 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:56:59,758 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:57:40,236 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:57:42,331 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:57:46,314 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:58:00,490 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 19:59:41,869 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:00:12,514 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:00:44,156 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:01:08,184 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:02:08,830 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:02:32,602 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:02:36,742 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:03:14,238 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:05:40,769 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:06:21,991 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:06:32,752 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:06:53,895 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:07:05,128 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:07:14,246 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:08:41,509 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:10:26,059 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:10:50,519 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:12:32,425 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:12:37,515 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:12:41,438 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:00,258 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:11,317 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:23,392 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:41,554 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:48,223 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:52,651 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:14:58,750 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:18:15,310 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:18:25,997 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-22 20:18:42,041 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:105]
|
||||
2025-11-25 22:17:52,630 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:54,791 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:54,791 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:54,970 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:54,970 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:54,970 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,118 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,118 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,118 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,118 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,267 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,416 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:17:55,587 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
2025-11-25 22:20:26,716 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\.\config.py:110]
|
||||
2025-11-25 22:20:40,356 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:110]
|
||||
|
||||
@ -1,99 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""测试 Werkzeug base64 编码方式"""
|
||||
from app.utils.auth_validator import AuthValidator
|
||||
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
# 测试旧格式的密码哈希(数据库中的格式)
|
||||
old_hash = "$pbkdf2-sha256$29000$N2aBd1I5Eaz5bYY2CXbu2A$1lEXwDoX9S5slrv0cFHsQ8fAj55m43.1mPbX5f.Ra0U"
|
||||
password = "admin123"
|
||||
|
||||
print(f"旧格式哈希: {old_hash}")
|
||||
print(f"密码: {password}")
|
||||
|
||||
# 尝试使用 Werkzeug 验证
|
||||
try:
|
||||
result = check_password_hash(old_hash, password)
|
||||
print(f"Werkzeug 验证结果: {result}")
|
||||
except Exception as e:
|
||||
print(f"Werkzeug 验证失败: {e}")
|
||||
|
||||
# 手动解析旧格式
|
||||
if old_hash.startswith('$pbkdf2-sha256$'):
|
||||
parts = old_hash.split('$')
|
||||
print(f"\n部分数量: {len(parts)}")
|
||||
if len(parts) == 5:
|
||||
iterations = int(parts[2])
|
||||
salt_str = parts[3]
|
||||
hash_str = parts[4]
|
||||
|
||||
print(f"\n迭代次数: {iterations}")
|
||||
print(f"Salt 字符串: {salt_str}")
|
||||
print(f"Salt 长度: {len(salt_str)}")
|
||||
print(f"Hash 字符串: {hash_str}")
|
||||
print(f"Hash 长度: {len(hash_str)}")
|
||||
|
||||
# 尝试 base64 解码
|
||||
try:
|
||||
# 添加填充
|
||||
def add_padding(s):
|
||||
missing = len(s) % 4
|
||||
return s + '=' * (4 - missing) if missing else s
|
||||
|
||||
# 处理点号 - 可能是 URL-safe base64 的变体
|
||||
# 点号在 base64 中不存在,可能是其他字符的编码
|
||||
# 尝试将点号替换为可能的 base64 字符
|
||||
def fix_base64(s):
|
||||
# 尝试不同的替换方式
|
||||
# 点号可能是 + 或 / 的编码错误
|
||||
s1 = s.replace('.', '+')
|
||||
s2 = s.replace('.', '/')
|
||||
s3 = s.replace('.', '=')
|
||||
return [s, s1, s2, s3]
|
||||
|
||||
salt_padded = add_padding(salt_str)
|
||||
hash_variants = [add_padding(h) for h in fix_base64(hash_str)]
|
||||
|
||||
print(f"\nSalt 填充后: {salt_padded}")
|
||||
print(f"Hash 变体数量: {len(hash_variants)}")
|
||||
|
||||
salt_bytes = base64.b64decode(salt_padded, validate=False)
|
||||
|
||||
# 尝试所有变体
|
||||
for i, hash_padded in enumerate(hash_variants):
|
||||
try:
|
||||
print(f"\n尝试 Hash 变体 {i}: {hash_padded[:50]}...")
|
||||
hash_bytes = base64.b64decode(hash_padded, validate=False)
|
||||
print(f"变体 {i} 解码成功,长度: {len(hash_bytes)}")
|
||||
|
||||
# 计算 PBKDF2
|
||||
password_bytes = password.encode('utf-8')
|
||||
computed = hashlib.pbkdf2_hmac('sha256', password_bytes, salt_bytes, iterations)
|
||||
|
||||
import hmac
|
||||
match = hmac.compare_digest(computed, hash_bytes)
|
||||
print(f"变体 {i} 哈希匹配: {match}")
|
||||
if match:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"变体 {i} 失败: {e}")
|
||||
|
||||
print(f"\nSalt 解码成功,长度: {len(salt_bytes)}")
|
||||
print(f"Hash 解码成功,长度: {len(hash_bytes)}")
|
||||
|
||||
# 计算 PBKDF2
|
||||
password_bytes = password.encode('utf-8')
|
||||
computed = hashlib.pbkdf2_hmac('sha256', password_bytes, salt_bytes, iterations)
|
||||
|
||||
print(f"\n计算的哈希长度: {len(computed)}")
|
||||
print(f"存储的哈希长度: {len(hash_bytes)}")
|
||||
|
||||
import hmac
|
||||
match = hmac.compare_digest(computed, hash_bytes)
|
||||
print(f"\n哈希匹配: {match}")
|
||||
except Exception as e:
|
||||
print(f"\n解码/验证失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
validator = AuthValidator(software_id="ArticleReplace",
|
||||
api_url="http://km.taisan.online/api/v1",
|
||||
gui_mode=True
|
||||
)
|
||||
|
||||
# 执行验证
|
||||
if not validator.validate():
|
||||
print("授权验证失败,程序退出")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user