feat: 发布 v3.5.0 版本

This commit is contained in:
huangzhenpc
2025-02-14 16:15:04 +08:00
parent 8b2fbef54a
commit b11452aea8
7 changed files with 369 additions and 164 deletions

138
build.bat
View File

@@ -1,65 +1,119 @@
@echo off
chcp 65001
echo 开始打包流程...
setlocal EnableDelayedExpansion
echo 开始正式版本打包...
:: 设置工作目录为脚本所在目录
cd /d "%~dp0"
:: 激活虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 激活虚拟环境...
call venv\Scripts\activate.bat
) else (
echo 警告: 未找到虚拟环境,使用系统 Python
)
:: 确保安装了必要的包
echo 检查依赖包...
pip install -r requirements.txt
:: 更新版本号
python update_version.py
:: 读取版本号
set /p VERSION=<version.txt
echo 当前版本: %VERSION%
echo 当前版本: !VERSION!
:: 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
:: 提取主版本号和次版本号 (3.4.7 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("!VERSION!") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
echo 主版本目录: !MAJOR_VERSION!
:: 创建版本目录
set VERSION_DIR=dist\%MAJOR_VERSION%
if not exist "%VERSION_DIR%" (
mkdir "%VERSION_DIR%"
echo 创建目录: %VERSION_DIR%
set VERSION_DIR=dist\!MAJOR_VERSION!
if not exist "!VERSION_DIR!" (
mkdir "!VERSION_DIR!"
echo 创建目录: !VERSION_DIR!
)
:: 使用新的spec文件进行打包
pyinstaller --noconfirm build_nezha.spec
:: 清理 Python 缓存文件
echo 清理Python缓存文件...
for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d"
del /s /q *.pyc >nul 2>&1
del /s /q *.pyo >nul 2>&1
:: 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
:: 清理旧的打包文件
echo 清理旧文件...
if exist "build" rd /s /q "build"
if exist "*.spec" del /f /q "*.spec"
:: 使用优化选项进行打包
echo 开始打包...
pyinstaller ^
--noconfirm ^
--clean ^
--onefile ^
--noconsole ^
--icon=icon/two.ico ^
--name "听泉cursor助手!VERSION!" ^
--add-data "icon;icon" ^
--add-data "version.txt;." ^
--add-data "testversion.txt;." ^
--add-data "requirements.txt;." ^
--exclude-module _tkinter ^
--exclude-module tkinter ^
--exclude-module PIL.ImageTk ^
--exclude-module PIL.ImageWin ^
--exclude-module numpy ^
--exclude-module pandas ^
--exclude-module matplotlib ^
--exclude "__pycache__" ^
--exclude "*.pyc" ^
--exclude "*.pyo" ^
--exclude "*.pyd" ^
main.py
:: 检查打包结果并移动文件
set TEMP_FILE=dist\听泉cursor助手!VERSION!.exe
set TARGET_FILE=!VERSION_DIR!\听泉cursor助手v!VERSION!.exe
echo 检查文件: !TEMP_FILE!
if exist "!TEMP_FILE!" (
echo 正式版本打包成功!
:: 移动到版本目录
echo 移动文件到: !TARGET_FILE!
move "!TEMP_FILE!" "!TARGET_FILE!"
:: 显示文件大小
for %%I in ("!TARGET_FILE!") do (
echo 文件大小: %%~zI 字节
)
echo.
echo 正式版本构建完成!
echo 版本号: v!VERSION!
echo 文件位置: !TARGET_FILE!
) else (
echo 错误: 打包失败,文件不存在
echo 预期文件路径: !TEMP_FILE!
dir /b dist
pause
exit /b 1
)
:: 检查目标目录是否存在
echo 检查目标目录: %VERSION_DIR%
if not exist "%VERSION_DIR%" (
echo 错误: 目标目录不存在
pause
exit /b 1
:: 清理临时文件
echo 清理临时文件...
if exist "build" rd /s /q "build"
if exist "dist\听泉cursor助手!VERSION!.exe" del /f /q "dist\听泉cursor助手!VERSION!.exe"
if exist "*.spec" del /f /q "*.spec"
:: 退出虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 退出虚拟环境...
deactivate
)
:: 移动文件到版本目录
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
move "dist\听泉cursor助手%VERSION%.exe" "%VERSION_DIR%\听泉cursor助手v%VERSION%.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: %VERSION_DIR%
echo 3. 目标文件是否已存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
dir /b dist
dir /b "%VERSION_DIR%"
pause
exit /b 1
)
echo.
echo 打包完成!
echo 文件保存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
endlocal
pause

View File

@@ -1,70 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
import os
import sys
from PyInstaller.utils.hooks import collect_all
def get_version():
with open('version.txt', 'r', encoding='utf-8') as f:
version = f.read().strip()
return version
version = get_version()
# 收集所有需要的依赖
datas = [('icon', 'icon'), ('version.txt', '.')]
binaries = []
hiddenimports = [
'win32gui', 'win32con', 'win32process', 'psutil', # Windows API 相关
'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', # GUI 相关
'PyQt5.sip', # PyQt5 必需
'PyQt5.QtNetwork', # 网络相关
'PIL', # Pillow 相关
'PIL._imaging', # Pillow 核心
'requests', 'urllib3', 'certifi', # 网络请求相关
'json', 'uuid', 'hashlib', 'logging', # 基础功能相关
'importlib', # 导入相关
'pkg_resources', # 包资源
]
# 主要的分析对象
a = Analysis(
['main.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['_tkinter', 'tkinter', 'Tkinter'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
module_collection_mode={'PyQt5': 'pyz+py'},
)
# 创建PYZ
pyz = PYZ(a.pure)
# 创建EXE
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name=f'听泉cursor助手{version}',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['icon\\two.ico'],
)

145
gui/main_window_new.py Normal file
View File

@@ -0,0 +1,145 @@
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()

29
main.py
View File

@@ -7,6 +7,9 @@ import shutil
import tempfile
import urllib3
import ctypes
import win32event
import win32api
import winerror
from pathlib import Path
from PyQt5.QtWidgets import QApplication, QMessageBox, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon
@@ -18,6 +21,28 @@ from account_switcher import AccountSwitcher
urllib3.disable_warnings()
logging.getLogger('urllib3').setLevel(logging.ERROR)
def prevent_multiple_instances():
"""防止程序多开
Returns:
bool: 如果是第一个实例返回True否则返回False
"""
try:
# 创建一个唯一的互斥锁名称
mutex_name = "Global\\CursorHelper_SingleInstance_Lock"
# 尝试创建互斥锁
handle = win32event.CreateMutex(None, 1, mutex_name)
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
# 互斥锁已存在,说明程序已经在运行
logging.warning("程序已经在运行")
QMessageBox.warning(None, "警告", "程序已经在运行!\n请不要重复打开程序。")
return False
return True
except Exception as e:
logging.error(f"检查程序实例失败: {str(e)}")
return True # 如果检查失败,允许程序运行
def cleanup_temp():
"""清理临时文件"""
try:
@@ -93,6 +118,10 @@ def print_banner():
def main():
"""主函数"""
try:
# 0. 检查是否已有实例在运行
if not prevent_multiple_instances():
return 1
# 1. 首先检查管理员权限
if not is_admin():
if run_as_admin():

View File

@@ -1,84 +1,123 @@
@echo off
chcp 65001 >nul
chcp 65001
setlocal EnableDelayedExpansion
echo 开始测试打包...
REM 激活虚拟环境
call venv\Scripts\activate.bat
:: 设置工作目录为脚本所在目录
cd /d "%~dp0"
REM 确保安装了必要的包
:: 激活虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 激活虚拟环境...
call venv\Scripts\activate.bat
) else (
echo 警告: 未找到虚拟环境,使用系统 Python
)
:: 确保安装了必要的包
echo 检查依赖包...
pip install -r requirements.txt
REM 读取当前版本号
:: 读取版本号
set /p VERSION=<version.txt
echo 当前正式版本: %VERSION%
echo 当前版本: !VERSION!
REM 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
:: 提取主版本号和次版本号 (3.4.7 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("!VERSION!") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
echo 主版本目录: !MAJOR_VERSION!
REM 读取测试版本号(如果存在)
:: 读取测试版本号(如果存在)
if exist testversion.txt (
set /p TEST_VERSION=<testversion.txt
) else (
set TEST_VERSION=0
)
REM 增加测试版本号
:: 增加测试版本号
set /a TEST_VERSION+=1
echo !TEST_VERSION!>testversion.txt
echo 测试版本号: !TEST_VERSION!
REM 组合完整版本号
set FULL_VERSION=%VERSION%.!TEST_VERSION!
:: 组合完整版本号
set FULL_VERSION=!VERSION!.!TEST_VERSION!
echo 完整版本号: !FULL_VERSION!
REM 创建测试版本输出目录
set TEST_DIR=dist\test\%MAJOR_VERSION%
:: 创建测试版本目录
set TEST_DIR=dist\test\!MAJOR_VERSION!
if not exist "!TEST_DIR!" (
mkdir "!TEST_DIR!"
echo 创建目录: !TEST_DIR!
)
REM 清理旧文件
if exist "dist\听泉cursor助手%VERSION%.exe" del "dist\听泉cursor助手%VERSION%.exe"
if exist "build" rmdir /s /q "build"
:: 清理 Python 缓存文件
echo 清理Python缓存文件...
for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d"
del /s /q *.pyc >nul 2>&1
del /s /q *.pyo >nul 2>&1
REM 执行打包
venv\Scripts\python.exe -m PyInstaller build_nezha.spec --clean
:: 清理旧的打包文件
echo 清理旧文件...
if exist "build" rd /s /q "build"
if exist "*.spec" del /f /q "*.spec"
REM 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
dir /b dist
pause
exit /b 1
:: 使用优化选项进行打包
echo 开始打包...
pyinstaller ^
--noconfirm ^
--clean ^
--onefile ^
--noconsole ^
--icon=icon/two.ico ^
--name "听泉cursor助手_test" ^
--add-data "icon;icon" ^
--add-data "version.txt;." ^
--add-data "testversion.txt;." ^
--add-data "requirements.txt;." ^
--exclude-module _tkinter ^
--exclude-module tkinter ^
--exclude-module PIL.ImageTk ^
--exclude-module PIL.ImageWin ^
--exclude-module numpy ^
--exclude-module pandas ^
--exclude-module matplotlib ^
--exclude "__pycache__" ^
--exclude "*.pyc" ^
--exclude "*.pyo" ^
--exclude "*.pyd" ^
main.py
:: 检查打包结果并移动文件
set TEMP_FILE=dist\听泉cursor助手_test.exe
set TARGET_FILE=!TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
echo 检查文件: !TEMP_FILE!
if exist "!TEMP_FILE!" (
echo 测试打包成功!
:: 移动到版本目录
echo 移动文件到: !TARGET_FILE!
move "!TEMP_FILE!" "!TARGET_FILE!"
:: 显示文件大小
for %%I in ("!TARGET_FILE!") do (
echo 文件大小: %%~zI 字节
)
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: !TARGET_FILE!
) else (
echo 错误: 打包失败,文件不存在
)
REM 移动并重命名文件
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
move "dist\听泉cursor助手%VERSION%.exe" "!TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: !TEST_DIR!
echo 3. 目标文件是否已存在: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
dir /b dist
dir /b "!TEST_DIR!"
pause
exit /b 1
:: 退出虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 退出虚拟环境...
deactivate
)
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
REM 退出虚拟环境
deactivate
endlocal
pause

View File

@@ -29,7 +29,15 @@ class VersionManager:
def _get_current_version(self) -> str:
"""获取当前版本号"""
try:
version_file = self.root_path / "version.txt"
# 首先尝试从打包后的路径读取
if getattr(sys, 'frozen', False):
# 如果是打包后的程序
base_path = Path(sys._MEIPASS)
else:
# 如果是开发环境
base_path = self.root_path
version_file = base_path / "version.txt"
if not version_file.exists():
logging.error(f"版本文件不存在: {version_file}")
return "0.0.0"

View File

@@ -1 +1 @@
3.4.7
3.5.0