主要变更: 1. 新增UIActionScheduler实现优先级UI任务调度,支持CRITICAL/HIGH/NORMAL/LOW四级优先级 2. 替换原有SendQueue为UIActionScheduler,统一所有UI操作的调度逻辑 3. 为截图、登录、重启等API添加调度器封装,支持超时、限流与fast-fail机制 4. 新增Prometheus监控指标,统计UI任务执行、等待耗时、超时、限流与快速失败情况 5. 为QrCapture添加命令执行超时保护,避免X11操作卡死 6. 扩展StatusResponse与状态接口,暴露调度器指标 7. 新增完整的单元测试覆盖调度器功能 8. 为看门狗注入调度器,实现kill前队列清空与快速失败窗口
117 lines
4.8 KiB
Python
117 lines
4.8 KiB
Python
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import logging
|
||
import time
|
||
|
||
from fastapi import APIRouter
|
||
|
||
from woc_bridge.config import _state, _require_xdotool, _require_db_reader
|
||
from woc_bridge.db.coordinator import with_db_retry, _resolve_db_state
|
||
from woc_bridge.models import StatusResponse
|
||
from woc_bridge.ui.metrics import send_queue_pending as send_queue_pending_gauge
|
||
from woc_bridge.version import BRIDGE_VERSION, BRIDGE_CAPABILITIES, MEDIA_SUPPORTED
|
||
|
||
logger = logging.getLogger("woc-bridge")
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/api/status", response_model=StatusResponse)
|
||
@with_db_retry
|
||
async def get_status() -> StatusResponse:
|
||
"""聚合返回 bridge 运行状态。
|
||
|
||
一次请求内完成 4 类检测:进程运行、窗口存在、登录态、DB 可达性,
|
||
并返回当前账号信息与客户端轮询建议参数。所有调用方(channels 适配器、
|
||
面板健康检查、诊断 autofix)都应优先调本接口判断前置条件。
|
||
|
||
Returns:
|
||
StatusResponse:含 bridge_version / wechat_running / wechat_window_found
|
||
/ login_state / db_accessible / current_wxid / current_nickname
|
||
/ uptime_seconds / display / max_batch_size / poll_interval_ms
|
||
|
||
Notes:
|
||
- 不抛业务错误:即使微信未运行 / DB 加密,也返回 200 + 对应 false 字段,
|
||
让调用方按字段判定下一步动作(而非按 HTTP 状态码)
|
||
- login_state 内联判定(不调 detect_login_state),避免重复 pgrep/xdotool
|
||
- DB 加密时细分:有 key 且验证过 → encrypted_key_ok + db_accessible=true;
|
||
无 key 且自动提取未启用 → encrypted_no_key;
|
||
自动提取已尝试但失败 → key_extract_failed;
|
||
二者 db_accessible 均为 false
|
||
- current_wxid/nickname 仅在 db_accessible=true 且 login_state=logged_in
|
||
时才尝试读 DB,失败静默为空字符串
|
||
"""
|
||
xdotool = _require_xdotool()
|
||
db_reader = _require_db_reader()
|
||
|
||
# 一次 status 请求内复用 pgrep/xdotool 结果,避免重复调用
|
||
wechat_running = await xdotool.is_wechat_running()
|
||
window_id = await xdotool.find_wechat_window() if wechat_running else None
|
||
wechat_window_found = window_id is not None
|
||
# 登录态内联判定,避免 detect_login_state 内部再次 pgrep/xdotool search
|
||
if not wechat_running:
|
||
login_state = "not_running"
|
||
elif not wechat_window_found:
|
||
login_state = "not_logged_in"
|
||
else:
|
||
login_state = "logged_in"
|
||
|
||
# DB 状态:统一走 _resolve_db_state(不触发自动提取,仅解析)
|
||
db_state = await _resolve_db_state()
|
||
db_accessible = db_state.db_accessible
|
||
db_error_code = db_state.db_error_code
|
||
init_in_progress = _state.init_state.state == "running"
|
||
init_progress_pct = _state.init_state.progress_pct
|
||
init_message = _state.init_state.message
|
||
|
||
# current_wxid / current_nickname:从 DB 读,失败为空
|
||
current_wxid = ""
|
||
current_nickname = ""
|
||
if db_accessible and login_state == "logged_in":
|
||
try:
|
||
self_info = await asyncio.to_thread(db_reader.get_self_info)
|
||
current_wxid = self_info.get("wxid", "")
|
||
current_nickname = self_info.get("nickname", "")
|
||
except Exception:
|
||
current_wxid = ""
|
||
current_nickname = ""
|
||
|
||
uptime_seconds = time.monotonic() - _state.start_time
|
||
|
||
send_queue = _state.send_queue
|
||
send_queue_pending = send_queue.pending_count() if send_queue else 0
|
||
ui_scheduler_metrics = None
|
||
if send_queue is not None and hasattr(send_queue, "get_metrics"):
|
||
try:
|
||
ui_scheduler_metrics = send_queue.get_metrics()
|
||
except Exception as exc:
|
||
logger.warning("[status] get_metrics failed: %s", exc)
|
||
|
||
# 修复既有 Prometheus Gauge bug:每次 status 请求时 set 当前 pending 数
|
||
try:
|
||
send_queue_pending_gauge.set(send_queue_pending)
|
||
except Exception as exc:
|
||
logger.debug("[status] send_queue_pending_gauge.set failed: %s", exc)
|
||
|
||
return StatusResponse(
|
||
bridge_version=BRIDGE_VERSION,
|
||
wechat_running=wechat_running,
|
||
wechat_window_found=wechat_window_found,
|
||
login_state=login_state,
|
||
db_accessible=db_accessible,
|
||
db_error_code=db_error_code,
|
||
init_in_progress=init_in_progress,
|
||
init_progress_pct=init_progress_pct,
|
||
init_message=init_message,
|
||
current_wxid=current_wxid,
|
||
current_nickname=current_nickname,
|
||
uptime_seconds=uptime_seconds,
|
||
display=_state.config.display,
|
||
max_batch_size=_state.config.max_batch_size,
|
||
poll_interval_ms=_state.config.poll_interval_ms,
|
||
send_queue_pending=send_queue_pending,
|
||
media_supported=MEDIA_SUPPORTED,
|
||
bridge_capabilities=list(BRIDGE_CAPABILITIES),
|
||
ui_scheduler=ui_scheduler_metrics,
|
||
)
|