diff --git a/app/api/auth.py b/app/api/auth.py index fdb76b2..e305faa 100644 --- a/app/api/auth.py +++ b/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 diff --git a/app/api/product.py b/app/api/product.py index acdeb2f..4a3f083 100644 --- a/app/api/product.py +++ b/app/api/product.py @@ -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: diff --git a/app/api/user.py b/app/api/user.py index 22cf816..cb53993 100644 --- a/app/api/user.py +++ b/app/api/user.py @@ -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, diff --git a/app/models/product.py b/app/models/product.py index 9540829..a05968b 100644 --- a/app/models/product.py +++ b/app/models/product.py @@ -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 diff --git a/app/utils/.auth_cache b/app/utils/.auth_cache deleted file mode 100644 index d4abb1c..0000000 --- a/app/utils/.auth_cache +++ /dev/null @@ -1,3 +0,0 @@ -{ - "remember_DEMO_SOFTWARE_2024": true -} \ No newline at end of file diff --git a/app/utils/.machine_code b/app/utils/.machine_code deleted file mode 100644 index 2e5f69e..0000000 --- a/app/utils/.machine_code +++ /dev/null @@ -1 +0,0 @@ -843665BD5F1FC5FE8946763877F8E457 \ No newline at end of file diff --git a/app/utils/.machine_id b/app/utils/.machine_id deleted file mode 100644 index 3310e2a..0000000 --- a/app/utils/.machine_id +++ /dev/null @@ -1 +0,0 @@ -E6684431683D6EAB7ED7FA9CE6C8BB9B \ No newline at end of file diff --git a/app/utils/auth_validator.py b/app/utils/auth_validator.py index 9e31955..9fe3eab 100644 --- a/app/utils/auth_validator.py +++ b/app/utils/auth_validator.py @@ -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("", 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 --- \ No newline at end of file diff --git a/app/web/templates/license/export.html b/app/web/templates/license/export.html index 9e49c7e..a7e9bdc 100644 --- a/app/web/templates/license/export.html +++ b/app/web/templates/license/export.html @@ -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(() => { // 恢复按钮状态 diff --git a/logs/kamaxitong.log b/logs/kamaxitong.log index 2ff9e9b..d067186 100644 --- a/logs/kamaxitong.log +++ b/logs/kamaxitong.log @@ -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: 错误类型: [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] diff --git a/test_werkzeug_base64.py b/test_werkzeug_base64.py index e4ded3c..1b0d584 100644 --- a/test_werkzeug_base64.py +++ b/test_werkzeug_base64.py @@ -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("授权验证失败,程序退出")