diff --git a/WebSocket/backend_singleton.py b/WebSocket/backend_singleton.py index 16b247d..43999df 100644 --- a/WebSocket/backend_singleton.py +++ b/WebSocket/backend_singleton.py @@ -194,12 +194,19 @@ class WebSocketManager: """连接后端WebSocket""" try: - # 1 保存token到配置 - try: - from config import set_saved_token - set_saved_token(token) - except Exception: - pass + # 🔥 根据配置决定是否保存token + # 生产模式:保存token,方便用户下次自动加载 + # 测试模式:不保存,避免多实例冲突 + import config as cfg + if not cfg.is_multi_instance_mode(): + try: + from config import set_saved_token + set_saved_token(token) + self._log("生产模式:已保存token到配置文件", "INFO") + except Exception as e: + self._log(f"保存token失败: {e}", "WARNING") + else: + self._log("测试模式:不保存token(支持多实例运行)", "INFO") # 2 获取或创建后端客户端 backend = get_backend_client() diff --git a/config.py b/config.py index 42a2d86..043b6cb 100644 --- a/config.py +++ b/config.py @@ -1,136 +1,169 @@ -# -*- coding: utf-8 -*- -""" -项目配置文件 -统一管理所有配置参数,避免硬编码 -""" -# 用户访问令牌(默认空字符串) -import os # 用于路径与目录操作(写入用户配置目录) -import json # 用于将令牌保存为 JSON 格式 - -# 后端服务器配置 -# BACKEND_HOST = "192.168.5.233" -# BACKEND_HOST = "192.168.5.106" -BACKEND_HOST = "192.168.5.12" -# BACKEND_HOST = "shuidrop.com" -# BACKEND_HOST = "test.shuidrop.com" -BACKEND_PORT = "8000" -# BACKEND_PORT = "" -BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" -# BACKEND_WS_URL = f"wss://{BACKEND_HOST}" - -# WebSocket配置 -WS_CONNECT_TIMEOUT = 16.0 -WS_MESSAGE_TIMEOUT = 30.0 -WS_PING_INTERVAL = 15 # 10秒ping间隔(提高检测频率) -WS_PING_TIMEOUT = 10 # 5秒ping超时(更快检测断线) -WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳 -WS_ENABLE_APP_PING = False # 禁用应用层ping心跳(避免重复) - -# AI处理超时配置 -AI_PROCESS_TIMEOUT = 30 # AI处理超时时间(秒) -AI_LONG_PROCESS_THRESHOLD = 10 # AI长时间处理阈值(秒) - -# 内存管理配置 -MAX_PENDING_REPLIES = 100 -CLEANUP_INTERVAL = 300 # 5分钟 -FUTURE_TIMEOUT = 300 # 5分钟 - -# 终端日志配置(简化) -LOG_LEVEL = "INFO" -VERSION = "1.0" - -# GUI配置 -WINDOW_TITLE = "AI回复连接入口-V1.0" - -# 应用版本号(用于版本检查) -APP_VERSION = "1.5.66" - -# 平台特定配置 -PLATFORMS = { - "JD": { - "name": "京东", - "ws_url": "wss://dongdong.jd.com/workbench/websocket" - }, - "DOUYIN": { - "name": "抖音", - "ws_url": None # 动态获取 - }, - "QIANNIU": { - "name": "千牛(淘宝)", - "ws_url": "ws://127.0.0.1:3030" - }, - "PDD": { - "name": "拼多多", - "ws_url": None # 动态获取 - } -} - -def get_backend_ws_url(platform: str, store_id: str) -> str: - """获取旧版后端WebSocket URL(按店铺建连接,保留兼容)""" - return f"{BACKEND_WS_URL}/ws/platform/{platform.lower()}/{store_id}/" - -def get_gui_ws_url(exe_token: str) -> str: - """获取新版单连接GUI专用WebSocket URL(按用户token建一条连接)""" - return f"{BACKEND_WS_URL}/ws/gui/{exe_token}/" - -def get_config(): - """获取所有配置""" - return { - 'backend_host': BACKEND_HOST, - 'backend_port': BACKEND_PORT, - 'backend_ws_url': BACKEND_WS_URL, - 'ws_connect_timeout': WS_CONNECT_TIMEOUT, - 'ws_message_timeout': WS_MESSAGE_TIMEOUT, - 'max_pending_replies': MAX_PENDING_REPLIES, - 'cleanup_interval': CLEANUP_INTERVAL, - 'platforms': PLATFORMS - } - - - -APP_NAME = "ShuidropGUI" # 应用名称(作为配置目录名) - -API_TOKEN = 'sd_acF0TisgfFOtsBm4ytqb17MQbcxuX9Vp' # 默认回退令牌(仅当未找到外部配置时使用) - -def _get_config_paths(): - """返回(配置目录, 配置文件路径),位于 %APPDATA%/ShuidropGUI/config.json""" - base_dir = os.getenv('APPDATA') or os.path.expanduser('~') # 优先使用 APPDATA,其次使用用户主目录 - cfg_dir = os.path.join(base_dir, APP_NAME) # 组合配置目录路径 - cfg_file = os.path.join(cfg_dir, 'config.json') # 组合配置文件路径 - return cfg_dir, cfg_file - - -def get_saved_token() -> str: - """优先从外部 JSON 配置读取令牌,不存在时回退到内置 API_TOKEN""" - try: - cfg_dir, cfg_file = _get_config_paths() # 获取目录与文件路径 - if os.path.exists(cfg_file): # 如果配置文件存在 - with open(cfg_file, 'r', encoding='utf-8') as f: # 以 UTF-8 读取 - data = json.load(f) # 解析 JSON 内容 - token = data.get('token', '') # 读取 token 字段 - if token: # 如果有效 - return token # 返回读取到的令牌 - except Exception: - pass # 读取失败时静默回退 - return API_TOKEN # 回退为内置的默认值 - - -def set_saved_token(new_token: str) -> bool: - """将访问令牌写入外部 JSON 配置,并更新内存中的值 - - new_token: 新的访问令牌字符串 - 返回: True 表示写入成功,False 表示失败 - """ - try: - cfg_dir, cfg_file = _get_config_paths() - os.makedirs(cfg_dir, exist_ok=True) - data = {'token': new_token} - with open(cfg_file, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=2) - # 同步更新内存变量,保证运行期可立即生效 - global API_TOKEN - API_TOKEN = new_token - return True - except Exception as e: - # 发生异常时打印提示并返回失败 - print(f"写入令牌失败: {e}") +# -*- coding: utf-8 -*- +""" +项目配置文件 +统一管理所有配置参数,避免硬编码 +""" +# 用户访问令牌(默认空字符串) +import os # 用于路径与目录操作(写入用户配置目录) +import json # 用于将令牌保存为 JSON 格式 + +# 后端服务器配置 +# BACKEND_HOST = "192.168.5.233" +# BACKEND_HOST = "192.168.5.106" +BACKEND_HOST = "192.168.5.12" +# BACKEND_HOST = "shuidrop.com" +# BACKEND_HOST = "test.shuidrop.com" +BACKEND_PORT = "8000" +# BACKEND_PORT = "" +BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" +# BACKEND_WS_URL = f"wss://{BACKEND_HOST}" + +# WebSocket配置 +WS_CONNECT_TIMEOUT = 16.0 +WS_MESSAGE_TIMEOUT = 30.0 +WS_PING_INTERVAL = 15 # 10秒ping间隔(提高检测频率) +WS_PING_TIMEOUT = 10 # 5秒ping超时(更快检测断线) +WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳 +WS_ENABLE_APP_PING = False # 禁用应用层ping心跳(避免重复) + +# AI处理超时配置 +AI_PROCESS_TIMEOUT = 30 # AI处理超时时间(秒) +AI_LONG_PROCESS_THRESHOLD = 10 # AI长时间处理阈值(秒) + +# 内存管理配置 +MAX_PENDING_REPLIES = 100 +CLEANUP_INTERVAL = 300 # 5分钟 +FUTURE_TIMEOUT = 300 # 5分钟 + +# 终端日志配置(简化) +LOG_LEVEL = "INFO" +VERSION = "1.0" + +# GUI配置 +WINDOW_TITLE = "AI回复连接入口-V1.0" + +# 应用版本号(用于版本检查) +APP_VERSION = "1.5.66" + +# 🔥 多实例运行模式开关 +# - True: 测试模式(多实例,不保存token,避免冲突) +# - False: 生产模式(单实例,保存token,自动加载) +# +# 使用方法: +# 1. 修改此值:MULTI_INSTANCE_MODE = False # 改为生产模式 +# 2. 或设置环境变量:SHUIDROP_MULTI_INSTANCE=0 # 临时切换到生产模式 +MULTI_INSTANCE_MODE = True # 默认:测试模式 + +def is_multi_instance_mode() -> bool: + """ + 检查是否为多实例模式(支持环境变量覆盖) + + 优先级: + 1. 环境变量 SHUIDROP_MULTI_INSTANCE(0=生产,1=测试) + 2. 配置文件 MULTI_INSTANCE_MODE + + Returns: + bool: True=多实例模式,False=单实例模式 + """ + # 检查环境变量 + env_value = os.getenv('SHUIDROP_MULTI_INSTANCE') + if env_value is not None: + # 0, false, False, no, No → 生产模式 + if env_value.lower() in ('0', 'false', 'no'): + return False + # 1, true, True, yes, Yes → 测试模式 + if env_value.lower() in ('1', 'true', 'yes'): + return True + + # 使用配置文件值 (如果不做设置我们可以直接用编码变量进行控制是否可以允许多实例的方式运行) + return MULTI_INSTANCE_MODE + +# 平台特定配置 +PLATFORMS = { + "JD": { + "name": "京东", + "ws_url": "wss://dongdong.jd.com/workbench/websocket" + }, + "DOUYIN": { + "name": "抖音", + "ws_url": None # 动态获取 + }, + "QIANNIU": { + "name": "千牛(淘宝)", + "ws_url": "ws://127.0.0.1:3030" + }, + "PDD": { + "name": "拼多多", + "ws_url": None # 动态获取 + } +} + +def get_backend_ws_url(platform: str, store_id: str) -> str: + """获取旧版后端WebSocket URL(按店铺建连接,保留兼容)""" + return f"{BACKEND_WS_URL}/ws/platform/{platform.lower()}/{store_id}/" + +def get_gui_ws_url(exe_token: str) -> str: + """获取新版单连接GUI专用WebSocket URL(按用户token建一条连接)""" + return f"{BACKEND_WS_URL}/ws/gui/{exe_token}/" + +def get_config(): + """获取所有配置""" + return { + 'backend_host': BACKEND_HOST, + 'backend_port': BACKEND_PORT, + 'backend_ws_url': BACKEND_WS_URL, + 'ws_connect_timeout': WS_CONNECT_TIMEOUT, + 'ws_message_timeout': WS_MESSAGE_TIMEOUT, + 'max_pending_replies': MAX_PENDING_REPLIES, + 'cleanup_interval': CLEANUP_INTERVAL, + 'platforms': PLATFORMS + } + + + +APP_NAME = "ShuidropGUI" # 应用名称(作为配置目录名) + +API_TOKEN = 'sd_acF0TisgfFOtsBm4ytqb17MQbcxuX9Vp' # 默认回退令牌(仅当未找到外部配置时使用) + +def _get_config_paths(): + """返回(配置目录, 配置文件路径),位于 %APPDATA%/ShuidropGUI/config.json""" + base_dir = os.getenv('APPDATA') or os.path.expanduser('~') # 优先使用 APPDATA,其次使用用户主目录 + cfg_dir = os.path.join(base_dir, APP_NAME) # 组合配置目录路径 + cfg_file = os.path.join(cfg_dir, 'config.json') # 组合配置文件路径 + return cfg_dir, cfg_file + + +def get_saved_token() -> str: + """优先从外部 JSON 配置读取令牌,不存在时回退到内置 API_TOKEN""" + try: + cfg_dir, cfg_file = _get_config_paths() # 获取目录与文件路径 + if os.path.exists(cfg_file): # 如果配置文件存在 + with open(cfg_file, 'r', encoding='utf-8') as f: # 以 UTF-8 读取 + data = json.load(f) # 解析 JSON 内容 + token = data.get('token', '') # 读取 token 字段 + if token: # 如果有效 + return token # 返回读取到的令牌 + except Exception: + pass # 读取失败时静默回退 + return API_TOKEN # 回退为内置的默认值 + + +def set_saved_token(new_token: str) -> bool: + """将访问令牌写入外部 JSON 配置,并更新内存中的值 + - new_token: 新的访问令牌字符串 + 返回: True 表示写入成功,False 表示失败 + """ + try: + cfg_dir, cfg_file = _get_config_paths() + os.makedirs(cfg_dir, exist_ok=True) + data = {'token': new_token} + with open(cfg_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + # 同步更新内存变量,保证运行期可立即生效 + global API_TOKEN + API_TOKEN = new_token + return True + except Exception as e: + # 发生异常时打印提示并返回失败 + print(f"写入令牌失败: {e}") return False \ No newline at end of file diff --git a/main.py b/main.py index 48d231e..8e6cbc4 100644 --- a/main.py +++ b/main.py @@ -145,14 +145,20 @@ class LoginWindow(QMainWindow): # noinspection PyUnresolvedReferences self.token_input.returnPressed.connect(self.login) # 表示回车提交 self.token_input.setMinimumHeight(34) # 最小输入框高度 - # 预填已保存的令牌(如果存在) - try: - from config import get_saved_token - saved = get_saved_token() - if saved: - self.token_input.setText(saved) - except Exception: - pass + # 🔥 根据配置决定是否自动加载token + # 生产模式:自动加载保存的token(方便用户) + # 测试模式:不加载,避免多实例冲突 + if not config.is_multi_instance_mode(): + try: + from config import get_saved_token + saved = get_saved_token() + if saved: + self.token_input.setText(saved) + print("[INFO] 生产模式:已自动加载保存的token") + except Exception: + pass + else: + print("[INFO] 测试模式:不自动加载token,支持多实例运行") token_layout.addWidget(token_label) token_layout.addWidget(self.token_input)