Todo: 修改因exe_token错误导致的不断重连问题

Todo: 修改打包中.bat打包文件与测试打包脚本不一致问题
New: 新增installer安装包环境搭建数据
This commit is contained in:
2025-09-28 17:00:02 +08:00
parent 7f9894908d
commit c58cec750f
7 changed files with 640 additions and 55 deletions

View File

@@ -29,6 +29,7 @@ class BackendClient:
self.error_callback: Optional[Callable] = None
self.login_callback: Optional[Callable] = None # 新增平台登录下发cookies回调
self.success_callback: Optional[Callable] = None # 新增:后端连接成功回调
self.token_error_callback: Optional[Callable] = None # 新增token错误回调
self.is_connected = False
@@ -261,7 +262,8 @@ class BackendClient:
close: Callable = None,
error: Callable = None,
login: Callable = None,
success: Callable = None):
success: Callable = None,
token_error: Callable = None):
"""设置各种消息类型的回调函数"""
if store_list:
self.store_list_callback = store_list
@@ -279,6 +281,8 @@ class BackendClient:
self.login_callback = login
if success:
self.success_callback = success
if token_error:
self.token_error_callback = token_error
def on_connected(self):
"""连接成功时的处理"""
@@ -338,7 +342,7 @@ class BackendClient:
active_store_id = None
try:
from Utils.JD.JdUtils import WebsocketManager as JdManager
from Utils.Dy.DyUtils import WebsocketManager as DyManager
from Utils.Dy.DyUtils import DouYinWebsocketManager as DyManager
from Utils.Pdd.PddUtils import WebsocketManager as PddManager
# 检查各平台是否有活跃连接
@@ -429,6 +433,8 @@ class BackendClient:
self._handle_login(message)
elif msg_type == 'error':
self._handle_error_message(message)
elif msg_type == 'error_token':
self._handle_token_error(message)
elif msg_type == 'staff_list':
self._handle_staff_list(message)
else:
@@ -1163,10 +1169,35 @@ class BackendClient:
def _handle_error_message(self, message: Dict[str, Any]):
"""处理错误消息"""
error_msg = message.get('error', '未知错误')
content = message.get('content', '')
# 检查是否为token错误无论type是error还是error_token
if content == "无效的exe_token" or "无效的exe_token" in content:
print(f"[错误] 检测到token错误: {content}")
self._handle_token_error(message)
return
print(f"后端连接错误: {error_msg}")
if self.error_callback:
self.error_callback(error_msg, message)
def _handle_token_error(self, message: Dict[str, Any]):
"""处理token错误消息 - 无效token时停止重连并显示错误"""
error_content = message.get('content', '无效的exe_token')
print(f"[错误] Token验证失败: {error_content}")
# 停止重连机制
self.should_stop = True
self.is_reconnecting = False
# 触发token错误回调
if self.token_error_callback:
self.token_error_callback(error_content)
# 主动关闭连接
if self.websocket:
asyncio.run_coroutine_threadsafe(self.websocket.close(), self.loop)
def _handle_staff_list(self, message: Dict[str, Any]):
"""处理客服列表更新消息"""
staff_list = message.get('data', {}).get('staff_list', [])

View File

@@ -35,10 +35,11 @@ class WebSocketManager:
'success': None,
'error': None,
'platform_connected': None,
'token_error': None,
}
def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None,
platform_connected: Callable = None): # ← 新增参数
platform_connected: Callable = None, token_error: Callable = None):
"""设置回调函数"""
if log:
self.callbacks['log'] = log
@@ -48,6 +49,8 @@ class WebSocketManager:
self.callbacks['error'] = error
if platform_connected: # ← 新增
self.callbacks['platform_connected'] = platform_connected
if token_error:
self.callbacks['token_error'] = token_error
def _log(self, message: str, level: str = "INFO"):
"""内部日志方法"""
@@ -84,33 +87,49 @@ class WebSocketManager:
backend = get_backend_client()
if backend:
# 3 如果有客户端更新token并重连
backend.set_token(token)
# 检查现有客户端是否因token错误而停止
if backend.should_stop:
self._log("检测到客户端因token错误已停止创建新的客户端", "INFO")
# 断开旧客户端
backend.disconnect()
# 清除旧客户端引用
set_backend_client(None)
backend = None
else:
# 3 如果有客户端更新token并重连
backend.set_token(token)
# 设置回调函数
def _on_backend_success():
try:
self._log("连接服务成功", "SUCCESS")
if self.callbacks['success']:
self.callbacks['success']()
except Exception as e:
self._log(f"成功回调执行失败: {e}", "ERROR")
# 设置回调函数
def _on_backend_success():
try:
self._log("连接服务成功", "SUCCESS")
if self.callbacks['success']:
self.callbacks['success']()
except Exception as e:
self._log(f"成功回调执行失败: {e}", "ERROR")
def _on_backend_login(platform_name: str, store_id: str, cookies: str):
self._log(
f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}",
"INFO")
self._handle_platform_login(platform_name, store_id, cookies)
def _on_backend_login(platform_name: str, store_id: str, cookies: str):
self._log(
f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}",
"INFO")
self._handle_platform_login(platform_name, store_id, cookies)
def _on_token_error(error_content: str):
self._log(f"Token验证失败: {error_content}", "ERROR")
if self.callbacks['token_error']:
self.callbacks['token_error'](error_content)
backend.set_callbacks(success=_on_backend_success, login=_on_backend_login)
backend.set_callbacks(success=_on_backend_success, login=_on_backend_login, token_error=_on_token_error)
if not backend.is_connected:
backend.connect()
self.backend_client = backend
self._log("令牌已提交已连接后端。等待后端下发平台cookies后自动连接平台...", "SUCCESS")
return True
if not backend.is_connected:
backend.connect()
self.backend_client = backend
self._log("令牌已提交已连接后端。等待后端下发平台cookies后自动连接平台...", "SUCCESS")
return True
else:
# 如果没有现有客户端或客户端被重置,创建新的
if not backend:
backend = BackendClient.from_exe_token(token)
@@ -128,7 +147,12 @@ class WebSocketManager:
except Exception as e:
self._log(f"成功回调执行失败: {e}", "ERROR")
backend.set_callbacks(login=_on_backend_login, success=_on_backend_success)
def _on_token_error(error_content: str):
self._log(f"Token验证失败: {error_content}", "ERROR")
if self.callbacks['token_error']:
self.callbacks['token_error'](error_content)
backend.set_callbacks(login=_on_backend_login, success=_on_backend_success, token_error=_on_token_error)
backend.connect()
set_backend_client(backend)
@@ -402,6 +426,11 @@ class WebSocketManager:
self._log("🔄 开始执行 start_with_cookies", "DEBUG")
result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=data))
self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG")
# Cookie启动成功时也要通知GUI
if result:
self._log("✅ [PDD] Cookie启动成功平台连接已建立", "SUCCESS")
self._notify_platform_connected("拼多多")
# 根据实际登录结果上报状态给后端
if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]:

View File

@@ -85,9 +85,32 @@ def main():
result = subprocess.run(['python', 'quick_build.py'], capture_output=False)
if result.returncode == 0:
print("\n🎉 生产环境打包成功!")
print("📁 输出目录: dist/MultiPlatformGUI/")
print("🔇 已禁用所有日志功能,客户端不会产生日志文件")
# 确保输出目录正确性
if os.path.exists('dist/main') and not os.path.exists('dist/MultiPlatformGUI'):
print("🔄 修正输出目录名称...")
try:
os.rename('dist/main', 'dist/MultiPlatformGUI')
print("✅ 已重命名为: dist/MultiPlatformGUI/")
except Exception as e:
print(f"⚠️ 重命名失败: {e}")
# 验证最终输出
if os.path.exists('dist/MultiPlatformGUI'):
print("\n🎉 生产环境打包成功!")
print("📁 输出目录: dist/MultiPlatformGUI/")
print("🔇 已禁用所有日志功能,客户端不会产生日志文件")
# 验证关键文件
exe_path = 'dist/MultiPlatformGUI/main.exe'
if os.path.exists(exe_path):
size = os.path.getsize(exe_path) / 1024 / 1024 # MB
print(f"✅ 主程序: main.exe ({size:.1f} MB)")
else:
print("⚠️ 主程序文件未找到")
else:
print("\n❌ 输出目录验证失败")
if os.path.exists('dist/main'):
print("💡 发现: dist/main/ 目录,请手动重命名为 MultiPlatformGUI")
else:
print("\n❌ 打包失败")

106
installer/README.md Normal file
View File

@@ -0,0 +1,106 @@
# GUI 安装包构建工具
这个目录包含了使用 NSIS 构建 Windows 安装包的完整解决方案。
## 目录结构
```
installer/
├── build_installer.py # 主要的构建脚本
├── installer.nsi # 自动生成的 NSIS 脚本
├── assets/ # 安装包资源文件
│ ├── icon.png # 应用程序图标
│ └── license.txt # 软件许可协议
└── output/ # 生成的安装包输出目录
```
## 使用方法
### 方法一:使用批处理脚本(推荐)
```bash
# 在 GUI_main 目录下运行
build_installer.bat
```
### 方法二直接运行Python脚本
```bash
# 在 GUI_main/installer 目录下运行
python build_installer.py
```
## 前提条件
1. **NSIS 安装**
- 下载地址https://nsis.sourceforge.io/Download
- 安装后确保 `makensis` 命令在 PATH 中
2. **应用程序构建**
- 必须先运行 `build_production.py` 构建应用程序
- 确保 `dist/MultiPlatformGUI/` 目录存在
## 构建流程
1. **环境检查**:验证 NSIS 安装和应用程序构建
2. **准备资源**:复制图标文件和创建许可证
3. **生成脚本**:自动生成 NSIS 安装脚本
4. **编译安装包**:使用 makensis 编译最终的安装程序
## 输出结果
安装包将生成在 `installer/output/` 目录下,文件名格式:
```
ShuiDi_AI_Assistant_Setup_1.0.0_YYYYMMDD_HHMMSS.exe
```
## 安装包功能
- ✅ 完整的中文界面
- ✅ 自动创建开始菜单快捷方式
- ✅ 自动创建桌面快捷方式
- ✅ 注册表集成
- ✅ 完整的卸载功能
- ✅ 版本信息显示
## 自定义配置
可以修改 `build_installer.py` 中的以下配置:
```python
# 应用程序信息
self.app_name = "水滴AI客服智能助手"
self.app_name_en = "ShuiDi AI Assistant"
self.app_version = "1.0.0"
self.app_publisher = "水滴科技"
self.app_url = "https://www.shuidi.tech"
```
## 故障排除
### 常见问题
1. **找不到 makensis 命令**
- 确保 NSIS 已正确安装
- 将 NSIS 安装目录添加到 PATH 环境变量
2. **找不到 dist 目录**
- 先运行 `build_production.py` 构建应用程序
3. **图标文件问题**
- 确保 `static/` 目录下有图标文件
- 建议准备 ICO 格式的图标文件
### 日志和调试
构建过程中的详细信息会在控制台显示,包括:
- NSIS 版本信息
- 文件复制状态
- 编译过程输出
- 最终安装包信息
## 进一步优化
可以考虑添加以下功能:
- 数字签名支持
- 多语言支持
- 自定义安装选项
- 更新检查机制

View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
NSIS 安装包构建脚本
用于创建 GUI 应用程序的Windows安装包
"""
import os
import sys
import shutil
import subprocess
import datetime
from pathlib import Path
class NSISInstaller:
def __init__(self):
self.script_dir = Path(__file__).parent
self.project_root = self.script_dir.parent
self.dist_dir = self.project_root / "dist" / "MultiPlatformGUI"
self.output_dir = self.script_dir / "output"
self.assets_dir = self.script_dir / "assets"
self.nsis_script = self.script_dir / "installer.nsi"
# 应用程序信息
self.app_name = "水滴AI客服智能助手"
self.app_name_en = "ShuiDi AI Assistant"
self.app_version = "1.0.0"
self.app_publisher = "水滴智能科技"
self.app_url = "https://shuidrop.com/"
self.exe_name = "main.exe"
def check_prerequisites(self):
"""检查构建前提条件"""
print("🔍 检查构建前提条件...")
# 检查NSIS安装
try:
result = subprocess.run(['makensis', '/VERSION'],
capture_output=True, text=True, check=True)
nsis_version = result.stdout.strip()
print(f"✅ NSIS 版本: {nsis_version}")
except (subprocess.CalledProcessError, FileNotFoundError):
print("❌ 错误: 未找到NSIS或makensis命令")
print(" 请从 https://nsis.sourceforge.io/Download 下载并安装NSIS")
print(" 确保将NSIS添加到系统PATH环境变量")
return False
# 检查dist目录
if not self.dist_dir.exists():
print(f"❌ 错误: 未找到构建输出目录 {self.dist_dir}")
print(" 请先运行 build_production.py 构建应用程序")
return False
# 检查主程序文件
exe_path = self.dist_dir / self.exe_name
if not exe_path.exists():
print(f"❌ 错误: 未找到主程序文件 {exe_path}")
return False
print(f"✅ 找到主程序: {exe_path}")
return True
def prepare_assets(self):
"""准备安装包资源文件"""
print("📁 准备资源文件...")
# 创建assets目录
self.assets_dir.mkdir(exist_ok=True)
# 复制图标文件
icon_sources = [
self.project_root / "static" / "ai_assistant_icon_64.png",
self.project_root / "static" / "ai_assistant_icon_32.png",
self.project_root / "static" / "ai_assistant_icon_16.png"
]
# 寻找可用的图标文件
icon_found = False
for icon_source in icon_sources:
if icon_source.exists():
# 如果有PNG图标我们需要转换为ICO格式
# 这里暂时复制PNG实际项目中建议准备ICO格式图标
icon_dest = self.assets_dir / "icon.ico"
try:
# 尝试使用PIL转换PNG为ICO格式
from PIL import Image
img = Image.open(icon_source)
ico_path = self.assets_dir / "icon.ico"
img.save(ico_path, format='ICO', sizes=[(16,16), (32,32), (48,48), (64,64)])
print(f"✅ 转换图标: {icon_source.name} -> icon.ico")
icon_found = True
break
except ImportError:
print("⚠️ 未安装PIL库跳过图标转换")
break
except Exception as e:
print(f"⚠️ 图标转换失败: {e}")
continue
if not icon_found:
print("⚠️ 未找到图标文件,将使用默认图标")
return icon_found
def generate_nsis_script(self, has_icon=False):
"""生成NSIS安装脚本"""
print("📝 生成NSIS安装脚本...")
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
installer_name = f"{self.app_name_en}_Setup_{self.app_version}_{timestamp}.exe"
nsis_content = f'''# 水滴AI客服智能助手 NSIS 安装脚本
# 自动生成于 {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
Unicode True
# 定义应用程序信息
!define APP_NAME "{self.app_name}"
!define APP_NAME_EN "{self.app_name_en}"
!define APP_VERSION "{self.app_version}"
!define APP_PUBLISHER "{self.app_publisher}"
!define APP_URL "{self.app_url}"
!define APP_EXE "{self.exe_name}"
# 安装程序信息
Name "${{APP_NAME}} v${{APP_VERSION}}"
OutFile "output\\{installer_name}"
InstallDir "$PROGRAMFILES\\${{APP_NAME_EN}}"
InstallDirRegKey HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "InstallPath"
# 界面设置
!include "MUI2.nsh"
!define MUI_ABORTWARNING
{f'!define MUI_ICON "assets\\\\icon.ico"' if has_icon else ''}
{f'!define MUI_UNICON "assets\\\\icon.ico"' if has_icon else ''}
# 页面配置
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "assets\\license.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
# 语言
!insertmacro MUI_LANGUAGE "SimpChinese"
# 版本信息
VIProductVersion "{self.app_version}.0"
VIAddVersionKey /LANG=2052 "ProductName" "${{APP_NAME}}"
VIAddVersionKey /LANG=2052 "Comments" "水滴AI客服智能助手"
VIAddVersionKey /LANG=2052 "CompanyName" "${{APP_PUBLISHER}}"
VIAddVersionKey /LANG=2052 "FileDescription" "${{APP_NAME}} 安装程序"
VIAddVersionKey /LANG=2052 "FileVersion" "${{APP_VERSION}}"
VIAddVersionKey /LANG=2052 "ProductVersion" "${{APP_VERSION}}"
VIAddVersionKey /LANG=2052 "OriginalFilename" "{installer_name}"
VIAddVersionKey /LANG=2052 "LegalCopyright" "© 2024 ${{APP_PUBLISHER}}"
# 安装段
Section "MainSection" SEC01
SetOutPath "$INSTDIR"
SetOverwrite on
# 复制所有文件
File /r "..\\dist\\MultiPlatformGUI\\*.*"
# 复制图标文件到安装目录(如果有的话)
{f'File "assets\\\\icon.ico"' if has_icon else ''}
# 创建开始菜单快捷方式
CreateDirectory "$SMPROGRAMS\\${{APP_NAME}}"
{f'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}"'}
{f'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\卸载${{APP_NAME}}.lnk" "$INSTDIR\\\\uninstall.exe" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$SMPROGRAMS\\\\${{APP_NAME}}\\\\卸载${{APP_NAME}}.lnk" "$INSTDIR\\\\uninstall.exe"'}
# 创建桌面快捷方式
{f'CreateShortCut "$DESKTOP\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}" "" "$INSTDIR\\\\icon.ico" 0' if has_icon else 'CreateShortCut "$DESKTOP\\\\${{APP_NAME}}.lnk" "$INSTDIR\\\\${{APP_EXE}}"'}
# 写入注册表
WriteRegStr HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "InstallPath" "$INSTDIR"
WriteRegStr HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}" "Version" "${{APP_VERSION}}"
# 写入卸载信息
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "DisplayName" "${{APP_NAME}}"
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "UninstallString" "$INSTDIR\\uninstall.exe"
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "DisplayVersion" "${{APP_VERSION}}"
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "Publisher" "${{APP_PUBLISHER}}"
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "URLInfoAbout" "${{APP_URL}}"
WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "NoModify" 1
WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}" "NoRepair" 1
# 创建卸载程序
WriteUninstaller "$INSTDIR\\uninstall.exe"
SectionEnd
# 卸载段
Section "Uninstall"
# 删除快捷方式
Delete "$SMPROGRAMS\\${{APP_NAME}}\\${{APP_NAME}}.lnk"
Delete "$SMPROGRAMS\\${{APP_NAME}}\\卸载${{APP_NAME}}.lnk"
RMDir "$SMPROGRAMS\\${{APP_NAME}}"
Delete "$DESKTOP\\${{APP_NAME}}.lnk"
# 删除安装目录
RMDir /r "$INSTDIR"
# 删除注册表项
DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${{APP_NAME_EN}}"
DeleteRegKey HKLM "Software\\${{APP_PUBLISHER}}\\${{APP_NAME_EN}}"
SectionEnd
'''
# 写入NSIS脚本文件
# 使用UTF-8 BOM编码NSIS可以正确识别
with open(self.nsis_script, 'w', encoding='utf-8-sig') as f:
f.write(nsis_content)
print(f"✅ NSIS脚本已生成: {self.nsis_script}")
return installer_name
def create_license_file(self):
"""创建许可证文件"""
license_file = self.assets_dir / "license.txt"
license_content = f"""软件许可协议
{self.app_name} v{self.app_version}
版权所有 © 2025 {self.app_publisher}
使用本软件即表示您同意以下条款:
1. 本软件仅供合法用途使用
2. 不得对软件进行反向工程、反编译或反汇编
3. 软件按"原样"提供,不提供任何明示或暗示的保证
4. 在任何情况下,软件提供者均不对因使用本软件而产生的任何损害承担责任
如有疑问,请联系:{self.app_url}
"""
with open(license_file, 'w', encoding='utf-8-sig') as f:
f.write(license_content)
print(f"✅ 许可证文件已创建: {license_file}")
def build_installer(self):
"""构建安装包"""
print("🚀 开始构建NSIS安装包...")
# 创建输出目录
self.output_dir.mkdir(exist_ok=True)
# 执行NSIS编译
try:
cmd = ['makensis', str(self.nsis_script)]
result = subprocess.run(cmd, cwd=str(self.script_dir),
capture_output=True, text=True, check=True)
print("✅ NSIS编译成功")
if result.stdout:
print("NSIS输出:")
print(result.stdout)
return True
except subprocess.CalledProcessError as e:
print("❌ NSIS编译失败")
print(f"错误信息: {e.stderr}")
return False
def run(self):
"""执行完整的构建流程"""
print("=" * 60)
print(f"🔧 {self.app_name} 安装包构建工具")
print("=" * 60)
try:
# 1. 检查前提条件
if not self.check_prerequisites():
return False
# 2. 准备资源文件
has_icon = self.prepare_assets()
# 3. 创建许可证文件
self.create_license_file()
# 4. 生成NSIS脚本
installer_name = self.generate_nsis_script(has_icon)
# 5. 构建安装包
if not self.build_installer():
return False
# 6. 显示结果
installer_path = self.output_dir / installer_name
if installer_path.exists():
installer_size = installer_path.stat().st_size / (1024 * 1024)
print("\n" + "=" * 60)
print("🎉 安装包构建成功!")
print(f"📁 安装包位置: {installer_path}")
print(f"📏 文件大小: {installer_size:.1f} MB")
print("🚀 可以直接分发给用户使用")
print("=" * 60)
return True
else:
print("❌ 安装包文件未找到")
return False
except Exception as e:
print(f"❌ 构建过程出错: {e}")
return False
def main():
"""主函数"""
installer = NSISInstaller()
success = installer.run()
if not success:
sys.exit(1)
if __name__ == "__main__":
main()

26
main.py
View File

@@ -402,6 +402,29 @@ class LoginWindow(QMainWindow):
except Exception as e:
self.add_log(f"处理平台连接事件失败: {e}", "ERROR")
def on_token_error(self, error_content: str):
"""处理token错误 - 显示红色错误信息并停止所有操作"""
try:
self.add_log(f"Token验证失败: {error_content}", "ERROR")
# 在状态标签显示红色错误信息
self.status_label.setText(f"🔴 {error_content}")
self.status_label.setStyleSheet(
"color: #dc3545; background: rgba(220, 53, 69, 0.1); border-radius: 12px; padding: 5px 10px; font-weight: bold;")
# 重置按钮状态
self.login_btn.setEnabled(True)
self.login_btn.setText("重新连接")
self.login_btn.setObjectName("loginButton") # 恢复原始样式
# 清空已连接平台列表
self.connected_platforms.clear()
self.add_log("由于token无效已停止所有连接操作", "ERROR")
except Exception as e:
self.add_log(f"处理token错误失败: {e}", "ERROR")
def delayed_platform_summary(self):
"""定时器触发的汇总显示更新"""
try:
@@ -615,7 +638,8 @@ class LoginWindow(QMainWindow):
log=self.add_log,
success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"),
error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"),
platform_connected=self.on_platform_connected # 新增:平台连接回调
platform_connected=self.on_platform_connected, # 新增:平台连接回调
token_error=self.on_token_error # 新增token错误回调
)
# 连接后端

View File

@@ -7,6 +7,7 @@
import os
import subprocess
import shutil
from pathlib import Path
def clean_build():
@@ -96,17 +97,31 @@ def build_with_command():
print("✅ 打包成功!")
print("📁 打包结果: dist/main/")
# 重命名目录
# 重命名目录为统一的输出路径
if os.path.exists('dist/main'):
try:
# 如果目标目录已存在,先删除
if os.path.exists('dist/MultiPlatformGUI'):
print("🗑️ 删除旧的 MultiPlatformGUI 目录...")
shutil.rmtree('dist/MultiPlatformGUI')
# 重命名
os.rename('dist/main', 'dist/MultiPlatformGUI')
print("✅ 已重命名为: dist/MultiPlatformGUI/")
# 验证重命名结果
if os.path.exists('dist/MultiPlatformGUI/main.exe'):
exe_size = os.path.getsize('dist/MultiPlatformGUI/main.exe') / 1024 / 1024
print(f"✅ 主程序验证通过: main.exe ({exe_size:.1f} MB)")
else:
print("⚠️ 重命名后主程序文件验证失败")
except Exception as e:
print(f"⚠️ 重命名失败: {e}")
print("💡 可以手动重命名: dist/main -> dist/MultiPlatformGUI")
print("📁 当前可用路径: dist/main/")
else:
print("⚠️ 未找到 dist/main 目录,重命名跳过")
# 创建使用说明
try:
@@ -221,31 +236,62 @@ def verify_result():
"""验证打包结果"""
print("\n🔍 验证打包结果...")
# 先检查MultiPlatformGUI再检查main目录
base_dirs = ["dist/MultiPlatformGUI", "dist/main"]
# 先检查MultiPlatformGUI(标准输出目录
target_dir = "dist/MultiPlatformGUI"
fallback_dir = "dist/main"
for base_dir in base_dirs:
if os.path.exists(base_dir):
exe_path = f"{base_dir}/main.exe"
dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll"
if os.path.exists(target_dir):
return _verify_directory(target_dir)
elif os.path.exists(fallback_dir):
print(f"⚠️ 发现备用目录: {fallback_dir}")
print("💡 建议重命名为: dist/MultiPlatformGUI")
return _verify_directory(fallback_dir)
else:
print("❌ 未找到任何打包输出目录")
return False
if os.path.exists(exe_path):
size = os.path.getsize(exe_path)
print(f"✅ 主程序: {exe_path} ({size:,} bytes)")
def _verify_directory(base_dir):
"""验证指定目录的打包结果"""
exe_path = f"{base_dir}/main.exe"
internal_dir = f"{base_dir}/_internal"
dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll"
static_dir = f"{base_dir}/_internal/static"
if os.path.exists(dll_path):
dll_size = os.path.getsize(dll_path)
print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:,} bytes)")
print(f"📁 有效路径: {base_dir}/")
print("✅ 验证通过")
return True
else:
print("千牛DLL不存在")
else:
print(f"{exe_path} 不存在")
print(f"📁 验证目录: {base_dir}")
# 检查主程序
if os.path.exists(exe_path):
size = os.path.getsize(exe_path) / 1024 / 1024 # MB
print(f"✅ 主程序: main.exe ({size:.1f} MB)")
else:
print("主程序 main.exe 不存在")
return False
print("❌ 未找到有效的打包结果")
return False
# 检查_internal目录
if os.path.exists(internal_dir):
file_count = len([f for f in os.listdir(internal_dir) if os.path.isfile(os.path.join(internal_dir, f))])
dir_count = len([d for d in os.listdir(internal_dir) if os.path.isdir(os.path.join(internal_dir, d))])
print(f"✅ 依赖目录: _internal/ ({file_count} 个文件, {dir_count} 个子目录)")
else:
print("❌ _internal 目录不存在")
return False
# 检查关键DLL可选
if os.path.exists(dll_path):
dll_size = os.path.getsize(dll_path) / 1024 # KB
print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:.1f} KB)")
else:
print("⚠️ 千牛DLL不存在可能正常")
# 检查静态资源
if os.path.exists(static_dir):
icon_files = list(Path(static_dir).glob("*.png"))
print(f"✅ 静态资源: static/ ({len(icon_files)} 个图标文件)")
else:
print("⚠️ static 目录不存在")
print(f"✅ 目录 {base_dir} 验证通过")
return True
def main():
@@ -296,10 +342,11 @@ def main():
if os.path.exists("dist/MultiPlatformGUI"):
print("📁 打包结果: dist/MultiPlatformGUI/")
print("🚀 运行方式: cd dist/MultiPlatformGUI && .\\main.exe")
print("📦 安装包构建: python installer/build_installer.py")
elif os.path.exists("dist/main"):
print("<EFBFBD><EFBFBD> 打包结果: dist/main/")
print("📁 打包结果: dist/main/")
print("🚀 运行方式: cd dist/main && .\\main.exe")
print("💡 提示: 可手动重命名为 MultiPlatformGUI")
print("⚠️ 建议重命名: dist/main -> dist/MultiPlatformGUI")
print("=" * 60)