2025-10-23 18:28:10 +08:00
|
|
|
|
"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
安全的验证器 V2 - 改进版
|
|
|
|
|
|
主要改进:
|
|
|
|
|
|
1. 持久化缓存存储(不会被系统清理)
|
|
|
|
|
|
2. 智能验证策略(已激活用户不频繁打扰)
|
|
|
|
|
|
3. 到期提醒功能
|
|
|
|
|
|
4. 增强的离线验证
|
|
|
|
|
|
5. 改善的用户界面
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
作者:太一
|
2025-10-25 17:49:09 +08:00
|
|
|
|
微信:taiyi1224
|
2025-10-23 18:28:10 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
2025-10-28 13:18:45 +08:00
|
|
|
|
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 "日期未知"
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
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
|
2025-10-25 17:49:09 +08:00
|
|
|
|
import urllib3
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
import sqlite3
|
|
|
|
|
|
|
|
|
|
|
|
# 禁用SSL警告(用于自签名证书)
|
|
|
|
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 配置区(这些可以公开) ==========
|
2025-10-25 17:49:09 +08:00
|
|
|
|
API_BASE_URL = "http://localhost:5100/" # 使用本地API服务器
|
|
|
|
|
|
API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224" # 从环境变量读取更安全
|
2025-10-28 13:18:45 +08:00
|
|
|
|
SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换(用于显示)
|
|
|
|
|
|
SOFTWARE_ID = "SOFTWARE_ID_PLACEHOLDER" # 软件唯一ID(用于版本管理和验证)
|
|
|
|
|
|
|
|
|
|
|
|
# 特殊标识(用于检测已加密文件)
|
|
|
|
|
|
ENCRYPTION_MARKER = "SECURE_EXE_VALIDATOR_V2_TAIYI"
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 机器码生成 ==========
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# ========== 持久化缓存管理器 ==========
|
|
|
|
|
|
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'
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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,
|
2025-10-28 13:18:45 +08:00
|
|
|
|
software_id TEXT NOT NULL,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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,
|
2025-10-28 13:18:45 +08:00
|
|
|
|
UNIQUE(software_id, machine_code)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
)
|
|
|
|
|
|
''')
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
def save_license(self, software_name, software_id, license_key, machine_code,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
token, start_time, end_time):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""保存许可证信息(使用软件ID)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 保存许可证缓存到: {self.db_path}")
|
|
|
|
|
|
safe_print(f"[DEBUG] software_id={software_id}, license_key={license_key[:10]}...")
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] start_time={start_time}, end_time={end_time}")
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
conn.execute('''
|
|
|
|
|
|
INSERT OR REPLACE INTO license_cache
|
2025-10-28 13:18:45 +08:00
|
|
|
|
(software_name, software_id, license_key, machine_code, token,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
start_time, end_time, last_validated)
|
2025-10-28 13:18:45 +08:00
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
|
''', (software_name, software_id, license_key, machine_code, token,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
start_time, end_time, datetime.now().isoformat()))
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("[DEBUG] 许可证缓存保存成功")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[ERROR] 保存许可证缓存失败: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
def get_license(self, software_id, machine_code):
|
|
|
|
|
|
"""获取许可证信息(使用软件ID)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 从数据库读取许可证: {self.db_path}")
|
|
|
|
|
|
safe_print(f"[DEBUG] 查询条件: software_id={software_id}, machine_code={machine_code}")
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
conn = sqlite3.connect(str(self.db_path))
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
cursor.execute('''
|
|
|
|
|
|
SELECT * FROM license_cache
|
2025-10-28 13:18:45 +08:00
|
|
|
|
WHERE software_id=? AND machine_code=?
|
|
|
|
|
|
''', (software_id, machine_code))
|
2025-10-25 17:49:09 +08:00
|
|
|
|
row = cursor.fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
if not row:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
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] 数据库为空")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
conn.close()
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 转换为字典
|
|
|
|
|
|
columns = [desc[0] for desc in cursor.description]
|
|
|
|
|
|
license_data = dict(zip(columns, row))
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 找到许可证: license_key={license_data.get('license_key', 'N/A')[:10]}...")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return license_data
|
|
|
|
|
|
except Exception as e:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[ERROR] 读取许可证缓存失败: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""检查许可证是否过期(只比较日期,不比较时间)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
|
|
|
|
|
end_time = datetime.fromisoformat(license_data['end_time'])
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 只比较日期部分,到期日当天仍然有效
|
|
|
|
|
|
today = datetime.now().date()
|
|
|
|
|
|
expire_date = end_time.date()
|
|
|
|
|
|
return today > expire_date
|
2025-10-25 17:49:09 +08:00
|
|
|
|
except:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def get_days_remaining(self, license_data):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""获取剩余天数(包括到期日当天)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
|
|
|
|
|
end_time = datetime.fromisoformat(license_data['end_time'])
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 只比较日期部分
|
|
|
|
|
|
today = datetime.now().date()
|
|
|
|
|
|
expire_date = end_time.date()
|
|
|
|
|
|
remaining = (expire_date - today).days
|
|
|
|
|
|
# 返回剩余天数(0表示今天是最后一天)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return max(0, remaining)
|
|
|
|
|
|
except:
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
def update_last_validated(self, software_id, machine_code):
|
|
|
|
|
|
"""更新最后验证时间(使用软件ID)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
|
|
|
|
|
conn = sqlite3.connect(str(self.db_path))
|
|
|
|
|
|
conn.execute('''
|
|
|
|
|
|
UPDATE license_cache
|
|
|
|
|
|
SET last_validated=?
|
2025-10-28 13:18:45 +08:00
|
|
|
|
WHERE software_id=? AND machine_code=?
|
|
|
|
|
|
''', (datetime.now().isoformat(), software_id, machine_code))
|
2025-10-25 17:49:09 +08:00
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"更新验证时间失败: {e}")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
def clear_license(self, software_id, machine_code):
|
|
|
|
|
|
"""清除许可证缓存(使用软件ID)"""
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
|
|
|
|
|
conn = sqlite3.connect(str(self.db_path))
|
|
|
|
|
|
conn.execute('''
|
|
|
|
|
|
DELETE FROM license_cache
|
2025-10-28 13:18:45 +08:00
|
|
|
|
WHERE software_id=? AND machine_code=?
|
|
|
|
|
|
''', (software_id, machine_code))
|
2025-10-25 17:49:09 +08:00
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"清除许可证缓存失败: {e}")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return False
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== API通信 ==========
|
2025-10-28 13:18:45 +08:00
|
|
|
|
def create_signature(license_key, machine_code, software_identifier):
|
|
|
|
|
|
"""创建请求签名(使用软件名称或ID)"""
|
|
|
|
|
|
message = f"{license_key}{machine_code}{software_identifier}"
|
2025-10-23 18:28:10 +08:00
|
|
|
|
return hmac.new(API_KEY.encode(), message.encode(), hashlib.sha256).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
def validate_license_online(license_key, machine_code):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""在线验证卡密(使用软件名称进行验证)"""
|
2025-10-23 18:28:10 +08:00
|
|
|
|
try:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 使用software_name创建签名(与服务器端保持一致)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
signature = create_signature(license_key, machine_code, SOFTWARE_NAME)
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 开始在线验证: license_key={license_key[:10]}..., machine_code={machine_code}")
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
response = requests.post(
|
2025-10-25 17:49:09 +08:00
|
|
|
|
f"{API_BASE_URL}api/validate",
|
2025-10-23 18:28:10 +08:00
|
|
|
|
json={
|
|
|
|
|
|
'license_key': license_key,
|
|
|
|
|
|
'machine_code': machine_code,
|
2025-10-28 13:18:45 +08:00
|
|
|
|
'software_name': SOFTWARE_NAME, # 显示名称
|
|
|
|
|
|
'software_id': SOFTWARE_ID, # 保留用于将来扩展
|
2025-10-23 18:28:10 +08:00
|
|
|
|
'signature': signature
|
|
|
|
|
|
},
|
|
|
|
|
|
headers={'X-API-Key': API_KEY},
|
2025-10-25 17:49:09 +08:00
|
|
|
|
timeout=10,
|
|
|
|
|
|
verify=False # 跳过SSL证书验证(用于自签名证书)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 服务器响应状态: {response.status_code}")
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
if response.status_code == 200:
|
|
|
|
|
|
data = response.json()
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[DEBUG] 服务器返回数据: success={data.get('success')}, message={data.get('message')}")
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
if data.get('success'):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 保存到持久化缓存(使用软件ID)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
cache = LicenseCache()
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 使用服务器返回的时间信息(优先)
|
|
|
|
|
|
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(
|
2025-10-25 17:49:09 +08:00
|
|
|
|
SOFTWARE_NAME,
|
2025-10-28 13:18:45 +08:00
|
|
|
|
SOFTWARE_ID,
|
2025-10-23 18:28:10 +08:00
|
|
|
|
license_key,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
machine_code,
|
2025-10-23 18:28:10 +08:00
|
|
|
|
data.get('token', ''),
|
2025-10-25 17:49:09 +08:00
|
|
|
|
start_time,
|
|
|
|
|
|
end_time
|
2025-10-23 18:28:10 +08:00
|
|
|
|
)
|
2025-10-28 13:18:45 +08:00
|
|
|
|
|
|
|
|
|
|
if save_success:
|
|
|
|
|
|
safe_print("[DEBUG] 缓存保存成功")
|
|
|
|
|
|
else:
|
|
|
|
|
|
safe_print("[WARNING] 缓存保存失败")
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
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:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[ERROR] 验证异常: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
return False, f"验证过程出错: {str(e)}"
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
def validate_license_offline(license_data):
|
|
|
|
|
|
"""增强的离线验证"""
|
2025-10-23 18:28:10 +08:00
|
|
|
|
try:
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 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
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 3. 检查许可证是否过期(只比较日期)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
end_time = datetime.fromisoformat(license_data['end_time'])
|
2025-10-28 13:18:45 +08:00
|
|
|
|
today = datetime.now().date()
|
|
|
|
|
|
expire_date = end_time.date()
|
|
|
|
|
|
if today > expire_date:
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return False, "许可证已过期", None
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 4. 计算剩余天数(包括今天)
|
|
|
|
|
|
remaining_days = (expire_date - today).days
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 5. 验证token(可选,增强安全性)
|
|
|
|
|
|
# 注意:即使没有token也允许通过,因为其他验证已足够
|
2025-10-25 17:49:09 +08:00
|
|
|
|
token = license_data.get('token', '')
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# token验证是可选的,不影响主要功能
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 6. 返回详细信息
|
|
|
|
|
|
info = {
|
|
|
|
|
|
'remaining_days': remaining_days,
|
|
|
|
|
|
'end_time': end_time,
|
|
|
|
|
|
'license_key': license_data['license_key']
|
|
|
|
|
|
}
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
return True, f"离线验证成功", info
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
# 根据剩余天数选择不同的提醒级别
|
2025-10-28 13:18:45 +08:00
|
|
|
|
if remaining_days == 0:
|
|
|
|
|
|
# 今天是最后一天,但仍然可以使用
|
|
|
|
|
|
title = "🔴 许可证今天到期"
|
|
|
|
|
|
message = f"您的许可证将于今天({safe_format_date(end_time)})到期\n明天将无法使用,请尽快联系管理员续费"
|
|
|
|
|
|
urgency = "urgent" # 不是critical,因为今天还能用
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 到期时间
|
2025-10-28 13:18:45 +08:00
|
|
|
|
tk.Label(root, text=f"到期时间:{safe_format_date(end_time)}",
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 资源解密 ==========
|
|
|
|
|
|
# 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)}"
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# ========== 增强的激活对话框 ==========
|
|
|
|
|
|
def show_enhanced_activation_dialog(message=None):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""增强版激活对话框(使用软件ID)"""
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 尝试加载已有许可证信息(使用软件ID)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
cache = LicenseCache()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
machine_code = get_machine_code()
|
2025-10-28 13:18:45 +08:00
|
|
|
|
existing_license = cache.get_license(SOFTWARE_ID, machine_code)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
root = tk.Tk()
|
2025-10-25 17:49:09 +08:00
|
|
|
|
root.title(f"{SOFTWARE_NAME} - 许可证管理")
|
|
|
|
|
|
root.geometry("550x520")
|
2025-10-23 18:28:10 +08:00
|
|
|
|
root.resizable(False, False)
|
|
|
|
|
|
|
|
|
|
|
|
# 居中
|
|
|
|
|
|
root.update_idletasks()
|
2025-10-25 17:49:09 +08:00
|
|
|
|
x = (root.winfo_screenwidth() - 550) // 2
|
|
|
|
|
|
y = (root.winfo_screenheight() - 520) // 2
|
|
|
|
|
|
root.geometry(f"550x520+{x}+{y}")
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
# 标题
|
|
|
|
|
|
tk.Label(root, text="🔐 软件许可证验证",
|
|
|
|
|
|
font=("Microsoft YaHei", 16, "bold")).pack(pady=20)
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 如果有消息,显示
|
|
|
|
|
|
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,
|
2025-10-28 13:18:45 +08:00
|
|
|
|
text=f"到期时间:{safe_format_date(end_time)}",
|
2025-10-25 17:49:09 +08:00
|
|
|
|
font=("Microsoft YaHei", 9),
|
|
|
|
|
|
fg="gray").pack(anchor=tk.W, pady=2)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
# 机器码
|
|
|
|
|
|
frame_machine = tk.Frame(root)
|
|
|
|
|
|
frame_machine.pack(fill=tk.X, padx=30, pady=10)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
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)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
messagebox.showinfo("成功", "机器码已复制到剪贴板\n请发送给管理员获取激活码")
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
tk.Button(frame_machine, text="📋 复制机器码",
|
|
|
|
|
|
command=copy_machine_code,
|
|
|
|
|
|
font=("Microsoft YaHei", 9)).pack(anchor=tk.E)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 激活码输入
|
2025-10-23 18:28:10 +08:00
|
|
|
|
frame_key = tk.Frame(root)
|
|
|
|
|
|
frame_key.pack(fill=tk.X, padx=30, pady=10)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
entry_key.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
entry_key.focus()
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 状态标签
|
2025-10-23 18:28:10 +08:00
|
|
|
|
status_label = tk.Label(root, text="", font=("Microsoft YaHei", 9))
|
|
|
|
|
|
status_label.pack(pady=10)
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 按钮区域
|
2025-10-23 18:28:10 +08:00
|
|
|
|
frame_buttons = tk.Frame(root)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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")
|
2025-10-23 18:28:10 +08:00
|
|
|
|
btn_activate.pack(side=tk.LEFT, padx=10)
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 如果有现有许可证且未过期,允许继续使用
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
2025-10-23 18:28:10 +08:00
|
|
|
|
tk.Button(frame_buttons, text="退出", command=sys.exit,
|
2025-10-25 17:49:09 +08:00
|
|
|
|
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()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
root.bind('<Return>', lambda e: on_activate())
|
|
|
|
|
|
root.protocol("WM_DELETE_WINDOW", sys.exit)
|
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# ========== 智能验证主流程 ==========
|
|
|
|
|
|
def smart_validate():
|
2025-10-28 13:18:45 +08:00
|
|
|
|
"""智能验证策略(使用软件ID)"""
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 1. 初始化缓存管理器
|
|
|
|
|
|
cache = LicenseCache()
|
|
|
|
|
|
machine_code = get_machine_code()
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
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)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
if not license_data:
|
|
|
|
|
|
# 首次使用,需要在线激活
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("[INFO] 未找到本地缓存,首次使用,需要激活")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
show_enhanced_activation_dialog()
|
|
|
|
|
|
return
|
2025-10-28 13:18:45 +08:00
|
|
|
|
else:
|
|
|
|
|
|
safe_print(f"[DEBUG] 找到本地缓存: license_key={license_data.get('license_key', 'N/A')[:10]}...")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 3. 检查是否过期
|
|
|
|
|
|
if cache.is_expired(license_data):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("[INFO] 许可证已过期")
|
|
|
|
|
|
cache.clear_license(SOFTWARE_ID, machine_code)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
show_enhanced_activation_dialog(message="许可证已过期,请重新激活")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 4. 计算剩余天数
|
|
|
|
|
|
remaining_days = cache.get_days_remaining(license_data)
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[INFO] 许可证剩余 {remaining_days} 天")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 5. 判断是否需要联网刷新(每7天一次)
|
|
|
|
|
|
if cache.should_revalidate(license_data):
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("[INFO] 尝试联网刷新状态...")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 尝试联网验证(静默,不打扰用户)
|
|
|
|
|
|
success, msg = try_online_revalidation(license_data)
|
|
|
|
|
|
if success:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
# 更新缓存中的验证时间(使用软件ID)
|
|
|
|
|
|
update_success = cache.update_last_validated(SOFTWARE_ID, machine_code)
|
|
|
|
|
|
if update_success:
|
|
|
|
|
|
safe_print(f"[DEBUG] 状态刷新成功: {msg}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
safe_print(f"[WARNING] 更新验证时间失败")
|
2025-10-23 18:28:10 +08:00
|
|
|
|
else:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[INFO] 状态刷新失败(不影响使用): {msg}")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 即使失败也继续使用(网络问题不影响已激活用户)
|
2025-10-28 13:18:45 +08:00
|
|
|
|
else:
|
|
|
|
|
|
safe_print("[DEBUG] 无需联网刷新(距上次验证不足7天)")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 6. 离线验证
|
|
|
|
|
|
success, msg, info = validate_license_offline(license_data)
|
|
|
|
|
|
if not success:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[ERROR] 离线验证失败: {msg}")
|
|
|
|
|
|
cache.clear_license(SOFTWARE_ID, machine_code)
|
2025-10-25 17:49:09 +08:00
|
|
|
|
show_enhanced_activation_dialog(message=msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"[INFO] 离线验证成功: {msg}")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 7. 显示到期提醒(如果接近到期)
|
|
|
|
|
|
if remaining_days <= 7:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"显示到期提醒(剩余{remaining_days}天)")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
show_expiry_reminder(remaining_days, license_data['end_time'])
|
|
|
|
|
|
|
|
|
|
|
|
# 8. 验证通过,启动程序
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("启动程序...")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
ok, err = extract_and_run()
|
|
|
|
|
|
if ok:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print("程序启动成功")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
else:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"程序启动失败: {err}")
|
2025-10-25 17:49:09 +08:00
|
|
|
|
messagebox.showerror("错误", f"程序启动失败:\n{err}")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-10-28 13:18:45 +08:00
|
|
|
|
safe_print(f"验证过程出错: {e}")
|
|
|
|
|
|
try:
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
2025-10-25 17:49:09 +08:00
|
|
|
|
messagebox.showerror("错误", f"验证过程出错:\n{str(e)}")
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 主函数 ==========
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""主函数"""
|
2025-10-28 13:18:45 +08:00
|
|
|
|
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)
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
2025-10-25 17:49:09 +08:00
|
|
|
|
# 使用智能验证策略
|
|
|
|
|
|
smart_validate()
|
2025-10-23 18:28:10 +08:00
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|
|
|
|
|
|
|