feat: 优化硬件ID生成方案,增加计算机名作为备选方案,确保ID生成的稳定性 v3.5.2

This commit is contained in:
huangzhenpc
2025-02-14 17:35:27 +08:00
parent b11452aea8
commit 26e159d71c
5 changed files with 337 additions and 250 deletions

View File

@@ -13,7 +13,12 @@ from pathlib import Path
from utils.config import Config from utils.config import Config
from utils.cursor_registry import CursorRegistry from utils.cursor_registry import CursorRegistry
from cursor_auth_manager import CursorAuthManager from cursor_auth_manager import CursorAuthManager
from utils.cursor_resetter import CursorResetter # 添加导入 from utils.cursor_resetter import CursorResetter
from datetime import datetime
# 添加缓存文件路径常量
CACHE_DIR = Path(os.path.expanduser("~")) / ".cursor_cache"
HARDWARE_ID_CACHE = CACHE_DIR / "hardware_id.json"
def is_admin() -> bool: def is_admin() -> bool:
"""检查是否具有管理员权限 """检查是否具有管理员权限
@@ -56,7 +61,11 @@ def run_as_admin():
return False return False
def get_hardware_id() -> str: def get_hardware_id() -> str:
"""获取硬件唯一标识""" """获取硬件唯一标识
方案1: CPU ID + 主板序列号 + BIOS序列号
方案2: 系统盘序列号 + Windows安装时间
方案3: 计算机名(最后的备选方案)
"""
try: try:
# 创建startupinfo对象来隐藏命令行窗口 # 创建startupinfo对象来隐藏命令行窗口
startupinfo = None startupinfo = None
@@ -64,26 +73,70 @@ def get_hardware_id() -> str:
startupinfo = subprocess.STARTUPINFO() startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE startupinfo.wShowWindow = subprocess.SW_HIDE
# 获取CPU信息 # 方案1: 尝试获取硬件信息
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode() try:
cpu_id = cpu_info.split('\n')[1].strip() # 获取CPU ID
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
# 获取主板序列号 cpu_id = cpu_info.split('\n')[1].strip()
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip() # 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
# 获取BIOS序列号 board_id = board_info.split('\n')[1].strip()
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip() # 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
# 组合信息并生成哈希 bios_id = bios_info.split('\n')[1].strip()
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest() # 如果所有信息都获取成功且有效
if all([cpu_id, board_id, bios_id]) and not all(x in ['', '0', 'None', 'To be filled by O.E.M.'] for x in [cpu_id, board_id, bios_id]):
combined = f"{cpu_id}:{board_id}:{bios_id}"
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用硬件信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案1失败: {str(e)}")
# 方案2: 系统盘序列号 + Windows安装时间
try:
backup_info = []
# 获取系统盘序列号
volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode()
volume_serial = volume_info.split('\n')[1].strip()
if volume_serial and volume_serial not in ['', '0']:
backup_info.append(("volume", volume_serial))
# 获取Windows安装时间
os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode()
install_date = os_info.split('\n')[1].strip()
if install_date:
backup_info.append(("install", install_date))
if backup_info:
combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info))
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用系统信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案2失败: {str(e)}")
# 方案3: 使用计算机名(最后的备选方案)
import platform
computer_name = platform.node()
if computer_name:
hardware_id = hashlib.md5(computer_name.encode()).hexdigest()
logging.info("使用计算机名生成ID成功")
return hardware_id
raise ValueError("无法获取任何可用信息来生成硬件ID")
except Exception as e: except Exception as e:
logging.error(f"获取硬件ID失败: {str(e)}") error_msg = f"生成硬件ID失败: {str(e)}"
# 如果获取失败使用UUID作为备选方案 logging.error(error_msg)
return str(uuid.uuid4()) raise RuntimeError(error_msg)
class AccountSwitcher: class AccountSwitcher:
def __init__(self): def __init__(self):
@@ -101,9 +154,9 @@ class AccountSwitcher:
self.package_json = self.app_path / "package.json" self.package_json = self.app_path / "package.json"
self.auth_manager = CursorAuthManager() self.auth_manager = CursorAuthManager()
self.config = Config() self.config = Config()
self.hardware_id = self.get_hardware_id() # 先获取硬件ID self.hardware_id = get_hardware_id() # 使用新的硬件ID获取函数
self.registry = CursorRegistry() # 添加注册表操作工具类 self.registry = CursorRegistry()
self.resetter = CursorResetter() # 添加重置工具类 self.resetter = CursorResetter()
self.max_retries = 5 self.max_retries = 5
self.wait_time = 1 self.wait_time = 1
@@ -111,33 +164,7 @@ class AccountSwitcher:
def get_hardware_id(self) -> str: def get_hardware_id(self) -> str:
"""获取硬件唯一标识""" """获取硬件唯一标识"""
try: return get_hardware_id() # 使用全局函数
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 获取CPU信息
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
# 组合信息并生成哈希
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest()
except Exception as e:
logging.error(f"获取硬件ID失败: {str(e)}")
# 如果获取失败使用UUID作为备选方案
return str(uuid.uuid4())
def get_cursor_version(self) -> str: def get_cursor_version(self) -> str:
"""获取Cursor版本号""" """获取Cursor版本号"""
@@ -387,24 +414,40 @@ class AccountSwitcher:
shell=True shell=True
) )
# 等待进程关闭 # 等待进程关闭,增加重试次数和等待时间
retry_count = 0 retry_count = 0
while retry_count < self.max_retries: max_retries = 10 # 增加最大重试次数
if not self.get_process_details("Cursor.exe"): wait_time = 2 # 增加每次等待时间
while retry_count < max_retries:
remaining_processes = self.get_process_details("Cursor.exe")
if not remaining_processes:
logging.info("所有Cursor进程已关闭") logging.info("所有Cursor进程已关闭")
# 额外等待一段时间确保系统资源完全释放
time.sleep(2)
return True return True
retry_count += 1 retry_count += 1
if retry_count >= self.max_retries: if retry_count >= max_retries:
processes = self.get_process_details("Cursor.exe") processes = self.get_process_details("Cursor.exe")
if processes: if processes:
logging.error(f"无法关闭以下进程:") logging.error(f"无法关闭以下进程:")
for p in processes: for p in processes:
logging.error(f"PID={p['pid']}, 路径={p['name']}") logging.error(f"PID={p['pid']}, 路径={p['name']}")
# 最后一次尝试强制结束
try:
subprocess.run(
"taskkill /f /im Cursor.exe /t >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
except:
pass
return False return False
logging.warning(f"等待进程关闭, 尝试 {retry_count}/{self.max_retries}...") logging.warning(f"等待进程关闭, 尝试 {retry_count}/{max_retries}...")
time.sleep(self.wait_time) time.sleep(wait_time)
return True return True
else: else:
@@ -456,61 +499,74 @@ class AccountSwitcher:
logging.error("无法关闭Cursor进程") logging.error("无法关闭Cursor进程")
return False return False
# 等待进程完全关闭 # 等待系统资源释放
time.sleep(2) time.sleep(3)
# 启动Cursor # 启动Cursor
if sys.platform == "win32": if sys.platform == "win32":
cursor_exe = self.cursor_path / "Cursor.exe" cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists(): if cursor_exe.exists():
try: max_retries = 3
# 使用subprocess启动 for attempt in range(max_retries):
startupinfo = subprocess.STARTUPINFO() try:
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # 使用subprocess启动
startupinfo = subprocess.STARTUPINFO()
subprocess.Popen( startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
str(cursor_exe),
startupinfo=startupinfo, subprocess.Popen(
creationflags=subprocess.CREATE_NEW_CONSOLE str(cursor_exe),
) startupinfo=startupinfo,
creationflags=subprocess.CREATE_NEW_CONSOLE
# 等待进程启动 )
time.sleep(3)
# 等待进程启动
# 验证进程是否启动 time.sleep(5) # 增加等待时间
processes = self.get_process_details("Cursor.exe")
if processes: # 验证进程是否启动
logging.info("Cursor启动成功") processes = self.get_process_details("Cursor.exe")
return True if processes:
else: logging.info("Cursor启动成功")
logging.error("Cursor进程未找到") return True
# 尝试使用 os.startfile 作为备选方案 else:
if attempt < max_retries - 1:
logging.warning(f"启动尝试 {attempt + 1} 失败,准备重试...")
time.sleep(3)
continue
logging.error("Cursor进程未找到")
# 尝试使用 os.startfile 作为备选方案
try:
os.startfile(str(cursor_exe))
time.sleep(5)
logging.info("使用备选方案启动Cursor")
return True
except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}")
return False
except Exception as e:
if attempt < max_retries - 1:
logging.warning(f"启动尝试 {attempt + 1} 失败: {str(e)},准备重试...")
time.sleep(3)
continue
logging.error(f"启动Cursor失败: {str(e)}")
# 尝试使用 os.startfile 作为最后的备选方案
try: try:
os.startfile(str(cursor_exe)) os.startfile(str(cursor_exe))
time.sleep(3) time.sleep(5)
logging.info("使用备选方案启动Cursor") logging.info("使用备选方案启动Cursor")
return True return True
except Exception as e: except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}") logging.error(f"备选启动方案失败: {str(e)}")
return False return False
except Exception as e:
logging.error(f"启动Cursor失败: {str(e)}")
# 尝试使用 os.startfile 作为备选方案
try:
os.startfile(str(cursor_exe))
time.sleep(3)
logging.info("使用备选方案启动Cursor")
return True
except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}")
return False
else: else:
logging.error(f"未找到Cursor程序: {cursor_exe}") logging.error(f"未找到Cursor程序: {cursor_exe}")
return False return False
elif sys.platform == "darwin": elif sys.platform == "darwin":
try: try:
subprocess.run("open -a Cursor", shell=True, check=True) subprocess.run("open -a Cursor", shell=True, check=True)
time.sleep(5)
logging.info("Cursor启动成功") logging.info("Cursor启动成功")
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@@ -519,6 +575,7 @@ class AccountSwitcher:
elif sys.platform == "linux": elif sys.platform == "linux":
try: try:
subprocess.run("cursor &", shell=True, check=True) subprocess.run("cursor &", shell=True, check=True)
time.sleep(5)
logging.info("Cursor启动成功") logging.info("Cursor启动成功")
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
@@ -529,16 +586,6 @@ class AccountSwitcher:
except Exception as e: except Exception as e:
logging.error(f"重启Cursor失败: {str(e)}") logging.error(f"重启Cursor失败: {str(e)}")
# 尝试使用 os.startfile 作为最后的备选方案
try:
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
time.sleep(3)
logging.info("使用最终备选方案启动Cursor")
return True
except:
pass
return False return False
def activate_and_switch(self, activation_code: str) -> Tuple[bool, str]: def activate_and_switch(self, activation_code: str) -> Tuple[bool, str]:

View File

@@ -1,145 +0,0 @@
import sys
from pathlib import Path
import logging
import os
from PIL import Image
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QFrame, QTextEdit,
QMessageBox, QApplication, QSystemTrayIcon, QMenu,
QDialog, QProgressBar, QStyle)
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap
import time
import requests
from urllib.parse import quote
import subprocess
sys.path.append(str(Path(__file__).parent.parent))
from utils.config import Config
from utils.version_manager import VersionManager
from account_switcher import AccountSwitcher
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.config = Config()
self.switcher = AccountSwitcher()
self.version_manager = VersionManager()
# 添加激活状态缓存
self._activation_status = None # 缓存的激活状态
self._status_timer = None # 状态更新定时器
# 添加心跳定时器
self._heartbeat_timer = QTimer()
self._heartbeat_timer.timeout.connect(self.send_heartbeat)
self._heartbeat_timer.start(5 * 60 * 1000) # 每5分钟发送一次心跳
# 添加请求锁,防止重复提交
self._is_requesting = False
self._last_request_time = 0
self._request_cooldown = 2 # 请求冷却时间(秒)
version = get_version()
cursor_version = self.switcher.get_cursor_version()
self.setWindowTitle(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})")
self.setMinimumSize(600, 680) # 增加最小宽度到600
# 设置窗口图标
icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "icon", "two.ico")
if os.path.exists(icon_path):
self.window_icon = QIcon(icon_path)
if not self.window_icon.isNull():
self.setWindowIcon(self.window_icon)
logging.info(f"成功设置窗口图标: {icon_path}")
else:
logging.warning("图标文件加载失败")
# 创建系统托盘图标
self.create_tray_icon()
# 创建主窗口部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 设置主窗口样式
central_widget.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #f8f9fa,
stop:0.5 #ffffff,
stop:1 #f8f9fa);
}
QFrame {
border: none;
background: transparent;
}
""")
# 创建主布局
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(12) # 减小区域间距
main_layout.setContentsMargins(20, 15, 20, 15) # 调整外边距
# 启动时检查一次状态
QTimer.singleShot(0, self.check_status)
# 启动时自动检查更新
QTimer.singleShot(1000, self.check_for_updates)
def send_heartbeat(self):
"""发送心跳请求"""
if not self._check_request_throttle():
return
def heartbeat_func():
return self.switcher.send_heartbeat()
# 创建工作线程
self.heartbeat_worker = ApiWorker(heartbeat_func)
self.heartbeat_worker.finished.connect(self.on_heartbeat_complete)
self.heartbeat_worker.start()
def on_heartbeat_complete(self, result):
"""心跳完成回调"""
success, message = result
self._request_complete()
if success:
logging.info(f"心跳发送成功: {message}")
# 更新状态显示
self.check_status()
else:
logging.error(f"心跳发送失败: {message}")
def closeEvent(self, event):
"""窗口关闭事件"""
try:
if hasattr(self, 'tray_icon') and self.tray_icon.isVisible():
event.ignore()
self.hide()
# 确保托盘图标显示
self.tray_icon.show()
self.tray_icon.showMessage(
"听泉Cursor助手",
"程序已最小化到系统托盘",
QSystemTrayIcon.Information,
2000
)
else:
# 如果托盘图标不可用,则正常退出
if self._status_timer:
self._status_timer.stop()
if hasattr(self, '_heartbeat_timer'):
self._heartbeat_timer.stop()
event.accept()
except Exception as e:
logging.error(f"处理关闭事件时发生错误: {str(e)}")
# 发生错误时,接受关闭事件
if self._status_timer:
self._status_timer.stop()
if hasattr(self, '_heartbeat_timer'):
self._heartbeat_timer.stop()
event.accept()

54
test_computer_name.py Normal file
View File

@@ -0,0 +1,54 @@
import platform
import hashlib
import time
def get_computer_name_id():
"""使用计算机名生成ID(方案三)"""
try:
computer_name = platform.node()
if computer_name:
print(f"\n计算机名: {computer_name}")
hardware_id = hashlib.md5(computer_name.encode()).hexdigest()
return hardware_id
return None
except Exception as e:
print(f"获取计算机名失败: {str(e)}")
return None
def test_stability():
"""测试ID的稳定性"""
print("开始测试计算机名生成ID的稳定性...")
print("将进行10次测试,每次间隔1秒")
# 存储每次生成的ID
computer_ids = []
for i in range(10):
print(f"\n{i+1} 次测试:")
# 测试计算机名方案
comp_id = get_computer_name_id()
if comp_id:
print(f"使用计算机名生成的ID: {comp_id}")
computer_ids.append(comp_id)
time.sleep(1)
# 分析结果
print("\n测试结果分析:")
if computer_ids:
unique_ids = set(computer_ids)
print(f"\n计算机名方案:")
print(f"生成次数: {len(computer_ids)}")
print(f"唯一ID数: {len(unique_ids)}")
print("是否稳定: " + ("" if len(unique_ids) == 1 else ""))
if len(unique_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_ids):
print(f"{idx+1}. {id}")
else:
print("\n计算机名方案: 获取失败")
if __name__ == "__main__":
test_stability()

131
test_hardware_id.py Normal file
View File

@@ -0,0 +1,131 @@
import subprocess
import hashlib
import logging
import sys
import time
def get_hardware_info():
"""获取硬件信息(方案一)"""
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
try:
# 获取CPU ID
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
if all([cpu_id, board_id, bios_id]):
print(f"\n方案一硬件信息:")
print(f"CPU ID: {cpu_id}")
print(f"主板序列号: {board_id}")
print(f"BIOS序列号: {bios_id}")
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest()
except Exception as e:
print(f"方案一获取失败: {str(e)}")
return None
def get_system_info():
"""获取系统信息(方案二)"""
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
backup_info = []
try:
# 获取系统盘序列号
volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode()
volume_serial = volume_info.split('\n')[1].strip()
if volume_serial:
backup_info.append(("volume", volume_serial))
print(f"\n系统盘序列号: {volume_serial}")
except Exception as e:
print(f"获取系统盘序列号失败: {str(e)}")
try:
# 获取Windows安装时间
os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode()
install_date = os_info.split('\n')[1].strip()
if install_date:
backup_info.append(("install", install_date))
print(f"Windows安装时间: {install_date}")
except Exception as e:
print(f"获取系统安装时间失败: {str(e)}")
if backup_info:
combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info))
return hashlib.md5(combined.encode()).hexdigest()
return None
def test_stability():
"""测试ID的稳定性"""
print("开始测试硬件ID稳定性...")
print("将进行10次测试,每次间隔1秒")
# 存储每次生成的ID
hardware_ids = []
system_ids = []
for i in range(10):
print(f"\n{i+1} 次测试:")
# 测试方案一
hw_id = get_hardware_info()
if hw_id:
print(f"方案一(硬件信息)生成的ID: {hw_id}")
hardware_ids.append(hw_id)
# 测试方案二
sys_id = get_system_info()
if sys_id:
print(f"方案二(系统信息)生成的ID: {sys_id}")
system_ids.append(sys_id)
time.sleep(1)
# 分析结果
print("\n测试结果分析:")
if hardware_ids:
unique_hw_ids = set(hardware_ids)
print(f"\n方案一(硬件信息):")
print(f"生成次数: {len(hardware_ids)}")
print(f"唯一ID数: {len(unique_hw_ids)}")
print("是否稳定: " + ("" if len(unique_hw_ids) == 1 else ""))
if len(unique_hw_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_hw_ids):
print(f"{idx+1}. {id}")
else:
print("\n方案一(硬件信息): 获取失败")
if system_ids:
unique_sys_ids = set(system_ids)
print(f"\n方案二(系统信息):")
print(f"生成次数: {len(system_ids)}")
print(f"唯一ID数: {len(unique_sys_ids)}")
print("是否稳定: " + ("" if len(unique_sys_ids) == 1 else ""))
if len(unique_sys_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_sys_ids):
print(f"{idx+1}. {id}")
else:
print("\n方案二(系统信息): 获取失败")
if __name__ == "__main__":
test_stability()

View File

@@ -1 +1 @@
3.5.0 3.5.2