""" 安全的验证器 V2 - 改进版 主要改进: 1. 持久化缓存存储(不会被系统清理) 2. 智能验证策略(已激活用户不频繁打扰) 3. 到期提醒功能 4. 增强的离线验证 5. 改善的用户界面 作者:太一 微信:taiyi1224 """ import os import sys import io # 修复Windows控制台编码问题(打包后的EXE中文输出) try: if sys.stdout.encoding != 'utf-8': sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') if sys.stderr.encoding != 'utf-8': sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') except Exception: pass # 安全的打印函数(避免编码错误) def safe_print(*args, **kwargs): """安全的打印函数,避免编码错误导致程序崩溃""" try: print(*args, **kwargs) except (UnicodeEncodeError, UnicodeDecodeError): # 如果编码失败,尝试用ASCII打印 try: ascii_args = [str(arg).encode('ascii', errors='replace').decode('ascii') for arg in args] print(*ascii_args, **kwargs) except: # 完全失败则静默忽略 pass def safe_format_date(dt): """安全的日期格式化函数,避免strftime中文编码错误""" try: # 不使用strftime的中文格式,直接用字符串拼接 return f"{dt.year}-{dt.month:02d}-{dt.day:02d}" except: return "日期未知" import json import hashlib import tempfile import subprocess import tkinter as tk from tkinter import messagebox import threading import time import hmac import base64 from datetime import datetime, timedelta from cryptography.fernet import Fernet import requests import urllib3 from pathlib import Path import sqlite3 # 禁用SSL警告(用于自签名证书) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # ========== 配置区(这些可以公开) ========== API_BASE_URL = "http://localhost:5100/" # 使用本地API服务器 API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224" # 从环境变量读取更安全 SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换(用于显示) SOFTWARE_ID = "SOFTWARE_ID_PLACEHOLDER" # 软件唯一ID(用于版本管理和验证) # 特殊标识(用于检测已加密文件) ENCRYPTION_MARKER = "SECURE_EXE_VALIDATOR_V2_TAIYI" # ========== 机器码生成 ========== def get_machine_code(): """生成机器码(简化且稳定的版本)""" import platform import uuid try: if platform.system() == 'Windows': # 优先使用主板序列号 try: import subprocess result = subprocess.check_output( 'wmic baseboard get serialnumber', shell=True, stderr=subprocess.DEVNULL, timeout=5 ).decode().strip() lines = [line.strip() for line in result.split('\n') if line.strip()] if len(lines) > 1 and lines[1] not in ['To Be Filled By O.E.M.', '']: return hashlib.md5(lines[1].encode()).hexdigest()[:16].upper() except: pass # 备用方案:使用MAC地址 mac = uuid.getnode() mac_str = ':'.join(['{:02x}'.format((mac >> i) & 0xff) for i in range(0, 48, 8)][::-1]) return hashlib.md5(mac_str.encode()).hexdigest()[:16].upper() except Exception as e: # 最终备用 fallback = f"{platform.node()}{uuid.getnode()}" return hashlib.md5(fallback.encode()).hexdigest()[:16].upper() # ========== 持久化缓存管理器 ========== class LicenseCache: """持久化许可证缓存管理器(使用SQLite)""" def __init__(self): # 存储在用户AppData目录,不会被系统清理 if os.name == 'nt': # Windows cache_base = Path(os.environ.get('APPDATA', Path.home())) else: # Linux/Mac cache_base = Path.home() / '.config' self.cache_dir = cache_base / '.secure_license' self.cache_dir.mkdir(exist_ok=True, parents=True) self.db_path = self.cache_dir / 'license.db' self._init_db() def _init_db(self): """初始化数据库""" conn = sqlite3.connect(str(self.db_path)) conn.execute(''' CREATE TABLE IF NOT EXISTS license_cache ( id INTEGER PRIMARY KEY, software_name TEXT NOT NULL, software_id TEXT NOT NULL, license_key TEXT NOT NULL, machine_code TEXT NOT NULL, token TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, last_validated TEXT NOT NULL, status TEXT DEFAULT 'active', created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(software_id, machine_code) ) ''') conn.commit() conn.close() def save_license(self, software_name, software_id, license_key, machine_code, token, start_time, end_time): """保存许可证信息(使用软件ID)""" try: safe_print(f"[DEBUG] 保存许可证缓存到: {self.db_path}") safe_print(f"[DEBUG] software_id={software_id}, license_key={license_key[:10]}...") conn = sqlite3.connect(str(self.db_path)) # 转换datetime为ISO格式字符串 if isinstance(start_time, datetime): start_time = start_time.isoformat() if isinstance(end_time, datetime): end_time = end_time.isoformat() safe_print(f"[DEBUG] start_time={start_time}, end_time={end_time}") conn.execute(''' INSERT OR REPLACE INTO license_cache (software_name, software_id, license_key, machine_code, token, start_time, end_time, last_validated) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', (software_name, software_id, license_key, machine_code, token, start_time, end_time, datetime.now().isoformat())) conn.commit() conn.close() safe_print("[DEBUG] 许可证缓存保存成功") return True except Exception as e: safe_print(f"[ERROR] 保存许可证缓存失败: {e}") import traceback traceback.print_exc() return False def get_license(self, software_id, machine_code): """获取许可证信息(使用软件ID)""" try: safe_print(f"[DEBUG] 从数据库读取许可证: {self.db_path}") safe_print(f"[DEBUG] 查询条件: software_id={software_id}, machine_code={machine_code}") conn = sqlite3.connect(str(self.db_path)) cursor = conn.cursor() cursor.execute(''' SELECT * FROM license_cache WHERE software_id=? AND machine_code=? ''', (software_id, machine_code)) row = cursor.fetchone() if not row: safe_print("[DEBUG] 未找到匹配的许可证记录") # 调试:列出所有记录 cursor.execute('SELECT software_id, machine_code, license_key FROM license_cache') all_records = cursor.fetchall() if all_records: safe_print(f"[DEBUG] 数据库中共有 {len(all_records)} 条记录:") for rec in all_records[:5]: # 只显示前5条 safe_print(f" - software_id={rec[0]}, machine_code={rec[1]}, license_key={rec[2][:10] if rec[2] else 'N/A'}...") else: safe_print("[DEBUG] 数据库为空") conn.close() return None # 转换为字典 columns = [desc[0] for desc in cursor.description] license_data = dict(zip(columns, row)) conn.close() safe_print(f"[DEBUG] 找到许可证: license_key={license_data.get('license_key', 'N/A')[:10]}...") return license_data except Exception as e: safe_print(f"[ERROR] 读取许可证缓存失败: {e}") import traceback traceback.print_exc() return None def should_revalidate(self, license_data): """判断是否需要重新验证(每7天联网验证一次)""" try: last_validated = datetime.fromisoformat(license_data['last_validated']) return (datetime.now() - last_validated).days >= 7 except: return True def is_expired(self, license_data): """检查许可证是否过期(只比较日期,不比较时间)""" try: end_time = datetime.fromisoformat(license_data['end_time']) # 只比较日期部分,到期日当天仍然有效 today = datetime.now().date() expire_date = end_time.date() return today > expire_date except: return True def get_days_remaining(self, license_data): """获取剩余天数(包括到期日当天)""" try: end_time = datetime.fromisoformat(license_data['end_time']) # 只比较日期部分 today = datetime.now().date() expire_date = end_time.date() remaining = (expire_date - today).days # 返回剩余天数(0表示今天是最后一天) return max(0, remaining) except: return 0 def update_last_validated(self, software_id, machine_code): """更新最后验证时间(使用软件ID)""" try: conn = sqlite3.connect(str(self.db_path)) conn.execute(''' UPDATE license_cache SET last_validated=? WHERE software_id=? AND machine_code=? ''', (datetime.now().isoformat(), software_id, machine_code)) conn.commit() conn.close() return True except Exception as e: safe_print(f"更新验证时间失败: {e}") return False def clear_license(self, software_id, machine_code): """清除许可证缓存(使用软件ID)""" try: conn = sqlite3.connect(str(self.db_path)) conn.execute(''' DELETE FROM license_cache WHERE software_id=? AND machine_code=? ''', (software_id, machine_code)) conn.commit() conn.close() return True except Exception as e: safe_print(f"清除许可证缓存失败: {e}") return False # ========== API通信 ========== def create_signature(license_key, machine_code, software_identifier): """创建请求签名(使用软件名称或ID)""" message = f"{license_key}{machine_code}{software_identifier}" return hmac.new(API_KEY.encode(), message.encode(), hashlib.sha256).hexdigest() def validate_license_online(license_key, machine_code): """在线验证卡密(使用软件名称进行验证)""" try: # 使用software_name创建签名(与服务器端保持一致) signature = create_signature(license_key, machine_code, SOFTWARE_NAME) safe_print(f"[DEBUG] 开始在线验证: license_key={license_key[:10]}..., machine_code={machine_code}") response = requests.post( f"{API_BASE_URL}api/validate", json={ 'license_key': license_key, 'machine_code': machine_code, 'software_name': SOFTWARE_NAME, # 显示名称 'software_id': SOFTWARE_ID, # 保留用于将来扩展 'signature': signature }, headers={'X-API-Key': API_KEY}, timeout=10, verify=False # 跳过SSL证书验证(用于自签名证书) ) safe_print(f"[DEBUG] 服务器响应状态: {response.status_code}") if response.status_code == 200: data = response.json() safe_print(f"[DEBUG] 服务器返回数据: success={data.get('success')}, message={data.get('message')}") if data.get('success'): # 保存到持久化缓存(使用软件ID) cache = LicenseCache() # 使用服务器返回的时间信息(优先) end_time = data.get('expires_at') start_time = data.get('start_time', datetime.now().isoformat()) # 如果服务器没有返回expires_at,使用默认值(不应该发生) if not end_time: safe_print("[WARNING] 服务器未返回expires_at,使用默认值") end_time = (datetime.now() + timedelta(days=30)).isoformat() safe_print(f"[DEBUG] 保存缓存: start_time={start_time}, end_time={end_time}") save_success = cache.save_license( SOFTWARE_NAME, SOFTWARE_ID, license_key, machine_code, data.get('token', ''), start_time, end_time ) if save_success: safe_print("[DEBUG] 缓存保存成功") else: safe_print("[WARNING] 缓存保存失败") return True, data.get('message', '验证成功') else: return False, data.get('message', '验证失败') else: return False, f"服务器错误: {response.status_code}" except requests.exceptions.ConnectionError: return False, "无法连接到服务器,请检查网络" except requests.exceptions.Timeout: return False, "服务器响应超时" except Exception as e: safe_print(f"[ERROR] 验证异常: {e}") import traceback traceback.print_exc() return False, f"验证过程出错: {str(e)}" def validate_license_offline(license_data): """增强的离线验证""" try: # 1. 验证数据完整性 required_fields = ['license_key', 'token', 'end_time', 'machine_code'] if not all(field in license_data for field in required_fields): return False, "缓存数据不完整", None # 2. 验证机器码 current_machine_code = get_machine_code() if license_data['machine_code'] != current_machine_code: return False, "设备已更改,需要重新激活", None # 3. 检查许可证是否过期(只比较日期) end_time = datetime.fromisoformat(license_data['end_time']) today = datetime.now().date() expire_date = end_time.date() if today > expire_date: return False, "许可证已过期", None # 4. 计算剩余天数(包括今天) remaining_days = (expire_date - today).days # 5. 验证token(可选,增强安全性) # 注意:即使没有token也允许通过,因为其他验证已足够 token = license_data.get('token', '') # token验证是可选的,不影响主要功能 # 6. 返回详细信息 info = { 'remaining_days': remaining_days, 'end_time': end_time, 'license_key': license_data['license_key'] } return True, f"离线验证成功", info except Exception as e: return False, f"离线验证失败: {str(e)}", None def try_online_revalidation(license_data): """尝试联网刷新状态(静默,不打扰用户)""" try: machine_code = get_machine_code() success, msg = validate_license_online( license_data['license_key'], machine_code ) return success, msg except: # 网络问题,不影响使用 return True, "离线模式" # ========== 到期提醒 ========== def show_expiry_reminder(remaining_days, end_time_str): """显示到期提醒""" try: end_time = datetime.fromisoformat(end_time_str) except: return # 根据剩余天数选择不同的提醒级别 if remaining_days == 0: # 今天是最后一天,但仍然可以使用 title = "🔴 许可证今天到期" message = f"您的许可证将于今天({safe_format_date(end_time)})到期\n明天将无法使用,请尽快联系管理员续费" urgency = "urgent" # 不是critical,因为今天还能用 elif remaining_days == 1: title = "🔴 许可证即将到期" message = f"您的许可证将于明天到期\n请尽快联系管理员续费" urgency = "urgent" elif remaining_days <= 3: title = "🟡 许可证即将到期" message = f"您的许可证还剩 {remaining_days} 天\n建议尽快联系管理员续费" urgency = "warning" elif remaining_days <= 7: title = "🟢 许可证到期提醒" message = f"您的许可证还剩 {remaining_days} 天\n请注意及时续费" urgency = "info" else: # 不需要提醒 return # 创建提醒窗口 root = tk.Tk() root.title(title) root.geometry("450x280") root.resizable(False, False) # 居中 root.update_idletasks() x = (root.winfo_screenwidth() - 450) // 2 y = (root.winfo_screenheight() - 280) // 2 root.geometry(f"450x280+{x}+{y}") # 根据紧急程度设置窗口属性 if urgency == "critical": root.attributes('-topmost', True) # 标题 title_color = "red" if urgency in ["critical", "urgent"] else "orange" if urgency == "warning" else "green" tk.Label(root, text=title, font=("Microsoft YaHei", 14, "bold"), fg=title_color).pack(pady=20) # 消息 tk.Label(root, text=message, font=("Microsoft YaHei", 11), justify=tk.CENTER).pack(pady=10) # 到期时间 tk.Label(root, text=f"到期时间:{safe_format_date(end_time)}", font=("Microsoft YaHei", 10), fg="gray").pack(pady=5) # 剩余天数(醒目显示) if remaining_days > 0: tk.Label(root, text=f"剩余 {remaining_days} 天", font=("Microsoft YaHei", 16, "bold"), fg=title_color).pack(pady=10) # 联系方式 tk.Label(root, text="联系管理员续费:V:taiyi1224", font=("Microsoft YaHei", 9), fg="blue").pack(pady=10) # 按钮 frame_buttons = tk.Frame(root) frame_buttons.pack(pady=15) def on_continue(): root.destroy() def on_contact(): import webbrowser try: # 尝试打开微信(如果有协议链接) webbrowser.open("weixin://") except: pass root.destroy() # 如果未过期,显示"继续使用"按钮 if urgency != "critical": tk.Button(frame_buttons, text="继续使用", command=on_continue, bg="#27ae60", fg="white", font=("Microsoft YaHei", 10, "bold"), padx=20, pady=8).pack(side=tk.LEFT, padx=10) tk.Button(frame_buttons, text="联系续费", command=on_contact, bg="#3498db", fg="white", font=("Microsoft YaHei", 10, "bold"), padx=20, pady=8).pack(side=tk.LEFT, padx=10) # 如果是critical级别,设置为模态窗口 if urgency == "critical": root.transient() root.grab_set() root.mainloop() # ========== 资源解密 ========== # ENCRYPTED_RESOURCE_PLACEHOLDER ENCRYPTED_RESOURCE = None def decrypt_resource(): """解密嵌入的资源""" try: if not ENCRYPTED_RESOURCE: return None, "未找到嵌入资源" from cryptography.fernet import Fernet import base64 # 重建完整密钥 key_part1_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_hint'].encode()) key_part2_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_part2'].encode()) # 合并密钥 full_key = key_part1_bytes + key_part2_bytes # 解密 fernet = Fernet(full_key) encrypted_data = base64.b64decode(ENCRYPTED_RESOURCE['data'].encode()) decrypted = fernet.decrypt(encrypted_data) # 验证哈希 if hashlib.sha256(decrypted).hexdigest() != ENCRYPTED_RESOURCE['hash']: return None, "文件完整性校验失败" return decrypted, "解密成功" except Exception as e: return None, f"解密失败: {str(e)}" def extract_and_run(): """提取并运行原始程序""" try: exe_content, msg = decrypt_resource() if not exe_content: return False, msg # 写入临时文件 temp_path = os.path.join(tempfile.gettempdir(), f"app_{os.getpid()}.exe") with open(temp_path, 'wb') as f: f.write(exe_content) # 设置权限 if hasattr(os, 'chmod'): os.chmod(temp_path, 0o755) # 启动程序 if os.name == 'nt': subprocess.Popen([temp_path], creationflags=subprocess.CREATE_NEW_CONSOLE) else: subprocess.Popen([temp_path]) time.sleep(2) # 等待程序启动 return True, "程序启动成功" except Exception as e: return False, f"启动失败: {str(e)}" # ========== 增强的激活对话框 ========== def show_enhanced_activation_dialog(message=None): """增强版激活对话框(使用软件ID)""" # 尝试加载已有许可证信息(使用软件ID) cache = LicenseCache() machine_code = get_machine_code() existing_license = cache.get_license(SOFTWARE_ID, machine_code) root = tk.Tk() root.title(f"{SOFTWARE_NAME} - 许可证管理") root.geometry("550x520") root.resizable(False, False) # 居中 root.update_idletasks() x = (root.winfo_screenwidth() - 550) // 2 y = (root.winfo_screenheight() - 520) // 2 root.geometry(f"550x520+{x}+{y}") # 标题 tk.Label(root, text="🔐 软件许可证验证", font=("Microsoft YaHei", 16, "bold")).pack(pady=20) # 如果有消息,显示 if message: tk.Label(root, text=message, font=("Microsoft YaHei", 10), fg="red").pack(pady=5) # 如果有现有许可证,显示状态 if existing_license: status_frame = tk.LabelFrame(root, text="当前许可证状态", font=("Microsoft YaHei", 10, "bold"), padx=10, pady=10) status_frame.pack(fill=tk.X, padx=30, pady=10) remaining = cache.get_days_remaining(existing_license) is_expired = cache.is_expired(existing_license) if is_expired: status_text = "❌ 已过期" status_color = "red" elif remaining <= 7: status_text = f"⚠️ 剩余 {remaining} 天" status_color = "orange" else: status_text = f"✅ 剩余 {remaining} 天" status_color = "green" tk.Label(status_frame, text=f"状态:{status_text}", font=("Microsoft YaHei", 11, "bold"), fg=status_color).pack(anchor=tk.W, pady=3) tk.Label(status_frame, text=f"激活码:{existing_license['license_key']}", font=("Consolas", 9), fg="gray").pack(anchor=tk.W, pady=2) end_time_str = existing_license.get('end_time', '') if end_time_str: try: end_time = datetime.fromisoformat(end_time_str) tk.Label(status_frame, text=f"到期时间:{safe_format_date(end_time)}", font=("Microsoft YaHei", 9), fg="gray").pack(anchor=tk.W, pady=2) except: pass # 机器码 frame_machine = tk.Frame(root) frame_machine.pack(fill=tk.X, padx=30, pady=10) tk.Label(frame_machine, text="机器码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) entry_machine = tk.Entry(frame_machine, state="readonly", font=("Consolas", 10), justify=tk.CENTER) entry_machine.insert(0, machine_code) entry_machine.pack(fill=tk.X, pady=5) def copy_machine_code(): root.clipboard_clear() root.clipboard_append(machine_code) messagebox.showinfo("成功", "机器码已复制到剪贴板\n请发送给管理员获取激活码") tk.Button(frame_machine, text="📋 复制机器码", command=copy_machine_code, font=("Microsoft YaHei", 9)).pack(anchor=tk.E) # 激活码输入 frame_key = tk.Frame(root) frame_key.pack(fill=tk.X, padx=30, pady=10) tk.Label(frame_key, text="激活码:" if not existing_license else "新激活码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) entry_key = tk.Entry(frame_key, font=("Consolas", 12), justify=tk.CENTER) entry_key.pack(fill=tk.X, pady=5) entry_key.focus() # 状态标签 status_label = tk.Label(root, text="", font=("Microsoft YaHei", 9)) status_label.pack(pady=10) # 按钮区域 frame_buttons = tk.Frame(root) frame_buttons.pack(pady=15) def on_activate(): license_key = entry_key.get().strip() if not license_key: messagebox.showerror("错误", "请输入激活码") return btn_activate.config(state=tk.DISABLED, text="验证中...") status_label.config(text="正在联网验证激活码,请稍候...", fg="blue") root.update() def validate_thread(): success, msg = validate_license_online(license_key, machine_code) root.after(0, lambda: handle_result(success, msg)) threading.Thread(target=validate_thread, daemon=True).start() def handle_result(success, msg): if success: messagebox.showinfo("成功", f"激活成功!\n{msg}\n\n正在启动程序...") root.destroy() ok, err = extract_and_run() if ok: sys.exit(0) else: messagebox.showerror("错误", f"程序启动失败:\n{err}") sys.exit(1) else: messagebox.showerror("激活失败", f"{msg}\n\n请检查:\n1. 激活码是否正确\n2. 网络连接是否正常\n3. 激活码是否已被其他设备使用") btn_activate.config(state=tk.NORMAL, text="验证并激活") status_label.config(text="", fg="black") btn_activate = tk.Button(frame_buttons, text="验证并激活", command=on_activate, bg="#27ae60", fg="white", font=("Microsoft YaHei", 11, "bold"), padx=25, pady=10, cursor="hand2") btn_activate.pack(side=tk.LEFT, padx=10) # 如果有现有许可证且未过期,允许继续使用 if existing_license and not cache.is_expired(existing_license): def on_continue(): root.destroy() ok, err = extract_and_run() if ok: sys.exit(0) else: messagebox.showerror("错误", f"程序启动失败:\n{err}") sys.exit(1) tk.Button(frame_buttons, text="继续使用", command=on_continue, bg="#3498db", fg="white", font=("Microsoft YaHei", 11, "bold"), padx=25, pady=10, cursor="hand2").pack(side=tk.LEFT, padx=10) tk.Button(frame_buttons, text="退出", command=sys.exit, bg="#e74c3c", fg="white", font=("Microsoft YaHei", 11, "bold"), padx=25, pady=10, cursor="hand2").pack(side=tk.LEFT, padx=10) # 帮助信息 help_frame = tk.Frame(root) help_frame.pack(pady=10) tk.Label(help_frame, text="需要帮助?", font=("Microsoft YaHei", 8), fg="gray").pack() tk.Label(help_frame, text="联系管理员:V:taiyi1224", font=("Microsoft YaHei", 8, "bold"), fg="blue", cursor="hand2").pack() root.bind('', lambda e: on_activate()) root.protocol("WM_DELETE_WINDOW", sys.exit) root.mainloop() # ========== 智能验证主流程 ========== def smart_validate(): """智能验证策略(使用软件ID)""" try: # 1. 初始化缓存管理器 cache = LicenseCache() machine_code = get_machine_code() safe_print(f"[DEBUG] 软件ID: {SOFTWARE_ID}") safe_print(f"[DEBUG] 机器码: {machine_code}") safe_print(f"[DEBUG] 缓存数据库路径: {cache.db_path}") # 2. 尝试加载本地缓存(使用软件ID) license_data = cache.get_license(SOFTWARE_ID, machine_code) if not license_data: # 首次使用,需要在线激活 safe_print("[INFO] 未找到本地缓存,首次使用,需要激活") show_enhanced_activation_dialog() return else: safe_print(f"[DEBUG] 找到本地缓存: license_key={license_data.get('license_key', 'N/A')[:10]}...") # 3. 检查是否过期 if cache.is_expired(license_data): safe_print("[INFO] 许可证已过期") cache.clear_license(SOFTWARE_ID, machine_code) show_enhanced_activation_dialog(message="许可证已过期,请重新激活") return # 4. 计算剩余天数 remaining_days = cache.get_days_remaining(license_data) safe_print(f"[INFO] 许可证剩余 {remaining_days} 天") # 5. 判断是否需要联网刷新(每7天一次) if cache.should_revalidate(license_data): safe_print("[INFO] 尝试联网刷新状态...") # 尝试联网验证(静默,不打扰用户) success, msg = try_online_revalidation(license_data) if success: # 更新缓存中的验证时间(使用软件ID) update_success = cache.update_last_validated(SOFTWARE_ID, machine_code) if update_success: safe_print(f"[DEBUG] 状态刷新成功: {msg}") else: safe_print(f"[WARNING] 更新验证时间失败") else: safe_print(f"[INFO] 状态刷新失败(不影响使用): {msg}") # 即使失败也继续使用(网络问题不影响已激活用户) else: safe_print("[DEBUG] 无需联网刷新(距上次验证不足7天)") # 6. 离线验证 success, msg, info = validate_license_offline(license_data) if not success: safe_print(f"[ERROR] 离线验证失败: {msg}") cache.clear_license(SOFTWARE_ID, machine_code) show_enhanced_activation_dialog(message=msg) return safe_print(f"[INFO] 离线验证成功: {msg}") # 7. 显示到期提醒(如果接近到期) if remaining_days <= 7: safe_print(f"显示到期提醒(剩余{remaining_days}天)") show_expiry_reminder(remaining_days, license_data['end_time']) # 8. 验证通过,启动程序 safe_print("启动程序...") ok, err = extract_and_run() if ok: safe_print("程序启动成功") sys.exit(0) else: safe_print(f"程序启动失败: {err}") messagebox.showerror("错误", f"程序启动失败:\n{err}") sys.exit(1) except Exception as e: safe_print(f"验证过程出错: {e}") try: import traceback traceback.print_exc() except: pass messagebox.showerror("错误", f"验证过程出错:\n{str(e)}") sys.exit(1) # ========== 主函数 ========== def main(): """主函数""" safe_print("=" * 60) safe_print(f"软件许可证验证系统 V2.0 (支持版本管理)") safe_print(f"软件名称: {SOFTWARE_NAME}") safe_print(f"软件ID: {SOFTWARE_ID}") safe_print(f"机器码: {get_machine_code()}") safe_print("=" * 60) # 使用智能验证策略 smart_validate() if __name__ == '__main__': main()