WechatOnCloud/bridge/woc_bridge/config.py
Kris 2e056f9bae refactor(bridge+panel): 重构发送逻辑并优化默认配置
1. 调整默认发送延迟从800ms改为3000ms,更新配置默认值与环境变量 fallback
2. 重写Fastify代理路由,修复POST body被提前消费导致的转发超时问题
3. 新增DB校验机制,发送后轮询确认消息是否到达目标会话
4. 重构xdotool操作流程:改用鼠标点击搜索与发送,替代快捷键组合以适配微信4.x自绘UI
5. 优化窗口激活、会话搜索与输入焦点处理逻辑,提升发送稳定性
2026-07-15 20:14:33 +08:00

236 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""bridge 配置与全局状态。
集中管理:
- DIAGNOSTIC_ITEMS诊断项定义
- InitStateDB 初始化后台任务状态
- BridgeConfig命令行参数 + 环境变量配置
- AppState运行期共享状态容器单例 _state
- _require_* 辅助函数:依赖检查(未初始化时抛 BridgeError
"""
from __future__ import annotations
import argparse
import asyncio
import logging
import os
import threading
from dataclasses import dataclass
from typing import Callable, Optional
from woc_bridge.db import DbReader, KeyCache
from woc_bridge.messaging import MessageStreamer, SendQueue
from woc_bridge.models import BridgeError
from woc_bridge.ui import QrCapture, XdotoolDriver
# ---------------------------------------------------------------------------
# 诊断项定义spec 11.1 节)
# ---------------------------------------------------------------------------
DIAGNOSTIC_ITEMS = [
{
"check_id": "wechat_running",
"name": "微信进程检查",
"severity": "critical",
"description": "检查微信客户端是否运行",
"auto_repairable": True,
},
{
"check_id": "login_state",
"name": "登录状态检查",
"severity": "critical",
"description": "检查微信是否已登录",
"auto_repairable": False,
},
{
"check_id": "db_accessible",
"name": "数据库可达性",
"severity": "error",
"description": "检查微信 DB 是否可读",
"auto_repairable": True, # 可通过自动提取 key 修复
},
{
"check_id": "xdotool_available",
"name": "xdotool 可用性",
"severity": "error",
"description": "检查 xdotool 是否可用",
"auto_repairable": True,
},
]
# 诊断项 check_id → 定义映射,便于查找
_DIAGNOSTIC_ITEM_BY_ID = {item["check_id"]: item for item in DIAGNOSTIC_ITEMS}
# ---------------------------------------------------------------------------
# DB 初始化后台任务状态
# ---------------------------------------------------------------------------
class InitState:
"""DB 初始化(密钥提取)后台任务状态。"""
def __init__(self) -> None:
self.state: str = "idle" # idle / running / success / failed
self.progress_pct: Optional[float] = None
self.message: Optional[str] = None
self.key_count: Optional[int] = None
self.error: Optional[str] = None
self._thread: Optional[threading.Thread] = None
def start(self, target: Callable, args: tuple = ()) -> None:
"""启动后台初始化线程。
若前一个线程仍存活,拒绝重复启动(调用方应先检查 state == running
"""
if self._thread is not None and self._thread.is_alive():
return
self.state = "running"
self.progress_pct = 0.0
self.message = "开始初始化"
self.error = None
self._thread = threading.Thread(target=target, args=args, daemon=True)
self._thread.start()
def set_progress(self, pct: float, message: str) -> None:
self.progress_pct = pct
self.message = message
def set_success(self, key_count: int, message: str) -> None:
self.state = "success"
self.progress_pct = 100.0
self.key_count = key_count
self.message = message
self.error = None
def set_failed(self, error: str) -> None:
self.state = "failed"
self.progress_pct = None
self.error = error
self.message = f"初始化失败: {error}"
# ---------------------------------------------------------------------------
# BridgeConfig
# ---------------------------------------------------------------------------
@dataclass
class BridgeConfig:
"""bridge 服务的配置项(来自命令行参数 + 环境变量)。
在启动时一次性构造并注入 AppState运行期只读不写。
所有 os.environ.get / 类型转换集中在此处,避免散落。
"""
# 命令行参数
listen: str = "0.0.0.0:8088"
wechat_db: str = "/config"
display: str = ":1"
# 环境变量
send_delay_ms: int = 3000
max_calls_per_sec: int = 10
max_batch_size: int = 50
poll_interval_ms: int = 2000
db_key: str = ""
auto_extract_enabled: bool = True
@classmethod
def from_args_and_env(cls, args: argparse.Namespace) -> "BridgeConfig":
"""从 argparse 结果 + 环境变量一次性构造配置。"""
return cls(
listen=args.listen,
wechat_db=args.wechat_db,
display=args.display,
send_delay_ms=int(os.environ.get("WOC_BRIDGE_SEND_DELAY_MS", "3000")),
max_calls_per_sec=int(os.environ.get("WOC_BRIDGE_MAX_CALLS_PER_SEC", "10")),
max_batch_size=int(os.environ.get("WOC_BRIDGE_MAX_BATCH_SIZE", "50")),
poll_interval_ms=int(os.environ.get("WOC_BRIDGE_POLL_INTERVAL_MS", "2000")),
db_key=os.environ.get("WOC_DB_KEY", "").strip(),
auto_extract_enabled=os.environ.get(
"WOC_DB_KEY_AUTO_EXTRACT", "true"
).lower() not in ("false", "0", "no", "off"),
)
# ---------------------------------------------------------------------------
# AppState
# ---------------------------------------------------------------------------
class AppState:
"""运行期共享状态容器。"""
def __init__(self) -> None:
self.config: BridgeConfig = BridgeConfig()
self.xdotool: XdotoolDriver | None = None
self.db_reader: DbReader | None = None
self.send_queue: SendQueue | None = None
self.qr_capture: QrCapture | None = None
self.message_streamer: MessageStreamer | None = None
self.start_time: float = 0.0
# DB 解密密钥缓存env / api / auto_extract / file 注入)
self.key_cache = KeyCache()
# DB 初始化后台任务状态
self.init_state = InitState()
# 自动提取异步锁:防止并发请求同时触发内存扫描
# asyncio.Lock 需在事件循环中创建,延迟到 lifespan 首次使用时初始化
self._extract_lock: asyncio.Lock | None = None
def extract_lock(self) -> asyncio.Lock:
"""懒初始化 asyncio.Lock需在事件循环中创建"""
if self._extract_lock is None:
self._extract_lock = asyncio.Lock()
return self._extract_lock
_state = AppState()
# ---------------------------------------------------------------------------
# 依赖检查辅助函数(替代 assert兼容 python -O
# ---------------------------------------------------------------------------
def _require_xdotool() -> XdotoolDriver:
"""返回 xdotool 驱动,未初始化时抛 BridgeError。"""
if _state.xdotool is None:
raise BridgeError(
code="BRIDGE_INTERNAL_ERROR",
message="xdotool driver 未初始化",
)
return _state.xdotool
def _require_db_reader() -> DbReader:
"""返回 DB 读取器,未初始化时抛 BridgeError。"""
if _state.db_reader is None:
raise BridgeError(
code="BRIDGE_INTERNAL_ERROR",
message="db_reader 未初始化",
)
return _state.db_reader
def _require_send_queue() -> SendQueue:
"""返回发送队列,未初始化时抛 BridgeError。"""
if _state.send_queue is None:
raise BridgeError(
code="BRIDGE_INTERNAL_ERROR",
message="send_queue 未初始化",
)
return _state.send_queue
def _require_qr_capture() -> QrCapture:
"""返回二维码截图器,未初始化时抛 BridgeError。"""
if _state.qr_capture is None:
raise BridgeError(
code="BRIDGE_INTERNAL_ERROR",
message="qr_capture 未初始化",
)
return _state.qr_capture
def _require_message_streamer() -> MessageStreamer:
"""返回消息流推送器,未初始化时抛 BridgeError。"""
if _state.message_streamer is None:
raise BridgeError(
code="BRIDGE_INTERNAL_ERROR",
message="message_streamer 未初始化",
)
return _state.message_streamer