优化微信同步超时兜底并下沉头像缓存预热
- 为 Msg/Sync 增加超时异常与主循环重试保护,避免启动阶段超时直接退出\n- 新增联系人头像缓存系统定时任务,启动时不再主动批量下载头像\n- 保留头像按需补下载能力,并补充详细中文注释
This commit is contained in:
90
robot.py
90
robot.py
@@ -60,6 +60,9 @@ class Robot:
|
||||
self.nickname = None
|
||||
self.alias = None
|
||||
self.phone = None
|
||||
# 连续同步超时计数单独保留,目的是区分“偶发网络抖动”和“服务端已经持续不可用”。
|
||||
# 这样后续日志可以更明确地告诉我们当前是第几次连续超时,排障时不需要手工数日志。
|
||||
self.ipad_sync_timeout_streak = 0
|
||||
self.message_auto_revoke: MessageAutoRevoke = None
|
||||
self.LOG.debug(f"DB+REDIS 连接池开始初始化")
|
||||
# 使用单例模式获取实例
|
||||
@@ -303,8 +306,11 @@ class Robot:
|
||||
# await self.ipad_bot.send_text_message("filehelper", "ipad客户端启动成功")
|
||||
count = 0
|
||||
while True:
|
||||
data = await self.ipad_bot.sync_message()
|
||||
data = data.get("AddMsgs")
|
||||
data_temp = await self._sync_ipad_messages_with_guard("处理堆积消息")
|
||||
if data_temp is None:
|
||||
continue
|
||||
|
||||
data = data_temp.get("AddMsgs")
|
||||
if not data:
|
||||
if count > 2:
|
||||
break
|
||||
@@ -324,17 +330,8 @@ class Robot:
|
||||
# 开始处理消息
|
||||
self.LOG.info("开始处理wechat_ipad消息")
|
||||
while self.ipad_running:
|
||||
try:
|
||||
data_temp = await self.ipad_bot.sync_message()
|
||||
except Exception as e:
|
||||
self.LOG.error(f"获取新消息失败 {e}")
|
||||
if "用户可能退出" in str(e):
|
||||
self.LOG.error(f"用户可能退出: {e}")
|
||||
self.email_sender.send_wechat_alert(self.config.email.get("alert_recipient"),
|
||||
f"用户可能退出: {e}", self.wxid,
|
||||
self.nickname)
|
||||
await self.login_twice_auto_auth()
|
||||
await asyncio.sleep(5)
|
||||
data_temp = await self._sync_ipad_messages_with_guard("消息主循环")
|
||||
if data_temp is None:
|
||||
continue
|
||||
|
||||
data = data_temp.get("AddMsgs")
|
||||
@@ -384,6 +381,55 @@ class Robot:
|
||||
self.LOG.exception(f"wechat_ipad客户端运行出错: {e}")
|
||||
self.ipad_running = False
|
||||
|
||||
async def _sync_ipad_messages_with_guard(self, phase: str) -> dict | None:
|
||||
"""统一封装 wechat_ipad 的消息同步调用。
|
||||
|
||||
设计说明:
|
||||
1. 启动阶段清空堆积消息和运行阶段实时拉消息,本质上调用的是同一个 `/api/Msg/Sync`;
|
||||
2. 过去两处各自直接调用 `sync_message()`,导致启动阶段一旦超时就会把整个主循环打断;
|
||||
3. 现在把重试、连续超时计数、掉线自愈放在一起,后续如果策略要调整,只改这一处即可。
|
||||
"""
|
||||
try:
|
||||
data = await self.ipad_bot.sync_message()
|
||||
if self.ipad_sync_timeout_streak > 0:
|
||||
# 这里在恢复成功时主动打一条恢复日志,方便和前面的连续超时告警配对查看。
|
||||
self.LOG.info(
|
||||
f"{phase}同步消息恢复正常,已清空连续超时计数: {self.ipad_sync_timeout_streak}"
|
||||
)
|
||||
self.ipad_sync_timeout_streak = 0
|
||||
return data
|
||||
except wechat_ipad.RequestTimeoutError as timeout_error:
|
||||
# 对同步超时做“警告级别 + 连续计数”处理,而不是直接当致命错误退出:
|
||||
# 1. 局域网环境下偶发抖动、服务端短暂卡顿都可能触发超时;
|
||||
# 2. 这类问题大多数可通过下一轮重试自动恢复;
|
||||
# 3. 只有保留连续次数,我们才能快速判断是偶发还是持续故障。
|
||||
self.ipad_sync_timeout_streak += 1
|
||||
self.LOG.warning(
|
||||
f"{phase}同步消息超时,第 {self.ipad_sync_timeout_streak} 次连续超时: {timeout_error}"
|
||||
)
|
||||
except Exception as e:
|
||||
if self.ipad_sync_timeout_streak > 0:
|
||||
# 非超时异常说明故障类型已经变化,先把超时计数归零,避免后续日志语义混乱。
|
||||
self.LOG.warning(
|
||||
f"{phase}同步消息异常类型已变化,重置连续超时计数: {self.ipad_sync_timeout_streak}"
|
||||
)
|
||||
self.ipad_sync_timeout_streak = 0
|
||||
|
||||
self.LOG.error(f"{phase}获取新消息失败: {e}")
|
||||
if "用户可能退出" in str(e):
|
||||
self.LOG.error(f"用户可能退出: {e}")
|
||||
self.email_sender.send_wechat_alert(
|
||||
self.config.email.get("alert_recipient"),
|
||||
f"用户可能退出: {e}",
|
||||
self.wxid,
|
||||
self.nickname
|
||||
)
|
||||
await self.login_twice_auto_auth()
|
||||
|
||||
# 这里统一等待 5 秒再重试,避免在服务端异常时进入高频空转。
|
||||
await asyncio.sleep(5)
|
||||
return None
|
||||
|
||||
# 在类里直接写一个内联 async 方法(不额外抽取新的对外方法)
|
||||
|
||||
async def _process_with_semaphore(self, wxmsg):
|
||||
@@ -1087,3 +1133,21 @@ class Robot:
|
||||
self.message_storage.write_to_db()
|
||||
except Exception as e:
|
||||
self.LOG.error(f"write_to_db error:{e}")
|
||||
|
||||
async def sync_contact_avatar_cache(self) -> None:
|
||||
"""系统级定时任务:增量同步联系人头像缓存。
|
||||
|
||||
说明:
|
||||
1. 头像缓存预热从登录启动链路挪到这里,避免机器人刚上线时就批量下载头像;
|
||||
2. `ContactManager.sync_avatar_cache()` 仍是同步 I/O,因此这里用 `asyncio.to_thread` 丢到线程池执行;
|
||||
3. 这样既保留定时批处理能力,也不会阻塞 async_job 所在的事件循环。
|
||||
"""
|
||||
try:
|
||||
stats = await asyncio.to_thread(
|
||||
self.contact_manager.sync_avatar_cache,
|
||||
"system_job:sync_contact_avatar_cache",
|
||||
)
|
||||
self.LOG.info(f"联系人头像缓存定时同步完成: {stats}")
|
||||
except Exception as e:
|
||||
self.LOG.error(f"sync_contact_avatar_cache error: {e}")
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user