[patch] 修改适中心跳机制 JD的平台登录抢占 提示弹框优化 并下发连接断开通知 托盘右击提示小标题显示精炼 增加store_name入回调方法做店铺名称定位
This commit is contained in:
@@ -177,6 +177,11 @@ class FixJdCookie:
|
|||||||
"""初始化 socket"""
|
"""初始化 socket"""
|
||||||
await self.send_heartbeat(ws, aid, pin_zj)
|
await self.send_heartbeat(ws, aid, pin_zj)
|
||||||
print("开始监听初始化")
|
print("开始监听初始化")
|
||||||
|
|
||||||
|
# 🔧 修复:生成唯一设备ID,避免多端互踢
|
||||||
|
import uuid
|
||||||
|
unique_device_id = f"shuidrop_gui_{uuid.uuid4().hex[:16]}"
|
||||||
|
|
||||||
auth = {
|
auth = {
|
||||||
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
|
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
|
||||||
"aid": aid,
|
"aid": aid,
|
||||||
@@ -185,8 +190,9 @@ class FixJdCookie:
|
|||||||
"type": "auth",
|
"type": "auth",
|
||||||
"body": {"presence": 1, "clientVersion": "2.6.3"},
|
"body": {"presence": 1, "clientVersion": "2.6.3"},
|
||||||
"to": {"app": "im.waiter"},
|
"to": {"app": "im.waiter"},
|
||||||
"from": {"app": "im.waiter", "pin": pin_zj, "clientType": "comet", "dvc": "device1234"}
|
"from": {"app": "im.waiter", "pin": pin_zj, "clientType": "comet", "dvc": unique_device_id}
|
||||||
}
|
}
|
||||||
|
print(f"[DEBUG] 使用唯一设备ID: {unique_device_id}")
|
||||||
await ws.send(json.dumps(auth))
|
await ws.send(json.dumps(auth))
|
||||||
|
|
||||||
|
|
||||||
@@ -611,10 +617,42 @@ class FixJdCookie:
|
|||||||
print(f"等待监听消息-{datetime.now()}")
|
print(f"等待监听消息-{datetime.now()}")
|
||||||
response = await asyncio.wait_for(ws.recv(), timeout=1)
|
response = await asyncio.wait_for(ws.recv(), timeout=1)
|
||||||
print(f"原始消息类型:{type(response)}, 消息体为: {response}")
|
print(f"原始消息类型:{type(response)}, 消息体为: {response}")
|
||||||
await self.process_incoming_message(response, ws, aid, pin_zj, vender_id, store)
|
|
||||||
|
# 🔧 修复:检测被踢下线消息
|
||||||
# 安全解析消息
|
|
||||||
json_resp = json.loads(response) if isinstance(response, (str, bytes)) else response
|
json_resp = json.loads(response) if isinstance(response, (str, bytes)) else response
|
||||||
|
|
||||||
|
# 检查是否为server_msg类型且code=5(被踢下线)
|
||||||
|
if json_resp.get("type") == "server_msg":
|
||||||
|
body = json_resp.get("body", {})
|
||||||
|
if body.get("code") == 5:
|
||||||
|
msgtext = body.get("msgtext", "账号在其他设备登录")
|
||||||
|
self._log(f"⚠️ 收到被踢下线消息: {msgtext}", "WARNING")
|
||||||
|
self._log("⚠️ 检测到多端登录冲突,请确保:", "WARNING")
|
||||||
|
self._log(" 1. 关闭网页版京东咚咚", "WARNING")
|
||||||
|
self._log(" 2. 关闭其他客户端", "WARNING")
|
||||||
|
self._log(" 3. 确认只有本客户端连接", "WARNING")
|
||||||
|
|
||||||
|
# 通知GUI显示弹窗
|
||||||
|
try:
|
||||||
|
from WebSocket.backend_singleton import get_websocket_manager
|
||||||
|
ws_manager = get_websocket_manager()
|
||||||
|
if ws_manager:
|
||||||
|
# 从platform_listeners获取店铺名称
|
||||||
|
store_name = "京东店铺"
|
||||||
|
for key, info in ws_manager.platform_listeners.items():
|
||||||
|
if info.get('store_id') == store:
|
||||||
|
store_name = info.get('store_name', '') or "京东店铺"
|
||||||
|
break
|
||||||
|
# 传递 store_id 参数,用于通知后端
|
||||||
|
ws_manager.notify_platform_kicked("京东", store_name, msgtext, store_id=store)
|
||||||
|
except Exception as notify_error:
|
||||||
|
self._log(f"通知GUI失败: {notify_error}", "ERROR")
|
||||||
|
|
||||||
|
# 不再自动重连,等待用户处理
|
||||||
|
stop_event.set()
|
||||||
|
break
|
||||||
|
|
||||||
|
await self.process_incoming_message(response, ws, aid, pin_zj, vender_id, store)
|
||||||
|
|
||||||
print(json_resp)
|
print(json_resp)
|
||||||
|
|
||||||
@@ -660,9 +698,8 @@ class FixJdCookie:
|
|||||||
if not await self.handle_reconnect(e):
|
if not await self.handle_reconnect(e):
|
||||||
break
|
break
|
||||||
|
|
||||||
# 关闭后端服务连接
|
# 关闭后端服务连接(JDBackendService没有close方法,跳过)
|
||||||
if self.backend_connected:
|
if self.backend_connected:
|
||||||
await self.backend_service.close()
|
|
||||||
self.backend_connected = False
|
self.backend_connected = False
|
||||||
self._log("🛑 消息监听已停止", "INFO")
|
self._log("🛑 消息监听已停止", "INFO")
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ class BackendClient:
|
|||||||
self._log("已禁用心跳机制", "WARNING")
|
self._log("已禁用心跳机制", "WARNING")
|
||||||
|
|
||||||
self.is_connected = True
|
self.is_connected = True
|
||||||
|
|
||||||
|
# 🔥 在重置之前记录是否是重连(用于后续上报平台状态)
|
||||||
|
was_reconnecting = self.reconnect_attempts > 0
|
||||||
|
|
||||||
self.reconnect_attempts = 0 # 重置重连计数
|
self.reconnect_attempts = 0 # 重置重连计数
|
||||||
self.is_reconnecting = False
|
self.is_reconnecting = False
|
||||||
self._log("后端WebSocket连接成功", "SUCCESS")
|
self._log("后端WebSocket连接成功", "SUCCESS")
|
||||||
@@ -140,7 +144,7 @@ class BackendClient:
|
|||||||
# 发送连接状态通知给后端
|
# 发送连接状态通知给后端
|
||||||
self._notify_connection_status(True)
|
self._notify_connection_status(True)
|
||||||
|
|
||||||
self.on_connected()
|
self.on_connected(was_reconnecting)
|
||||||
|
|
||||||
# 消息循环
|
# 消息循环
|
||||||
async for message in self.websocket:
|
async for message in self.websocket:
|
||||||
@@ -308,53 +312,68 @@ class BackendClient:
|
|||||||
if log:
|
if log:
|
||||||
self.log_callback = log
|
self.log_callback = log
|
||||||
|
|
||||||
def on_connected(self):
|
def on_connected(self, was_reconnecting: bool = False):
|
||||||
"""连接成功时的处理"""
|
"""连接成功时的处理"""
|
||||||
if self.reconnect_attempts > 0:
|
if was_reconnecting:
|
||||||
self._log(f"后端WebSocket重连成功!(第{self.reconnect_attempts}次尝试)", "SUCCESS")
|
self._log("后端WebSocket重连成功!", "SUCCESS")
|
||||||
else:
|
# 重连成功后上报平台状态给后端
|
||||||
self._log("后端WebSocket连接成功", "SUCCESS")
|
|
||||||
|
|
||||||
# 重连成功后可选择上报状态给后端
|
|
||||||
if self.reconnect_attempts > 0:
|
|
||||||
self._report_reconnect_status()
|
self._report_reconnect_status()
|
||||||
|
else:
|
||||||
|
self._log("后端WebSocket首次连接成功", "SUCCESS")
|
||||||
|
|
||||||
# 不再主动请求 get_store,避免与后端不兼容导致协程未完成
|
# 不再主动请求 get_store,避免与后端不兼容导致协程未完成
|
||||||
|
|
||||||
def _report_reconnect_status(self):
|
def _report_reconnect_status(self):
|
||||||
"""重连成功后上报当前状态(可选)"""
|
"""重连成功后上报当前状态"""
|
||||||
try:
|
try:
|
||||||
# 获取当前已连接的平台列表
|
# 获取当前已连接的平台列表
|
||||||
from WebSocket.backend_singleton import get_websocket_manager
|
from WebSocket.backend_singleton import get_websocket_manager
|
||||||
manager = get_websocket_manager()
|
manager = get_websocket_manager()
|
||||||
|
|
||||||
if hasattr(manager, 'platform_listeners') and manager.platform_listeners:
|
if hasattr(manager, 'platform_listeners') and manager.platform_listeners:
|
||||||
|
platform_count = len(manager.platform_listeners)
|
||||||
|
self._log(f"🔄 检测到 {platform_count} 个活跃平台连接,准备重新上报状态", "INFO")
|
||||||
|
|
||||||
|
# 延迟1秒,确保后端完全准备好
|
||||||
|
import time
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
for platform_key, listener_info in manager.platform_listeners.items():
|
for platform_key, listener_info in manager.platform_listeners.items():
|
||||||
store_id = listener_info.get('store_id')
|
store_id = listener_info.get('store_id')
|
||||||
platform = listener_info.get('platform')
|
platform = listener_info.get('platform')
|
||||||
|
store_name = listener_info.get('store_name', '')
|
||||||
|
|
||||||
if store_id and platform:
|
if store_id and platform:
|
||||||
# 上报平台仍在连接状态
|
# 上报平台仍在连接状态
|
||||||
try:
|
try:
|
||||||
|
store_display = store_name or store_id[:8] + "..."
|
||||||
reconnect_message = {
|
reconnect_message = {
|
||||||
"type": "connect_message",
|
"type": "connect_message",
|
||||||
"store_id": store_id,
|
"store_id": store_id,
|
||||||
"status": True,
|
"status": True,
|
||||||
"content": f"GUI重连成功,{platform}平台状态正常"
|
"cookies": "" # 重连时无需再次发送cookies
|
||||||
}
|
}
|
||||||
# 异步发送,不阻塞连接过程
|
|
||||||
asyncio.run_coroutine_threadsafe(
|
# 同步发送,确保发送成功
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
self._send_to_backend(reconnect_message),
|
self._send_to_backend(reconnect_message),
|
||||||
self.loop
|
self.loop
|
||||||
)
|
)
|
||||||
print(f"[重连] 已上报{platform}平台状态")
|
# 等待发送完成(最多2秒)
|
||||||
|
future.result(timeout=2)
|
||||||
|
|
||||||
|
self._log(f"✅ 已重新上报 {platform} 平台状态: {store_display}", "SUCCESS")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[重连] 上报{platform}平台状态失败: {e}")
|
self._log(f"❌ 上报 {platform} 平台状态失败: {e}", "ERROR")
|
||||||
|
import traceback
|
||||||
|
self._log(f"详细错误: {traceback.format_exc()}", "DEBUG")
|
||||||
else:
|
else:
|
||||||
print("[重连] 当前无活跃平台连接,跳过状态上报")
|
self._log("当前无活跃平台连接,跳过状态上报", "INFO")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[重连] 状态上报过程异常: {e}")
|
self._log(f"状态上报过程异常: {e}", "ERROR")
|
||||||
|
import traceback
|
||||||
|
self._log(f"详细错误: {traceback.format_exc()}", "DEBUG")
|
||||||
|
|
||||||
def _notify_connection_status(self, connected: bool):
|
def _notify_connection_status(self, connected: bool):
|
||||||
"""通知后端连接状态变化"""
|
"""通知后端连接状态变化"""
|
||||||
@@ -365,30 +384,21 @@ class BackendClient:
|
|||||||
# 获取当前活跃平台的store_id
|
# 获取当前活跃平台的store_id
|
||||||
active_store_id = None
|
active_store_id = None
|
||||||
try:
|
try:
|
||||||
from Utils.JD.JdUtils import WebsocketManager as JdManager
|
from WebSocket.backend_singleton import get_websocket_manager
|
||||||
from Utils.Dy.DyUtils import DouYinWebsocketManager as DyManager
|
manager = get_websocket_manager()
|
||||||
from Utils.Pdd.PddUtils import WebsocketManager as PddManager
|
|
||||||
|
# 从 platform_listeners 获取活跃平台
|
||||||
# 检查各平台是否有活跃连接
|
if hasattr(manager, 'platform_listeners') and manager.platform_listeners:
|
||||||
for mgr_class, platform_name in [(JdManager, "京东"), (DyManager, "抖音"), (PddManager, "拼多多")]:
|
# 获取第一个活跃平台的 store_id
|
||||||
try:
|
for platform_key, listener_info in manager.platform_listeners.items():
|
||||||
mgr = mgr_class()
|
store_id = listener_info.get('store_id')
|
||||||
if hasattr(mgr, '_store') and mgr._store:
|
platform = listener_info.get('platform')
|
||||||
for shop_key, entry in mgr._store.items():
|
if store_id and platform:
|
||||||
if entry and entry.get('platform'):
|
active_store_id = store_id
|
||||||
# 从shop_key中提取store_id(格式:平台:store_id)
|
self._log(f"检测到活跃{platform}平台: {store_id}", "DEBUG")
|
||||||
if ':' in shop_key:
|
break
|
||||||
_, store_id = shop_key.split(':', 1)
|
|
||||||
active_store_id = store_id
|
|
||||||
print(f"[状态] 检测到活跃{platform_name}平台: {store_id}")
|
|
||||||
break
|
|
||||||
if active_store_id:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[状态] 检查{platform_name}平台失败: {e}")
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[状态] 获取活跃平台信息失败: {e}")
|
self._log(f"获取活跃平台信息失败: {e}", "DEBUG")
|
||||||
|
|
||||||
status_message = {
|
status_message = {
|
||||||
"type": "connection_status",
|
"type": "connection_status",
|
||||||
@@ -400,9 +410,9 @@ class BackendClient:
|
|||||||
# 如果有活跃平台,添加store_id
|
# 如果有活跃平台,添加store_id
|
||||||
if active_store_id:
|
if active_store_id:
|
||||||
status_message["store_id"] = active_store_id
|
status_message["store_id"] = active_store_id
|
||||||
print(f"[状态] 添加store_id到状态消息: {active_store_id}")
|
self._log(f"添加store_id到状态消息: {active_store_id}", "DEBUG")
|
||||||
else:
|
else:
|
||||||
print(f"[状态] 未检测到活跃平台,不添加store_id")
|
self._log("未检测到活跃平台,不添加store_id", "DEBUG")
|
||||||
|
|
||||||
# 异步发送状态通知
|
# 异步发送状态通知
|
||||||
asyncio.run_coroutine_threadsafe(
|
asyncio.run_coroutine_threadsafe(
|
||||||
@@ -411,12 +421,12 @@ class BackendClient:
|
|||||||
)
|
)
|
||||||
|
|
||||||
status_text = "连接" if connected else "断开"
|
status_text = "连接" if connected else "断开"
|
||||||
print(f"[状态] 已通知后端GUI客户端{status_text}")
|
self._log(f"已通知后端GUI客户端{status_text}", "DEBUG")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[状态] 发送状态通知失败: {e}")
|
self._log(f"发送状态通知失败: {e}", "ERROR")
|
||||||
import traceback
|
import traceback
|
||||||
print(f"[状态] 详细错误: {traceback.format_exc()}")
|
self._log(f"详细错误: {traceback.format_exc()}", "DEBUG")
|
||||||
|
|
||||||
def on_message_received(self, message: Dict[str, Any]):
|
def on_message_received(self, message: Dict[str, Any]):
|
||||||
"""处理接收到的消息 - 根据WebSocket文档v2更新"""
|
"""处理接收到的消息 - 根据WebSocket文档v2更新"""
|
||||||
@@ -1172,10 +1182,11 @@ class BackendClient:
|
|||||||
self.get_store()
|
self.get_store()
|
||||||
|
|
||||||
def _handle_login(self, message: Dict[str, Any]):
|
def _handle_login(self, message: Dict[str, Any]):
|
||||||
"""处理平台登录消息(新版:type=login, cookies/login_params, store_id, platform_name)"""
|
"""处理平台登录消息(新版:type=login, cookies/login_params, store_id, platform_name, store_name)"""
|
||||||
cookies = message.get('cookies', '')
|
cookies = message.get('cookies', '')
|
||||||
store_id = message.get('store_id', '')
|
store_id = message.get('store_id', '')
|
||||||
platform_name = message.get('platform_name', '')
|
platform_name = message.get('platform_name', '')
|
||||||
|
store_name = message.get('store_name', '') # 新增:获取店铺名称
|
||||||
content = message.get('content', '')
|
content = message.get('content', '')
|
||||||
data = message.get('data', {})
|
data = message.get('data', {})
|
||||||
|
|
||||||
@@ -1184,17 +1195,17 @@ class BackendClient:
|
|||||||
(("拼多多登录" in content and data.get('login_params')) or
|
(("拼多多登录" in content and data.get('login_params')) or
|
||||||
("抖音登录" in content and data.get('login_flow')))):
|
("抖音登录" in content and data.get('login_flow')))):
|
||||||
# 登录参数模式 - 传递完整的消息JSON给处理器
|
# 登录参数模式 - 传递完整的消息JSON给处理器
|
||||||
print(f"收到{platform_name}登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}")
|
print(f"收到{platform_name}登录参数: 平台={platform_name}, 店铺={store_name or store_id}, 类型={content}")
|
||||||
if self.login_callback:
|
if self.login_callback:
|
||||||
# 传递完整的JSON消息,让处理器来解析登录参数
|
# 传递完整的JSON消息,让处理器来解析登录参数
|
||||||
import json
|
import json
|
||||||
full_message = json.dumps(message)
|
full_message = json.dumps(message)
|
||||||
self.login_callback(platform_name, store_id, full_message)
|
self.login_callback(platform_name, store_id, full_message, store_name)
|
||||||
else:
|
else:
|
||||||
# 普通Cookie模式
|
# 普通Cookie模式
|
||||||
print(f"收到登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}")
|
print(f"收到登录指令: 平台={platform_name}, 店铺={store_name or store_id}, cookies_len={len(cookies) if cookies else 0}")
|
||||||
if self.login_callback:
|
if self.login_callback:
|
||||||
self.login_callback(platform_name, store_id, cookies)
|
self.login_callback(platform_name, store_id, cookies, store_name)
|
||||||
|
|
||||||
def _handle_error_message(self, message: Dict[str, Any]):
|
def _handle_error_message(self, message: Dict[str, Any]):
|
||||||
"""处理错误消息"""
|
"""处理错误消息"""
|
||||||
@@ -1253,7 +1264,7 @@ class BackendClient:
|
|||||||
|
|
||||||
def _handle_disconnect(self, message: Dict[str, Any]):
|
def _handle_disconnect(self, message: Dict[str, Any]):
|
||||||
"""处理被踢下线消息"""
|
"""处理被踢下线消息"""
|
||||||
disconnect_message = message.get('message', '您的账号在其他设备登录,当前连接已断开')
|
disconnect_message = message.get('content', '您的账号在其他设备登录,当前连接已断开')
|
||||||
|
|
||||||
print(f"[断开] 收到后端断开通知: {disconnect_message}")
|
print(f"[断开] 收到后端断开通知: {disconnect_message}")
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ class WebSocketManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None,
|
def set_callbacks(self, log: Callable = None, success: Callable = None, error: Callable = None,
|
||||||
platform_connected: Callable = None, token_error: Callable = None, disconnect: Callable = None):
|
platform_connected: Callable = None, platform_disconnected: Callable = None,
|
||||||
|
token_error: Callable = None, disconnect: Callable = None):
|
||||||
"""设置回调函数"""
|
"""设置回调函数"""
|
||||||
if log:
|
if log:
|
||||||
self.callbacks['log'] = log
|
self.callbacks['log'] = log
|
||||||
@@ -69,6 +70,8 @@ class WebSocketManager:
|
|||||||
self.callbacks['error'] = error
|
self.callbacks['error'] = error
|
||||||
if platform_connected: # ← 新增
|
if platform_connected: # ← 新增
|
||||||
self.callbacks['platform_connected'] = platform_connected
|
self.callbacks['platform_connected'] = platform_connected
|
||||||
|
if platform_disconnected:
|
||||||
|
self.callbacks['platform_disconnected'] = platform_disconnected
|
||||||
if token_error:
|
if token_error:
|
||||||
self.callbacks['token_error'] = token_error
|
self.callbacks['token_error'] = token_error
|
||||||
if disconnect:
|
if disconnect:
|
||||||
@@ -101,6 +104,35 @@ class WebSocketManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log(f"通知平台连接失败: {e}", "ERROR")
|
self._log(f"通知平台连接失败: {e}", "ERROR")
|
||||||
|
|
||||||
|
def notify_platform_kicked(self, platform_name: str, store_name: str, reason: str = "账号在其他设备登录", store_id: str = None):
|
||||||
|
"""通知GUI平台被踢下线(供平台监听器调用)"""
|
||||||
|
try:
|
||||||
|
self._log(f"⚠️ 平台被踢下线: {platform_name} - {store_name}, 原因: {reason}", "WARNING")
|
||||||
|
|
||||||
|
# 从连接列表中移除
|
||||||
|
if platform_name in self.connected_platforms:
|
||||||
|
self.connected_platforms.remove(platform_name)
|
||||||
|
|
||||||
|
# 🔥 发送平台断开消息给后端(status=false)
|
||||||
|
if store_id and self.backend_client:
|
||||||
|
try:
|
||||||
|
disconnect_message = {
|
||||||
|
"type": "connect_message",
|
||||||
|
"store_id": store_id,
|
||||||
|
"status": False,
|
||||||
|
"cookies": ""
|
||||||
|
}
|
||||||
|
self.backend_client.send_message(disconnect_message)
|
||||||
|
self._log(f"✅ 已通知后端 {platform_name} 平台断开: {store_id}", "INFO")
|
||||||
|
except Exception as send_error:
|
||||||
|
self._log(f"❌ 发送平台断开消息失败: {send_error}", "ERROR")
|
||||||
|
|
||||||
|
# 通知GUI显示弹窗
|
||||||
|
if self.callbacks['platform_disconnected']:
|
||||||
|
self.callbacks['platform_disconnected'](platform_name, store_name, reason)
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"通知平台断开失败: {e}", "ERROR")
|
||||||
|
|
||||||
def connect_backend(self, token: str) -> bool:
|
def connect_backend(self, token: str) -> bool:
|
||||||
"""连接后端WebSocket"""
|
"""连接后端WebSocket"""
|
||||||
try:
|
try:
|
||||||
@@ -139,11 +171,12 @@ class WebSocketManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log(f"成功回调执行失败: {e}", "ERROR")
|
self._log(f"成功回调执行失败: {e}", "ERROR")
|
||||||
|
|
||||||
def _on_backend_login(platform_name: str, store_id: str, cookies: str):
|
def _on_backend_login(platform_name: str, store_id: str, cookies: str, store_name: str = ""):
|
||||||
|
store_display = store_name or store_id
|
||||||
self._log(
|
self._log(
|
||||||
f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}",
|
f"收到后端登录指令: 平台={platform_name}, 店铺={store_display}, cookies_len={len(cookies) if cookies else 0}",
|
||||||
"INFO")
|
"INFO")
|
||||||
self._handle_platform_login(platform_name, store_id, cookies)
|
self._handle_platform_login(platform_name, store_id, cookies, store_name)
|
||||||
|
|
||||||
def _on_token_error(error_content: str):
|
def _on_token_error(error_content: str):
|
||||||
self._log(f"Token验证失败: {error_content}", "ERROR")
|
self._log(f"Token验证失败: {error_content}", "ERROR")
|
||||||
@@ -175,11 +208,12 @@ class WebSocketManager:
|
|||||||
|
|
||||||
backend = BackendClient.from_exe_token(token)
|
backend = BackendClient.from_exe_token(token)
|
||||||
|
|
||||||
def _on_backend_login(platform_name: str, store_id: str, cookies: str):
|
def _on_backend_login(platform_name: str, store_id: str, cookies: str, store_name: str = ""):
|
||||||
|
store_display = store_name or store_id
|
||||||
self._log(
|
self._log(
|
||||||
f"收到后端登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}",
|
f"收到后端登录指令: 平台={platform_name}, 店铺={store_display}, cookies_len={len(cookies) if cookies else 0}",
|
||||||
"INFO")
|
"INFO")
|
||||||
self._handle_platform_login(platform_name, store_id, cookies)
|
self._handle_platform_login(platform_name, store_id, cookies, store_name)
|
||||||
|
|
||||||
def _on_backend_success():
|
def _on_backend_success():
|
||||||
try:
|
try:
|
||||||
@@ -221,7 +255,7 @@ class WebSocketManager:
|
|||||||
self.callbacks['error'](str(e))
|
self.callbacks['error'](str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _handle_platform_login(self, platform_name: str, store_id: str, cookies: str):
|
def _handle_platform_login(self, platform_name: str, store_id: str, cookies: str, store_name: str = ""):
|
||||||
"""处理平台登录请求"""
|
"""处理平台登录请求"""
|
||||||
try:
|
try:
|
||||||
# 🔥 检查并断开当前店铺的旧连接(策略B:先断开旧连接,再建立新连接)
|
# 🔥 检查并断开当前店铺的旧连接(策略B:先断开旧连接,再建立新连接)
|
||||||
@@ -335,16 +369,16 @@ class WebSocketManager:
|
|||||||
if cookies == "login_success":
|
if cookies == "login_success":
|
||||||
self._log("⚠️ 千牛平台收到空cookies,但允许启动监听器", "WARNING")
|
self._log("⚠️ 千牛平台收到空cookies,但允许启动监听器", "WARNING")
|
||||||
cookies = "" # 清空cookies,千牛不需要真实cookies
|
cookies = "" # 清空cookies,千牛不需要真实cookies
|
||||||
self._start_qianniu_listener(store_id, cookies)
|
self._start_qianniu_listener(store_id, cookies, store_name)
|
||||||
|
|
||||||
elif normalized_platform == "京东":
|
elif normalized_platform == "京东":
|
||||||
self._start_jd_listener(store_id, cookies)
|
self._start_jd_listener(store_id, cookies, store_name)
|
||||||
|
|
||||||
elif normalized_platform == "抖音":
|
elif normalized_platform == "抖音":
|
||||||
self._start_douyin_listener(store_id, cookies)
|
self._start_douyin_listener(store_id, cookies, store_name)
|
||||||
|
|
||||||
elif normalized_platform == "拼多多":
|
elif normalized_platform == "拼多多":
|
||||||
self._start_pdd_listener(store_id, cookies)
|
self._start_pdd_listener(store_id, cookies, store_name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._log(f"❌ 不支持的平台: {platform_name}", "ERROR")
|
self._log(f"❌ 不支持的平台: {platform_name}", "ERROR")
|
||||||
@@ -382,7 +416,7 @@ class WebSocketManager:
|
|||||||
import traceback
|
import traceback
|
||||||
self._log(f"详细错误: {traceback.format_exc()}", "ERROR")
|
self._log(f"详细错误: {traceback.format_exc()}", "ERROR")
|
||||||
|
|
||||||
def _start_jd_listener(self, store_id: str, cookies: str):
|
def _start_jd_listener(self, store_id: str, cookies: str, store_name: str = ""):
|
||||||
"""启动京东平台监听"""
|
"""启动京东平台监听"""
|
||||||
try:
|
try:
|
||||||
def _runner():
|
def _runner():
|
||||||
@@ -400,7 +434,8 @@ class WebSocketManager:
|
|||||||
self.platform_listeners[f"京东:{store_id}"] = {
|
self.platform_listeners[f"京东:{store_id}"] = {
|
||||||
'thread': thread,
|
'thread': thread,
|
||||||
'platform': '京东',
|
'platform': '京东',
|
||||||
'store_id': store_id
|
'store_id': store_id,
|
||||||
|
'store_name': store_name # 保存店铺名称
|
||||||
}
|
}
|
||||||
|
|
||||||
# 上报连接状态给后端
|
# 上报连接状态给后端
|
||||||
@@ -431,7 +466,7 @@ class WebSocketManager:
|
|||||||
except Exception as send_e:
|
except Exception as send_e:
|
||||||
self._log(f"失败状态下报连接状态也失败: {send_e}", "ERROR")
|
self._log(f"失败状态下报连接状态也失败: {send_e}", "ERROR")
|
||||||
|
|
||||||
def _start_douyin_listener(self, store_id: str, cookies: str):
|
def _start_douyin_listener(self, store_id: str, cookies: str, store_name: str = ""):
|
||||||
"""启动抖音平台监听"""
|
"""启动抖音平台监听"""
|
||||||
try:
|
try:
|
||||||
def _runner():
|
def _runner():
|
||||||
@@ -502,6 +537,7 @@ class WebSocketManager:
|
|||||||
'thread': thread,
|
'thread': thread,
|
||||||
'platform': '抖音',
|
'platform': '抖音',
|
||||||
'store_id': store_id,
|
'store_id': store_id,
|
||||||
|
'store_name': store_name # 保存店铺名称
|
||||||
}
|
}
|
||||||
|
|
||||||
# 更新监听器状态
|
# 更新监听器状态
|
||||||
@@ -519,7 +555,7 @@ class WebSocketManager:
|
|||||||
# 🔥 移除:确保失败时也不在这里上报状态
|
# 🔥 移除:确保失败时也不在这里上报状态
|
||||||
# 失败状态应该在DyLogin中处理,与拼多多保持一致
|
# 失败状态应该在DyLogin中处理,与拼多多保持一致
|
||||||
|
|
||||||
def _start_qianniu_listener(self, store_id: str, cookies: str):
|
def _start_qianniu_listener(self, store_id: str, cookies: str, store_name: str = ""):
|
||||||
"""启动千牛平台监听(单连接多店铺架构)"""
|
"""启动千牛平台监听(单连接多店铺架构)"""
|
||||||
try:
|
try:
|
||||||
# 获取用户token(从后端客户端获取)
|
# 获取用户token(从后端客户端获取)
|
||||||
@@ -548,6 +584,7 @@ class WebSocketManager:
|
|||||||
'thread': thread,
|
'thread': thread,
|
||||||
'platform': '千牛',
|
'platform': '千牛',
|
||||||
'store_id': store_id,
|
'store_id': store_id,
|
||||||
|
'store_name': store_name, # 保存店铺名称
|
||||||
'exe_token': exe_token
|
'exe_token': exe_token
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +616,7 @@ class WebSocketManager:
|
|||||||
except Exception as send_e:
|
except Exception as send_e:
|
||||||
self._log(f"失败状态下报千牛平台连接状态也失败: {send_e}", "ERROR")
|
self._log(f"失败状态下报千牛平台连接状态也失败: {send_e}", "ERROR")
|
||||||
|
|
||||||
def _start_pdd_listener(self, store_id: str, data: str):
|
def _start_pdd_listener(self, store_id: str, data: str, store_name: str = ""):
|
||||||
"""启动拼多多平台监听"""
|
"""启动拼多多平台监听"""
|
||||||
try:
|
try:
|
||||||
def _runner():
|
def _runner():
|
||||||
@@ -671,6 +708,7 @@ class WebSocketManager:
|
|||||||
'thread': thread,
|
'thread': thread,
|
||||||
'platform': '拼多多',
|
'platform': '拼多多',
|
||||||
'store_id': store_id,
|
'store_id': store_id,
|
||||||
|
'store_name': store_name # 保存店铺名称
|
||||||
}
|
}
|
||||||
|
|
||||||
# ✅ 临时方案:启动后立即通知(因为 PDD 监听器会阻塞,无法通过返回值判断)
|
# ✅ 临时方案:启动后立即通知(因为 PDD 监听器会阻塞,无法通过返回值判断)
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import json # 用于将令牌保存为 JSON 格式
|
|||||||
|
|
||||||
# 后端服务器配置
|
# 后端服务器配置
|
||||||
# BACKEND_HOST = "192.168.5.233"
|
# BACKEND_HOST = "192.168.5.233"
|
||||||
BACKEND_HOST = "192.168.5.106"
|
# BACKEND_HOST = "192.168.5.106"
|
||||||
|
BACKEND_HOST = "192.168.5.12"
|
||||||
# BACKEND_HOST = "shuidrop.com"
|
# BACKEND_HOST = "shuidrop.com"
|
||||||
# BACKEND_HOST = "test.shuidrop.com"
|
# BACKEND_HOST = "test.shuidrop.com"
|
||||||
BACKEND_PORT = "8000"
|
BACKEND_PORT = "8000"
|
||||||
@@ -20,8 +21,8 @@ BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}"
|
|||||||
# WebSocket配置
|
# WebSocket配置
|
||||||
WS_CONNECT_TIMEOUT = 16.0
|
WS_CONNECT_TIMEOUT = 16.0
|
||||||
WS_MESSAGE_TIMEOUT = 30.0
|
WS_MESSAGE_TIMEOUT = 30.0
|
||||||
WS_PING_INTERVAL = 10 # 10秒ping间隔(提高检测频率)
|
WS_PING_INTERVAL = 15 # 10秒ping间隔(提高检测频率)
|
||||||
WS_PING_TIMEOUT = 5 # 5秒ping超时(更快检测断线)
|
WS_PING_TIMEOUT = 10 # 5秒ping超时(更快检测断线)
|
||||||
WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳
|
WS_ENABLE_PING = True # 是否启用WebSocket原生ping心跳
|
||||||
WS_ENABLE_APP_PING = False # 禁用应用层ping心跳(避免重复)
|
WS_ENABLE_APP_PING = False # 禁用应用层ping心跳(避免重复)
|
||||||
|
|
||||||
|
|||||||
131
main.py
131
main.py
@@ -613,6 +613,36 @@ class LoginWindow(QMainWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.add_log(f"处理平台连接事件失败: {e}", "ERROR")
|
self.add_log(f"处理平台连接事件失败: {e}", "ERROR")
|
||||||
|
|
||||||
|
def on_platform_kicked(self, platform_name: str, store_name: str, reason: str):
|
||||||
|
"""处理平台被踢下线 - 显示弹窗警告"""
|
||||||
|
try:
|
||||||
|
self.add_log(f"⚠️ {platform_name}平台被踢下线: {store_name}, 原因: {reason}", "WARNING")
|
||||||
|
|
||||||
|
# 显示弹窗提示
|
||||||
|
message_text = (
|
||||||
|
f"【{store_name}】连接已断开\n\n"
|
||||||
|
f"原因:{reason}\n\n"
|
||||||
|
f"请确保:\n"
|
||||||
|
f"1. 关闭网页版{platform_name}客户端\n"
|
||||||
|
f"2. 关闭其他{platform_name}客户端\n"
|
||||||
|
f"3. 确认只有本程序连接\n\n"
|
||||||
|
f"处理完成后,请重新登录平台。"
|
||||||
|
)
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
f"{platform_name}连接已断开",
|
||||||
|
message_text,
|
||||||
|
QMessageBox.Ok
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新状态显示
|
||||||
|
self.status_label.setText(f"⚠️ {platform_name}已断开")
|
||||||
|
self.status_label.setStyleSheet(
|
||||||
|
"color: #ff6b6b; background: rgba(255, 107, 107, 0.1); border-radius: 12px; padding: 5px 10px;")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.add_log(f"显示平台断开提示失败: {e}", "ERROR")
|
||||||
|
|
||||||
def on_token_error(self, error_content: str):
|
def on_token_error(self, error_content: str):
|
||||||
"""处理token错误 - 显示红色错误信息并停止所有操作"""
|
"""处理token错误 - 显示红色错误信息并停止所有操作"""
|
||||||
try:
|
try:
|
||||||
@@ -761,23 +791,16 @@ class LoginWindow(QMainWindow):
|
|||||||
self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
|
self.tray_icon.setIcon(self.style().standardIcon(self.style().SP_ComputerIcon))
|
||||||
|
|
||||||
# 创建托盘菜单
|
# 创建托盘菜单
|
||||||
tray_menu = QMenu()
|
self.tray_menu = QMenu()
|
||||||
|
|
||||||
# 显示窗口动作
|
# 连接菜单显示信号,实时更新状态
|
||||||
show_action = QAction("显示主窗口", self)
|
self.tray_menu.aboutToShow.connect(self.update_tray_menu)
|
||||||
show_action.triggered.connect(self.show_window)
|
|
||||||
tray_menu.addAction(show_action)
|
|
||||||
|
|
||||||
# 分隔线
|
# 初始化菜单内容
|
||||||
tray_menu.addSeparator()
|
self.update_tray_menu()
|
||||||
|
|
||||||
# 退出动作
|
|
||||||
quit_action = QAction("退出程序", self)
|
|
||||||
quit_action.triggered.connect(self.quit_application)
|
|
||||||
tray_menu.addAction(quit_action)
|
|
||||||
|
|
||||||
# 设置托盘菜单
|
# 设置托盘菜单
|
||||||
self.tray_icon.setContextMenu(tray_menu)
|
self.tray_icon.setContextMenu(self.tray_menu)
|
||||||
|
|
||||||
# 设置托盘提示
|
# 设置托盘提示
|
||||||
self.tray_icon.setToolTip("AI客服智能助手")
|
self.tray_icon.setToolTip("AI客服智能助手")
|
||||||
@@ -790,6 +813,87 @@ class LoginWindow(QMainWindow):
|
|||||||
|
|
||||||
print("[INFO] 系统托盘已初始化")
|
print("[INFO] 系统托盘已初始化")
|
||||||
|
|
||||||
|
def update_tray_menu(self):
|
||||||
|
"""实时更新托盘菜单,显示连接状态"""
|
||||||
|
try:
|
||||||
|
# 清空现有菜单
|
||||||
|
self.tray_menu.clear()
|
||||||
|
|
||||||
|
# 获取WebSocket管理器
|
||||||
|
ws_manager = get_websocket_manager()
|
||||||
|
|
||||||
|
# 1. 显示后端连接状态
|
||||||
|
if ws_manager and ws_manager.backend_client and ws_manager.backend_client.is_connected:
|
||||||
|
backend_status = QAction("✅ 后端已连接", self)
|
||||||
|
backend_status.setEnabled(False) # 不可点击
|
||||||
|
self.tray_menu.addAction(backend_status)
|
||||||
|
|
||||||
|
# 2. 显示已连接的平台信息
|
||||||
|
platform_listeners = ws_manager.platform_listeners
|
||||||
|
if platform_listeners:
|
||||||
|
# 按平台分组店铺
|
||||||
|
platform_stores = {}
|
||||||
|
for key, info in platform_listeners.items():
|
||||||
|
platform = info.get('platform', '')
|
||||||
|
store_name = info.get('store_name', '')
|
||||||
|
store_id = info.get('store_id', '')
|
||||||
|
|
||||||
|
if platform not in platform_stores:
|
||||||
|
platform_stores[platform] = []
|
||||||
|
|
||||||
|
# 优先显示店铺名称,如果没有则显示 store_id 前8位
|
||||||
|
if store_name:
|
||||||
|
display_name = store_name
|
||||||
|
else:
|
||||||
|
display_name = store_id[:8] + "..." if len(store_id) > 8 else store_id
|
||||||
|
|
||||||
|
platform_stores[platform].append(display_name)
|
||||||
|
|
||||||
|
# 显示每个平台的店铺
|
||||||
|
for platform, stores in platform_stores.items():
|
||||||
|
stores_text = ", ".join(stores)
|
||||||
|
platform_info = QAction(f"📊 {platform}: {stores_text}", self)
|
||||||
|
platform_info.setEnabled(False) # 不可点击
|
||||||
|
self.tray_menu.addAction(platform_info)
|
||||||
|
else:
|
||||||
|
# 后端已连接但没有平台连接
|
||||||
|
no_platform = QAction("⚠️ 暂无平台连接", self)
|
||||||
|
no_platform.setEnabled(False)
|
||||||
|
self.tray_menu.addAction(no_platform)
|
||||||
|
else:
|
||||||
|
# 后端未连接
|
||||||
|
backend_disconnected = QAction("❌ 后端未连接", self)
|
||||||
|
backend_disconnected.setEnabled(False)
|
||||||
|
self.tray_menu.addAction(backend_disconnected)
|
||||||
|
|
||||||
|
# 添加分隔线
|
||||||
|
self.tray_menu.addSeparator()
|
||||||
|
|
||||||
|
# 3. 显示主窗口
|
||||||
|
show_action = QAction("显示主窗口", self)
|
||||||
|
show_action.triggered.connect(self.show_window)
|
||||||
|
self.tray_menu.addAction(show_action)
|
||||||
|
|
||||||
|
# 添加分隔线
|
||||||
|
self.tray_menu.addSeparator()
|
||||||
|
|
||||||
|
# 4. 退出程序
|
||||||
|
quit_action = QAction("退出程序", self)
|
||||||
|
quit_action.triggered.connect(self.quit_application)
|
||||||
|
self.tray_menu.addAction(quit_action)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] 更新托盘菜单失败: {e}")
|
||||||
|
# 如果更新失败,至少保证基本功能可用
|
||||||
|
self.tray_menu.clear()
|
||||||
|
show_action = QAction("显示主窗口", self)
|
||||||
|
show_action.triggered.connect(self.show_window)
|
||||||
|
self.tray_menu.addAction(show_action)
|
||||||
|
|
||||||
|
quit_action = QAction("退出程序", self)
|
||||||
|
quit_action.triggered.connect(self.quit_application)
|
||||||
|
self.tray_menu.addAction(quit_action)
|
||||||
|
|
||||||
def tray_icon_activated(self, reason):
|
def tray_icon_activated(self, reason):
|
||||||
"""托盘图标被激活时的处理"""
|
"""托盘图标被激活时的处理"""
|
||||||
if reason == QSystemTrayIcon.DoubleClick:
|
if reason == QSystemTrayIcon.DoubleClick:
|
||||||
@@ -898,6 +1002,7 @@ class LoginWindow(QMainWindow):
|
|||||||
success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"),
|
success=lambda: self.add_log("WebSocket连接管理器连接成功", "SUCCESS"),
|
||||||
error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"),
|
error=lambda error: self.add_log(f"WebSocket连接管理器错误: {error}", "ERROR"),
|
||||||
platform_connected=self.on_platform_connected, # 新增:平台连接回调
|
platform_connected=self.on_platform_connected, # 新增:平台连接回调
|
||||||
|
platform_disconnected=self.on_platform_kicked, # 新增:平台被踢回调
|
||||||
token_error=self.on_token_error, # 新增:token错误回调
|
token_error=self.on_token_error, # 新增:token错误回调
|
||||||
disconnect=self.on_disconnect # 新增:被踢下线回调
|
disconnect=self.on_disconnect # 新增:被踢下线回调
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user