支持864登录二维码切换与退出重登
This commit is contained in:
@@ -476,6 +476,61 @@ def api_login_qr_status():
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@robot_bp.route('/api/login_qr_mode', methods=['POST'])
|
||||
@login_required
|
||||
def api_switch_login_qr_mode():
|
||||
"""切换 864 登录二维码模式,并重启登录流程。"""
|
||||
try:
|
||||
server = current_app.dashboard_server
|
||||
robot = getattr(server, "robot", None)
|
||||
if robot is None:
|
||||
return jsonify({"success": False, "error": "机器人实例不可用"}), 500
|
||||
|
||||
payload = request.get_json(silent=True) or {}
|
||||
login_qr_api = str(payload.get("login_qr_api", "") or "").strip()
|
||||
login_way = str(payload.get("login_way", "") or "").strip()
|
||||
result = robot.switch_server_864_login_entry(
|
||||
login_qr_api=login_qr_api,
|
||||
login_way=login_way or None,
|
||||
do_logout=True,
|
||||
)
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "864 登录二维码模式已切换,新的登录线程正在启动",
|
||||
"data": {
|
||||
**result,
|
||||
"login_qr_state": _serialize_login_qr_state(server),
|
||||
},
|
||||
})
|
||||
except Exception as e:
|
||||
LOG.error(f"切换 864 登录二维码模式失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@robot_bp.route('/api/logout_864', methods=['POST'])
|
||||
@login_required
|
||||
def api_logout_864():
|
||||
"""退出当前 864 登录态,并重新进入二维码登录引导。"""
|
||||
try:
|
||||
server = current_app.dashboard_server
|
||||
robot = getattr(server, "robot", None)
|
||||
if robot is None:
|
||||
return jsonify({"success": False, "error": "机器人实例不可用"}), 500
|
||||
|
||||
result = robot.logout_server_864_and_restart_login()
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "864 已退出当前登录态,新的二维码登录流程正在启动",
|
||||
"data": {
|
||||
**result,
|
||||
"login_qr_state": _serialize_login_qr_state(server),
|
||||
},
|
||||
})
|
||||
except Exception as e:
|
||||
LOG.error(f"退出 864 登录态失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
# API路由
|
||||
@robot_bp.route('/api/groups')
|
||||
@login_required
|
||||
|
||||
@@ -187,6 +187,28 @@
|
||||
</div>
|
||||
<div class="login-qr-dialog__actions">
|
||||
<el-button size="mini" @click="loadLoginQrStatus(true)">立即刷新状态</el-button>
|
||||
<el-button
|
||||
v-if="isServer864Login"
|
||||
size="mini"
|
||||
:loading="loginQrActionLoading === 'harmony_api'"
|
||||
@click="switch864LoginMode('harmony_api', 'harmony')">
|
||||
鸿蒙二维码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isServer864Login"
|
||||
size="mini"
|
||||
:loading="loginQrActionLoading === 'new'"
|
||||
@click="switch864LoginMode('new')">
|
||||
标准New二维码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isServer864Login"
|
||||
size="mini"
|
||||
type="danger"
|
||||
:loading="loginQrActionLoading === 'logout_864'"
|
||||
@click="logout864Login">
|
||||
退出864登录
|
||||
</el-button>
|
||||
<el-button v-if="loginQrCurrent.scan_url" type="text" @click="copyLoginQrScanUrl">
|
||||
复制扫码链接
|
||||
</el-button>
|
||||
@@ -586,6 +608,7 @@
|
||||
server_now: 0
|
||||
},
|
||||
loginQrCountdownSeconds: 0,
|
||||
loginQrActionLoading: '',
|
||||
groups: [],
|
||||
selectedGroupForHourlyTrend: '',
|
||||
hourlyTrendDays: 1,
|
||||
@@ -622,6 +645,10 @@
|
||||
loginQrCurrent() {
|
||||
return this.loginQrDialog.current || {};
|
||||
},
|
||||
isServer864Login() {
|
||||
const providerName = String(this.loginQrDialog.provider_name || '').trim().toLowerCase();
|
||||
return providerName === 'server_864' || providerName === '864';
|
||||
},
|
||||
showLoginQrBanner() {
|
||||
return !this.loginQrDialog.logged_in;
|
||||
},
|
||||
@@ -971,6 +998,63 @@
|
||||
this.loginQrDialog.loading = false;
|
||||
});
|
||||
},
|
||||
switch864LoginMode(loginQrApi, loginWay = '') {
|
||||
if (!this.isServer864Login) {
|
||||
this.$message.warning('当前不是 864 登录模式,暂不支持切换二维码入口');
|
||||
return;
|
||||
}
|
||||
this.loginQrActionLoading = loginQrApi;
|
||||
axios.post('/robot/api/login_qr_mode', {
|
||||
login_qr_api: loginQrApi,
|
||||
login_way: loginWay
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const state = (((response.data || {}).data || {}).login_qr_state) || {};
|
||||
if (state && Object.keys(state).length > 0) {
|
||||
this.applyLoginQrState(state);
|
||||
}
|
||||
this.$message.success(response.data.message || '864 登录二维码入口已切换');
|
||||
setTimeout(() => this.loadLoginQrStatus(true), 1200);
|
||||
return;
|
||||
}
|
||||
this.$message.error(response.data.error || '切换 864 登录二维码入口失败');
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = (((error || {}).response || {}).data || {}).error || '切换 864 登录二维码入口失败';
|
||||
this.$message.error(errorMessage);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loginQrActionLoading = '';
|
||||
});
|
||||
},
|
||||
logout864Login() {
|
||||
if (!this.isServer864Login) {
|
||||
this.$message.warning('当前不是 864 登录模式,暂不支持此操作');
|
||||
return;
|
||||
}
|
||||
this.loginQrActionLoading = 'logout_864';
|
||||
axios.post('/robot/api/logout_864')
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const state = (((response.data || {}).data || {}).login_qr_state) || {};
|
||||
if (state && Object.keys(state).length > 0) {
|
||||
this.applyLoginQrState(state);
|
||||
}
|
||||
this.$message.success(response.data.message || '864 已退出当前登录态');
|
||||
setTimeout(() => this.loadLoginQrStatus(true), 1200);
|
||||
return;
|
||||
}
|
||||
this.$message.error(response.data.error || '退出 864 登录态失败');
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = (((error || {}).response || {}).data || {}).error || '退出 864 登录态失败';
|
||||
this.$message.error(errorMessage);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loginQrActionLoading = '';
|
||||
});
|
||||
},
|
||||
tickLoginQrCountdown() {
|
||||
if (this.loginQrDialog.logged_in) {
|
||||
this.loginQrCountdownSeconds = 0;
|
||||
|
||||
99
robot.py
99
robot.py
@@ -927,9 +927,106 @@ class Robot:
|
||||
if hasattr(self, "ipad_bot") and self.ipad_bot and hasattr(self.ipad_bot, "stop_runtime"):
|
||||
self.ipad_bot.stop_runtime()
|
||||
if self.ipad_loop:
|
||||
self.ipad_loop.stop()
|
||||
try:
|
||||
# 事件循环运行在独立线程里,主线程这里需要走线程安全停止:
|
||||
# 1. 直接 `loop.stop()` 在某些时机会留下竞态,导致旧线程迟迟不退出;
|
||||
# 2. Dashboard 现在需要支持“不重启主进程,直接切换 864 登录模式”;
|
||||
# 3. 因此这里改成 `call_soon_threadsafe`,让旧 provider 能更稳地收尾退出。
|
||||
self.ipad_loop.call_soon_threadsafe(self.ipad_loop.stop)
|
||||
except Exception:
|
||||
self.ipad_loop.stop()
|
||||
if self.ipad_thread and self.ipad_thread.is_alive():
|
||||
self.ipad_thread.join(timeout=5)
|
||||
self.ipad_thread = None
|
||||
self.ipad_loop = None
|
||||
self.ipad_bot = None
|
||||
self.LOG.info("wechat_ipad客户端已停止")
|
||||
|
||||
def switch_server_864_login_entry(self, *, login_qr_api: str, login_way: str | None = None, do_logout: bool = True) -> dict:
|
||||
"""切换 864 登录入口并重启登录流程。
|
||||
|
||||
设计说明:
|
||||
1. 用户希望在后台直接切换“鸿蒙专用二维码”和“标准 New 二维码”,而不是改 `.env` 后重启整套服务;
|
||||
2. 当前 864 runtime 在独立线程里长驻运行,最稳妥的切换方式是“更新配置 -> 退出旧登录态 -> 重启 provider 登录线程”;
|
||||
3. 这样实现虽然比纯运行时热切换更直接,但代码层次更浅,也更便于后续继续接其他 server 变体。
|
||||
"""
|
||||
if not isinstance(self.ipad_config, dict):
|
||||
raise RuntimeError("wechat_ipad 运行时配置尚未初始化,暂时无法切换 864 登录入口")
|
||||
|
||||
current_server_type = self._normalize_wechat_provider_key(self.ipad_config.get("server_type", "legacy_855"))
|
||||
if current_server_type != "server_864":
|
||||
raise RuntimeError("当前仅支持在 server_864 模式下切换登录二维码入口")
|
||||
|
||||
normalized_login_qr_api = str(login_qr_api or "").strip().lower()
|
||||
if normalized_login_qr_api not in {"harmony_api", "new", "new_x"}:
|
||||
raise ValueError(f"不支持的 864 登录入口模式: {login_qr_api}")
|
||||
|
||||
normalized_login_way = str(login_way or self.ipad_config.get("login_way", "mac") or "mac").strip().lower()
|
||||
if normalized_login_qr_api == "harmony_api":
|
||||
normalized_login_way = "harmony"
|
||||
|
||||
if do_logout and self.ipad_bot and self.ipad_loop:
|
||||
try:
|
||||
logout_future = asyncio.run_coroutine_threadsafe(self.ipad_bot.log_out(), self.ipad_loop)
|
||||
logout_future.result(timeout=20)
|
||||
except Exception as e:
|
||||
# 切换登录模式时,退出旧会话失败不应阻断后续重启:
|
||||
# 1. 某些 864 版本在会话已失效时会直接返回错误;
|
||||
# 2. 用户真正关心的是“后台能否尽快切到新的二维码入口”;
|
||||
# 3. 因此这里记录日志后继续向下执行重启流程。
|
||||
self.LOG.warning(f"切换 864 登录入口前执行退出请求失败,继续重启登录流程: {e}")
|
||||
|
||||
self.ipad_config["login_qr_api"] = normalized_login_qr_api
|
||||
self.ipad_config["login_way"] = normalized_login_way
|
||||
if isinstance(getattr(self.config, "wechat_ipad", None), dict):
|
||||
self.config.wechat_ipad["login_qr_api"] = normalized_login_qr_api
|
||||
self.config.wechat_ipad["login_way"] = normalized_login_way
|
||||
|
||||
self._clear_ipad_identity_cache()
|
||||
switch_label_map = {
|
||||
"harmony_api": "864 鸿蒙专用二维码",
|
||||
"new": "864 标准 New 二维码",
|
||||
"new_x": f"864 NewX 二维码({normalized_login_way})",
|
||||
}
|
||||
with self._ipad_login_qr_lock:
|
||||
self.ipad_login_qr_state = {
|
||||
"logged_in": False,
|
||||
"active": True,
|
||||
"status": "waiting",
|
||||
"provider_name": "server_864",
|
||||
"provider_stage": "login_required",
|
||||
"connection_ready": False,
|
||||
"login_required": True,
|
||||
"status_text": f"正在切换到{switch_label_map.get(normalized_login_qr_api, normalized_login_qr_api)},请稍候刷新二维码",
|
||||
"current": {
|
||||
"login_qr_api": normalized_login_qr_api,
|
||||
"login_way": normalized_login_way,
|
||||
"updated_at": time.time(),
|
||||
"updated_at_text": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
},
|
||||
"history": [],
|
||||
"updated_at": time.time(),
|
||||
}
|
||||
|
||||
self.stop_wechat_ipad()
|
||||
if not self.init_wechat_ipad():
|
||||
raise RuntimeError("重启 wechat_ipad 登录线程失败,请检查 864 服务端与配置")
|
||||
|
||||
return {
|
||||
"server_type": "server_864",
|
||||
"login_qr_api": normalized_login_qr_api,
|
||||
"login_way": normalized_login_way,
|
||||
"message": switch_label_map.get(normalized_login_qr_api, normalized_login_qr_api),
|
||||
}
|
||||
|
||||
def logout_server_864_and_restart_login(self) -> dict:
|
||||
"""退出当前 864 登录态,并按现有二维码入口重新进入登录引导。"""
|
||||
return self.switch_server_864_login_entry(
|
||||
login_qr_api=str(self.ipad_config.get("login_qr_api", "new_x") or "new_x"),
|
||||
login_way=str(self.ipad_config.get("login_way", "mac") or "mac"),
|
||||
do_logout=True,
|
||||
)
|
||||
|
||||
def keep_running_and_block_process(self) -> None:
|
||||
"""
|
||||
保持机器人运行,不让进程退出
|
||||
|
||||
@@ -105,7 +105,19 @@ class LoginMixin(Server864APIClientBase):
|
||||
normalized_login_qr_api = str(login_qr_api or "new_x").strip().lower()
|
||||
normalized_login_way = self._normalize_login_way(login_way)
|
||||
|
||||
if normalized_login_qr_api in {"new_x", "x", "newx"}:
|
||||
if normalized_login_qr_api in {"harmony_api", "harmony", "harmony_login_api"}:
|
||||
# `HarmonyLoginApi` 是 864 服务端单独暴露的一条鸿蒙二维码链路:
|
||||
# 1. 用户当前希望在 Dashboard 上显式切换“鸿蒙专用二维码”和“标准 New 二维码”;
|
||||
# 2. 该接口的请求模型与 `GetLoginQrCodeNew` 一致,仍然只需要 `Proxy/Check`;
|
||||
# 3. 因此这里单独加一个模式键,不去复用 `new_x + way=harmony`,避免两条链路在运维上混淆。
|
||||
data = await self._request_data(
|
||||
"post",
|
||||
"/login/HarmonyLoginApi",
|
||||
json_body={"Proxy": proxy_value, "Check": False},
|
||||
timeout=30,
|
||||
)
|
||||
uuid, qr_url = self._extract_qr_response(data)
|
||||
elif normalized_login_qr_api in {"new_x", "x", "newx"}:
|
||||
try:
|
||||
# NewX 是当前 864 联调里更完整的一条登录链路:
|
||||
# 1. 它支持 `Way` 指定登录端形态,兼容更多 server 变体;
|
||||
@@ -126,6 +138,14 @@ class LoginMixin(Server864APIClientBase):
|
||||
timeout=30,
|
||||
)
|
||||
uuid, qr_url = self._extract_qr_response(data)
|
||||
elif normalized_login_qr_api in {"new", "legacy_new"}:
|
||||
data = await self._request_data(
|
||||
"post",
|
||||
"/login/GetLoginQrCodeNew",
|
||||
json_body={"Proxy": proxy_value, "Check": False},
|
||||
timeout=30,
|
||||
)
|
||||
uuid, qr_url = self._extract_qr_response(data)
|
||||
else:
|
||||
data = await self._request_data(
|
||||
"post",
|
||||
@@ -211,5 +231,12 @@ class LoginMixin(Server864APIClientBase):
|
||||
|
||||
async def log_out(self) -> bool:
|
||||
"""退出当前 864 登录态。"""
|
||||
await self._request_data("get", "/login/LogOutRequest", timeout=15)
|
||||
try:
|
||||
# 不同 864 版本里退出接口命名存在差异:
|
||||
# 1. 当前项目早期接的是 `/login/LogOutRequest`;
|
||||
# 2. 用户本地 864 源码中实际注册的是 `/login/LogOut`;
|
||||
# 3. 因此这里优先尝试较新的 `/LogOut`,失败后再回退到旧路径,降低版本切换成本。
|
||||
await self._request_data("get", "/login/LogOut", timeout=15)
|
||||
except Exception:
|
||||
await self._request_data("get", "/login/LogOutRequest", timeout=15)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user