修复864登录异常退出并阻止空身份进入后台
This commit is contained in:
7
robot.py
7
robot.py
@@ -652,6 +652,13 @@ class Robot:
|
|||||||
2. provider 不应该知道本项目有哪些数据库表、后台缓存或插件系统;
|
2. provider 不应该知道本项目有哪些数据库表、后台缓存或插件系统;
|
||||||
3. 因此登录“流程”放到 provider,登录后的“业务初始化”继续留在 Robot。
|
3. 因此登录“流程”放到 provider,登录后的“业务初始化”继续留在 Robot。
|
||||||
"""
|
"""
|
||||||
|
# 这里再做一次项目侧兜底校验:
|
||||||
|
# 1. provider 已经会尽量保证只有“拿到可用身份”才会调进来;
|
||||||
|
# 2. 但 Robot 这一层承接的是联系人同步、插件注入、消息归档等重业务动作,不能接受空账号继续执行;
|
||||||
|
# 3. 因此只要 `wxid/nickname` 都为空,就立刻阻断后台初始化,强制回到扫码登录流程。
|
||||||
|
if not str(login_identity.get("wxid", "") or "").strip() and not str(login_identity.get("nickname", "") or "").strip():
|
||||||
|
raise RuntimeError("当前未拿到可用登录账号身份,已阻止进入后台初始化流程")
|
||||||
|
|
||||||
self.wxid = login_identity.get("wxid", "")
|
self.wxid = login_identity.get("wxid", "")
|
||||||
self.nickname = login_identity.get("nickname", "")
|
self.nickname = login_identity.get("nickname", "")
|
||||||
self.alias = login_identity.get("alias", "")
|
self.alias = login_identity.get("alias", "")
|
||||||
|
|||||||
@@ -101,7 +101,14 @@ class Server864RuntimeMixin:
|
|||||||
login_way=login_way,
|
login_way=login_way,
|
||||||
)
|
)
|
||||||
|
|
||||||
await on_login_ready(self.get_login_identity())
|
login_identity = self.get_login_identity()
|
||||||
|
# 在 provider 侧再做一次硬校验:
|
||||||
|
# 1. `_ensure_login()` 已经负责等待真正可用的登录态,但运行期代码后续可能继续演进;
|
||||||
|
# 2. 这里补一道收口保护,可以避免未来某次重构把“空身份”再次漏进 Robot 业务层;
|
||||||
|
# 3. 一旦这里失败,说明当前 provider 还没有拿到稳定账号身份,必须继续停留在登录阶段。
|
||||||
|
if not self._has_login_identity(login_identity):
|
||||||
|
raise RuntimeError("server_864 登录流程未拿到可用账号身份,已阻止进入后台业务初始化")
|
||||||
|
await on_login_ready(login_identity)
|
||||||
logger.info("server_864 登录成功")
|
logger.info("server_864 登录成功")
|
||||||
|
|
||||||
await self._set_runtime_running(True, on_runtime_state_change=on_runtime_state_change, logger=logger)
|
await self._set_runtime_running(True, on_runtime_state_change=on_runtime_state_change, logger=logger)
|
||||||
@@ -175,10 +182,11 @@ class Server864RuntimeMixin:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 先探测一次 864 当前阶段,让 Dashboard 能直接区分“等服务端准备”和“需要扫码”:
|
while True:
|
||||||
# 1. 864 的未登录态并不只有一种,部分场景其实是远端连接对象还没建好;
|
# 这里把“申请二维码 -> 轮询扫码 -> 登录收口”包成一轮完整会话:
|
||||||
# 2. 若首页始终只显示“未登录”,运维很难判断下一步是等服务端还是去扫码;
|
# 1. 864 某些失败不是用户操作问题,而是服务端在扫码后主动中断当前连接;
|
||||||
# 3. 这里把差异压缩成轻量阶段字段,供前端直接展示,不改动核心登录流程。
|
# 2. 对这类场景,最合理的行为不是让线程退出,而是把错误留在前端并回到下一轮扫码;
|
||||||
|
# 3. 这样可以满足“没登录成功就不要进入后台,但页面持续引导重新登录”的产品预期。
|
||||||
login_stage_snapshot = await self._probe_login_stage()
|
login_stage_snapshot = await self._probe_login_stage()
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
@@ -189,6 +197,8 @@ class Server864RuntimeMixin:
|
|||||||
|
|
||||||
uuid, url = await self.get_qr_code(print_qr=True, login_qr_api=login_qr_api, login_way=login_way)
|
uuid, url = await self.get_qr_code(print_qr=True, login_qr_api=login_qr_api, login_way=login_way)
|
||||||
scan_url = f"http://weixin.qq.com/x/{uuid}" if uuid else ""
|
scan_url = f"http://weixin.qq.com/x/{uuid}" if uuid else ""
|
||||||
|
effective_time = 0
|
||||||
|
raw_state = 0
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
{
|
{
|
||||||
@@ -210,15 +220,16 @@ class Server864RuntimeMixin:
|
|||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
login_completed = False
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
is_logged_in, login_status = await self.check_login_status()
|
is_logged_in, login_status = await self.check_login_status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = self._normalize_login_runtime_message(str(e))
|
error_message = self._normalize_login_runtime_message(str(e))
|
||||||
# 登录轮询阶段一旦出现明确错误,应立刻同步到 Dashboard:
|
# 轮询阶段失败时只更新前端,不中断 provider 主线程:
|
||||||
# 1. 之前这类异常只会停在控制台日志,前端继续显示旧的“等待扫码/等待验证”文案;
|
# 1. 线上最常见的是本轮登录连接已被服务端丢弃,继续崩线程只会让运维反复重启;
|
||||||
# 2. 用户已经扫码后,最需要的是第一时间知道“为什么卡住了”;
|
# 2. 这里先把明确错误展示给前端,再回到外层重新申请二维码;
|
||||||
# 3. 因此这里把错误直接回写成登录态,让弹窗成为当前环境登录的真实看板。
|
# 3. 这样用户在新环境里可以直接继续扫码,不需要手工重启 ABOT。
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
{
|
{
|
||||||
@@ -238,7 +249,10 @@ class Server864RuntimeMixin:
|
|||||||
logger=logger,
|
logger=logger,
|
||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
raise RuntimeError(error_message) from e
|
logger.warning(f"server_864 登录状态轮询失败,本轮二维码作废并等待重新登录: {error_message}")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
break
|
||||||
|
|
||||||
if is_logged_in:
|
if is_logged_in:
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
@@ -258,13 +272,14 @@ class Server864RuntimeMixin:
|
|||||||
"provider_stage": "login_finalizing",
|
"provider_stage": "login_finalizing",
|
||||||
"connection_ready": True,
|
"connection_ready": True,
|
||||||
"login_required": False,
|
"login_required": False,
|
||||||
"raw_state": 2,
|
"raw_state": max(raw_state, 2),
|
||||||
"login_qr_api": login_qr_api,
|
"login_qr_api": login_qr_api,
|
||||||
"login_way": login_way,
|
"login_way": login_way,
|
||||||
},
|
},
|
||||||
logger=logger,
|
logger=logger,
|
||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
|
login_completed = True
|
||||||
break
|
break
|
||||||
|
|
||||||
# 864 的登录状态查询会回传当前 uuid 和有效期:
|
# 864 的登录状态查询会回传当前 uuid 和有效期:
|
||||||
@@ -354,13 +369,11 @@ class Server864RuntimeMixin:
|
|||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 若 server 已明确告知二维码失效或停在终态缓存,则立即重新申请一张新码:
|
# 若 server 已明确告知二维码失效或停在终态缓存,则立即结束本轮、回到外层申请新二维码:
|
||||||
# 1. 这能避免 Dashboard 一直展示一张已经不可扫的旧二维码;
|
# 1. 这能避免 Dashboard 一直展示一张已经不可扫的旧二维码;
|
||||||
# 2. 864 某些版本即使 `effective_time` 还大于 0,也可能已经停在 `state=4` 终态缓存里;
|
# 2. 864 某些版本即使 `effective_time` 还大于 0,也可能已经停在 `state=4` 终态缓存里;
|
||||||
# 3. 因此这里补充 `raw_state == 4` 的刷新条件,让前端能尽快得到新的登录入口。
|
# 3. 因此这里补充 `raw_state == 4` 的刷新条件,让前端能尽快得到新的登录入口。
|
||||||
if effective_time <= 0 or raw_state == 4:
|
if effective_time <= 0 or raw_state == 4:
|
||||||
uuid, url = await self.get_qr_code(print_qr=True, login_qr_api=login_qr_api, login_way=login_way)
|
|
||||||
scan_url = f"http://weixin.qq.com/x/{uuid}" if uuid else ""
|
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
{
|
{
|
||||||
@@ -369,7 +382,7 @@ class Server864RuntimeMixin:
|
|||||||
"scan_url": scan_url,
|
"scan_url": scan_url,
|
||||||
"expires_in": None,
|
"expires_in": None,
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"status_text": "二维码已刷新,等待扫码登录",
|
"status_text": "二维码已失效,正在准备新二维码",
|
||||||
"login_source": "refresh_qr",
|
"login_source": "refresh_qr",
|
||||||
"provider_name": "server_864",
|
"provider_name": "server_864",
|
||||||
"provider_stage": "waiting_scan",
|
"provider_stage": "waiting_scan",
|
||||||
@@ -381,8 +394,12 @@ class Server864RuntimeMixin:
|
|||||||
logger=logger,
|
logger=logger,
|
||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
|
break
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
if not login_completed:
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._wait_init_ready(logger=logger)
|
await self._wait_init_ready(logger=logger)
|
||||||
identity_ready = await self._refresh_identity_from_profile(logger=logger)
|
identity_ready = await self._refresh_identity_from_profile(logger=logger)
|
||||||
@@ -395,12 +412,13 @@ class Server864RuntimeMixin:
|
|||||||
state_payload={"wxid": self.wxid, "login_time": ipad_config["login_time"]},
|
state_payload={"wxid": self.wxid, "login_time": ipad_config["login_time"]},
|
||||||
logger=logger,
|
logger=logger,
|
||||||
)
|
)
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = self._normalize_post_scan_failure_message(str(e))
|
error_message = self._normalize_post_scan_failure_message(str(e))
|
||||||
# 扫码成功后的收口阶段同样需要把异常同步给前端:
|
# 扫码成功后的收口阶段同样需要把异常同步给前端:
|
||||||
# 1. 这类错误往往发生在“state=2 之后但正式进入业务前”,最容易被误判成前端没刷新;
|
# 1. 这类错误往往发生在“state=2 之后但正式进入业务前”,最容易被误判成前端没刷新;
|
||||||
# 2. 例如当前用户遇到的“客户端版本过低”就是在这个阶段由服务端主动断开;
|
# 2. 例如当前用户遇到的“客户端版本过低”就是在这个阶段由服务端主动断开;
|
||||||
# 3. 因此这里明确把阶段标成 `status_unavailable`,让弹窗继续停留并展示真实原因。
|
# 3. 这里不再直接抛异常退出,而是把错误留在弹窗里并继续等待下一轮扫码。
|
||||||
await self._safe_callback(
|
await self._safe_callback(
|
||||||
on_login_qr_update,
|
on_login_qr_update,
|
||||||
{
|
{
|
||||||
@@ -414,14 +432,25 @@ class Server864RuntimeMixin:
|
|||||||
"provider_stage": "status_unavailable",
|
"provider_stage": "status_unavailable",
|
||||||
"connection_ready": False,
|
"connection_ready": False,
|
||||||
"login_required": True,
|
"login_required": True,
|
||||||
"raw_state": 2,
|
"raw_state": max(raw_state, 2),
|
||||||
"login_qr_api": login_qr_api,
|
"login_qr_api": login_qr_api,
|
||||||
"login_way": login_way,
|
"login_way": login_way,
|
||||||
},
|
},
|
||||||
logger=logger,
|
logger=logger,
|
||||||
callback_name="on_login_qr_update",
|
callback_name="on_login_qr_update",
|
||||||
)
|
)
|
||||||
raise RuntimeError(error_message) from e
|
logger.warning(f"server_864 登录收口失败,继续停留在扫码引导态等待重新登录: {error_message}")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_login_identity(login_identity: dict[str, Any] | None) -> bool:
|
||||||
|
"""判断当前 provider 是否已经拿到可用于进入业务层的账号身份。"""
|
||||||
|
payload = dict(login_identity or {})
|
||||||
|
# 这里把“可用身份”的标准压到最小:
|
||||||
|
# 1. 864 有些接口可能先拿到昵称、后拿到 wxid,也可能顺序相反;
|
||||||
|
# 2. 但两者都为空时,业务层无法知道当前到底是谁登录了;
|
||||||
|
# 3. 因此至少要拿到 `wxid` 或 `nickname` 之一,才允许继续进入 Robot 初始化。
|
||||||
|
return bool(str(payload.get("wxid", "") or "").strip() or str(payload.get("nickname", "") or "").strip())
|
||||||
|
|
||||||
async def _probe_login_stage(self) -> dict[str, Any]:
|
async def _probe_login_stage(self) -> dict[str, Any]:
|
||||||
"""探测 864 当前登录阶段,供 Dashboard 展示更准确的运维状态。"""
|
"""探测 864 当前登录阶段,供 Dashboard 展示更准确的运维状态。"""
|
||||||
|
|||||||
Reference in New Issue
Block a user