41 KiB
多应用桥接框架设计
目标:将 bridge 从"微信专属 API 服务"演进为"容器内多桌面应用的统一桥接框架"。一个容器内可同时运行多个应用(微信 / 小红书 / Telegram / Chromium / 自定义),每个应用作为独立实例被 bridge 管理,对外暴露结构化接口。
阅读提示:本文档是目标架构设计,同时用
[现状]标注当前代码的实际状态。当前 bridge 仍是一个约 2400 行的单文件服务(bridge/server.py),全局单例、微信强耦合;docker 与 panel 层已支持"一容器一应用"的多应用类型,但尚未支持"单容器多应用"。因此本文先聚焦 bridge 层改造,使其在"多容器单应用"模式下即可工作,单容器多应用作为后续扩展。
1. 背景与转变
1.1 现状
bridge 当前是单应用、单进程、强耦合微信的 API 服务:
- bridge/server.py 约 2400 行单文件,全局单例
_state = AppState()持有唯一的XdotoolDriver、DbReader、SendQueue、QrCapture。 - 所有路由均为
/api/*无前缀(如/api/send/text、/api/messages/since),直接操作全局_state,没有app_id概念。 - 响应模型字段名微信中心化:
wechat_running、wechat_window_found、错误码WECHAT_NOT_RUNNING/WECHAT_NOT_LOGGED_IN。 - DB 解密、联系人/消息读取、二维码登录全部写死微信表结构与 SQLCipher 参数。
- 一容器只跑一个应用(由 docker/app-defs.sh 的
WOC_APP_TYPE决定);想新增小红书/Telegram 自动化,当前只能再起一个容器。
1.2 转变
用户需求:一个容器内同时跑多个应用,每个应用对外暴露接口。
这要求 bridge 从"微信适配器"升格为"应用桥接框架":
| 维度 | 现状 | 目标 |
|---|---|---|
| 容器内应用数 | 1(由 WOC_APP_TYPE 决定) |
N(动态注册) |
| bridge 进程 | 跟随单一应用 | 一个 bridge 管理多个应用实例 |
| 路由 | 微信专属路由无前缀 | 通用路由无前缀 + 每个应用实例独立命名空间 |
| 状态 | 全局单例 _state |
按 app_id 隔离的 AppInstance 容器 |
| 资源调度 | 不需要 | 必须串行化 X11 / 剪贴板 / 焦点操作 |
1.3 已具备的基础(非本阶段改造重点)
- docker 层已通过
WOC_APP_TYPE支持多种应用类型:wechat/telegram/chromium/custom(docker/app-defs.sh)。 - panel 的
Instance.appType已能标识实例承载的应用类型,并透传给容器环境变量(panel/server/src/store.ts)。 - bridge 基于 FastAPI + Pydantic,已有统一的
BridgeError异常模型与全局异常处理器,为路由拆分和错误码扩展提供了基础。
本文档范围:仅聚焦 bridge 层改造;panel UI、docker 单容器多应用启动机制不在本阶段文档范围内。
2. 核心理念
2.1 三层抽象
应用类型 (AppKind) — 静态描述:微信 / 小红书 / Telegram / Chromium / Custom
↓ 实例化
应用实例 (AppInstance) — 运行期实体:唯一 app_id、独立状态、独立窗口
↓ 适配
应用驱动 (AppDriver) — 该实例的能力实现:发消息 / 发笔记 / 读 DB / 截图
- AppKind 是注册表项,定义"这类应用能做什么、怎么启动、需要什么依赖"
- AppInstance 是运行期实体,一个容器内可以同时存在多个同类实例(如两个微信账号、两个小红书账号)
- AppDriver 是实例的能力实现层,框架通过它操作具体应用
2.2 框架职责边界
框架负责(应用无关):
- HTTP 服务、路由分发、鉴权、限流
- 请求/响应日志、异常处理、错误码体系
- X11 串行化调度(避免多应用并发争用同一个 X server)
- 截图、剪贴板、窗口管理(通用 X11 能力)
- 应用生命周期管理(启动 / 停止 / 重启 / 健康检查)
- 诊断框架(通用检查项 + 应用自报检查项)
- 配置管理、命令行参数、环境变量
应用驱动负责(应用相关):
- 进程启动命令与参数
- 窗口识别(按 title / class / PID)
- 登录态检测与登录流程
- 应用专属能力实现(发消息、发笔记、读 DB、搜索等)
- 应用专属路由注册
- 应用专属诊断项
[现状] 当前所有职责都混在 bridge/server.py 中,没有框架/驱动分层;
XdotoolDriver直接实现微信专用方法,SendQueue是全局单例限流器,UIScheduler 尚未引入。
2.3 设计原则
- 组合优于继承:driver 持有通用工具实例(
self.xd),不继承工具类 - 一应用一命名空间:每个实例独立
app_id,路由与状态都按app_id隔离 - 声明式能力:driver 声明能力集合,框架据此挂载路由与协商
- 串行化 X11:所有窗口操作经统一 UIScheduler 排队执行
- 应用无关的 DB 抽象延迟:SQLCipher 解密暂留微信 driver 内,待第二个 SQLCipher 应用出现再抽公共层
- 向后兼容过渡:旧路径与新路径并存,alias 机制保证现有调用方不破坏
3. 总体架构
┌─────────────────────────────────────────────────────────────────┐
│ Docker 容器 │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ bridge 框架进程 (FastAPI, :8088) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 通用路由层 │ │ UIScheduler │ │ AppManager │ │ │
│ │ │ /api/status │ │ (X11 串行) │ │ (生命周期) │ │ │
│ │ │ /api/apps │ │ │ │ │ │ │
│ │ │ /api/screens │ └──────────────┘ └──────┬───────┘ │ │
│ │ │ /api/diag/* │ │ │ │
│ │ └──────────────┘ │ │ │
│ │ ┌─────────────┼─────────┐ │ │
│ │ │ │ │ │ │
│ │ ┌───────────────────┴───┐ ┌──────┴───────┐ │ │
│ │ │ AppInstance[wx_a1] │ │ AppInstance │ │ │
│ │ │ └ WechatDriver │ │ [xhs_b2] │ │ │
│ │ │ routes: /api/apps/ │ │ └ XhsDriver │ │ │
│ │ │ wx_a1/* │ │ routes: │ │ │
│ │ │ capabilities: │ │ /api/apps/ │ │ │
│ │ │ text_send, │ │ xhs_b2/* │ │ │
│ │ │ db_read, │ │ capabilities│ │ │
│ │ │ login_qr │ │ publish, │ │ │
│ │ └───────────────────────┘ │ search │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ X server (Xvfb :1) ◀── 所有应用共用,UIScheduler 串行化调度 │
│ ├─ 微信窗口 (wxid_abc) │
│ ├─ chromium 窗口 (xiaohongshu) │
│ KasmVNC (:3000/3001) ◀── web 桌面串流 │
│ │
│ 数据卷 /config/ │
│ ├─ wechat/<wxid>/ 微信数据 │
│ ├─ xiaohongshu/ 小红书 cookie + 数据 │
│ ├─ telegram/ Telegram 数据 │
│ └─ apps.json 应用实例注册表(持久化) │
└─────────────────────────────────────────────────────────────────┘
▲
│ HTTP Bearer Token
┌──────┴──────┐
│ panel 面板 │ ◀── 统一入口,调度多容器、多应用
└─────────────┘
[现状] 上图是目标态。当前每个 Docker 容器内只运行一个应用实例,bridge 进程也按单实例设计;
AppManager、UIScheduler、apps.json均未实现。本阶段优先让 bridge 在"多容器单应用"模式下具备框架能力(每个容器一个 bridge、一个 driver、一个 AppInstance),为未来的"单容器多应用"保留扩展点。
4. 核心概念
4.1 AppKind(应用类型)
静态注册项,描述"这类应用是什么"。一个 AppKind 在 bridge 启动时注册到 AppKindRegistry。
| 属性 | 说明 | 示例 |
|---|---|---|
kind_id |
唯一标识 | "wechat" / "xiaohongshu" / "telegram" / "chromium" / "custom" |
name |
显示名 | "微信" / "小红书" |
driver_class |
AppDriver 实现类 | WechatDriver / XiaohongshuDriver |
default_capabilities |
默认能力集合 | {"text_send","db_read","login_qr"} |
binary_path |
可执行文件路径(用于检测是否已安装) | /config/wechat/opt/wechat/wechat |
launch_args_template |
启动参数模板(支持变量替换) | "--user-data-dir={data_dir}" |
single_instance |
是否限制单实例(如系统托盘类应用) | true / false |
[现状]
AppKindRegistry尚未实现。当前应用类型由容器环境变量WOC_APP_TYPE决定,bridge 代码中无对应抽象;但目标类的字段(kind_id、driver_class、binary_path等)可直接映射到现有 docker/app-defs.sh 的定义。
4.2 AppInstance(应用实例)
运行期实体,代表一个正在运行(或已注册待启动)的应用。每个实例有唯一 app_id。
| 属性 | 说明 | 示例 |
|---|---|---|
app_id |
全局唯一实例标识 | "wx_a1" / "xhs_b2"(kind 前缀 + 短随机) |
kind_id |
所属应用类型 | "wechat" |
display_name |
用户可读名 | "工作微信" / "小红书主号" |
data_dir |
该实例独立数据目录 | /config/apps/wx_a1 |
window_id |
X11 窗口 ID(运行期动态) | 0x380000a |
pid |
进程 PID | 1234 |
login_state |
当前登录态 | "logged_in" / "need_login" / "not_running" |
capabilities |
实际可用能力(动态计算) | {"text_send"}(DB 未解锁时 db_read 不在集合内) |
status |
实例状态 | "registered" / "starting" / "running" / "stopped" / "crashed" |
实例寻址:所有应用专属路由以 /api/apps/{app_id}/ 为前缀,框架据此路由到对应实例的 driver。
[现状]
AppInstance数据类尚未实现。当前 bridge 的运行期状态是全局单例_state,没有app_id。本阶段每个 bridge 进程仍只管理一个实例,可把该实例的app_id固定为"default"或从WOC_APP_TYPE派生,使新路由层能提前落地。
4.3 AppDriver(应用驱动)
每种 AppKind 对应一个 AppDriver 实现类,负责该类应用的所有操作。driver 实例与 AppInstance 一一绑定。
class AppDriver(ABC):
kind_id: ClassVar[str]
default_capabilities: ClassVar[set[str]]
def __init__(self, instance: AppInstance, ctx: BridgeContext) -> None:
self.instance = instance
self.ctx = ctx # 框架通用能力(xd / scheduler / screenshot)
self.xd = ctx.xd_factory(instance) # 该实例专属的 XdotoolBase
# 生命周期
async def install(self) -> None: ... # 首次安装(下载/解压)
async def start(self) -> None: ... # 启动进程
async def stop(self) -> None: ... # 停止进程
async def restart(self) -> None: ...
async def is_running(self) -> bool: ...
async def health_check(self) -> dict: ...
# 窗口与登录
async def find_window(self) -> int | None: ...
async def activate_window(self) -> None: ...
async def detect_login_state(self) -> str: ...
async def wait_for_login(self, timeout: int) -> None: ...
async def logout(self) -> None: ...
# 能力查询
def capabilities(self) -> set[str]: ... # 返回当前实际可用能力(动态)
# 路由注册
def register_routes(self, router: APIRouter) -> None: ...
# 诊断
def diagnostic_items(self) -> list[dict]: ...
async def diagnostic_run(self, check_id: str) -> dict: ...
async def diagnostic_autofix(self, check_id: str) -> dict: ...
[现状]
AppDriver抽象基类尚未实现。当前微信相关逻辑直接写在 bridge/server.py 和 bridge/xdotool_driver.py 中,二者均强耦合微信。拆分时建议:
- 把
xdotool_driver.py中通用 X11 操作(_run、_key、_paste_via_xclip、窗口几何)抽成XdotoolBase;- 把微信专属方法(
find_wechat_window、send_text、_open_session_by_name)迁到drivers/wechat/driver.py的WechatDriver;- 微信 DB 读写、密钥提取、二维码登录迁到
drivers/wechat/子模块。
4.4 BridgeContext(框架上下文)
driver 通过它访问框架通用能力。框架保证线程安全与串行化。
| 字段 | 类型 | 说明 |
|---|---|---|
config |
BridgeConfig |
全局配置 |
xd_factory |
Callable[[AppInstance], XdotoolBase] |
为每个实例创建 X11 工具(绑定到该实例窗口) |
ui_scheduler |
UIScheduler |
X11 操作串行化调度器 |
screenshot |
Screenshot |
通用截图能力 |
send_queue |
SendQueue |
UI 操作串行化队列(多应用共享) |
logger |
logging.Logger |
框架 logger |
5. 模块设计
5.1 目录结构
bridge/
core/ 框架核心(应用无关)
__init__.py
context.py BridgeContext
config.py BridgeConfig(命令行 + env)
server.py FastAPI app 工厂 + lifespan + 全局中间件
logging.py logger 配置 + 请求日志中间件
errors.py BridgeError + ErrorResponse + 错误码表
models.py 通用响应模型(StatusResponse / AppInstanceInfo 等)
xdotool_base.py X11 通用操作基类
screenshot.py 通用截图
send_queue.py UI 串行化队列
ui_scheduler.py X11 操作调度器(多应用串行)
diagnostic.py 通用诊断框架
app_manager.py AppManager(实例生命周期管理)
app_kind.py AppKind + AppKindRegistry(类型注册表)
app_instance.py AppInstance 数据类
lifecycle.py InitState 后台任务框架
drivers/ 应用适配器
__init__.py
base.py AppDriver 抽象基类
wechat/
__init__.py
driver.py WechatDriver(AppDriver)
db_reader.py 微信 DB 读取
decryptor.py SQLCipher 解密
key_cache.py 密钥缓存
key_extractor.py 内存扫描提 key
models.py 微信专属请求/响应模型
routes.py 微信专属路由
diagnostic.py 微信诊断项
xiaohongshu/
__init__.py
driver.py XiaohongshuDriver(AppDriver)
routes.py 小红书专属路由
models.py
telegram/
__init__.py
driver.py TelegramDriver(AppDriver)
routes.py
chromium/
__init__.py
driver.py ChromiumDriver(AppDriver) 通用浏览器
custom/
__init__.py
driver.py CustomDriver(AppDriver)
tools/ 调试脚本
s6/ s6 服务定义
main.py 入口:解析参数 → 注册 kinds → 启动 app
[现状] 上述
core/、drivers/、main.py均为目标结构。当前 bridge 目录扁平:所有代码在 bridge/server.py、bridge/xdotool_driver.py、bridge/send_queue.py 等根级文件中。S1 阶段可按以下顺序迁移,避免一次性大爆炸:
- 先把
server.py中通用配置/错误/模型迁到core/;- 创建
drivers/base.py与drivers/wechat/;- 保持
main.py仍只注册一个WechatDriver,行为与旧版完全一致。
5.2 AppManager(应用管理器)
框架核心组件,管理容器内所有 AppInstance 的生命周期。
职责:
- 维护实例注册表(内存 + 持久化到
/config/apps.json) - 提供实例 CRUD:
register/unregister/list/get - 控制实例生命周期:
start/stop/restart - 健康检查:周期性轮询所有 running 实例的
is_running - 自动恢复:crashed 实例按策略重启(最多 N 次/小时)
实例注册表结构(/config/apps.json):
{
"version": 1,
"instances": [
{
"app_id": "wx_a1",
"kind_id": "wechat",
"display_name": "工作微信",
"data_dir": "/config/apps/wx_a1",
"auto_start": true,
"created_at": 1735900800
},
{
"app_id": "xhs_b2",
"kind_id": "xiaohongshu",
"display_name": "小红书主号",
"data_dir": "/config/apps/xhs_b2",
"auto_start": false,
"created_at": 1735900900
}
]
}
[现状]
AppManager与/config/apps.json均未实现。当前实例生命周期(容器启停、应用安装)由 panel 通过 Docker API 管理,bridge 只感知当前容器内已启动的单个应用。本阶段改造不推翻 panel 的生命周期管理,bridge 内只保留单实例的运行期抽象,为后续多实例注册表预留接口。
5.3 UIScheduler(X11 操作调度器)
问题:多应用共用一个 X server,并发操作会导致:
- 窗口焦点争用(A 激活窗口时 B 的操作打到 A)
- 剪贴板污染(A 写剪贴板,B 还没读就被覆盖)
- 键盘事件错位(A 的 Ctrl+V 发到 B 的窗口)
解决:所有 X11 操作(activate / key / paste / click / type)必须经 UIScheduler 排队执行。
class UIScheduler:
"""X11 操作串行化调度器。
所有 driver 的 X11 操作通过 submit() 提交,调度器按 FIFO 顺序
串行执行。同一时间只有一个操作在 X server 上进行。
"""
async def submit(self, coro_factory: CoroFactory, *, app_id: str) -> Any:
"""提交一个 X11 操作。
Args:
coro_factory: 返回 coroutine 的工厂(便于重试与超时控制)
app_id: 发起方实例 ID(用于日志与死锁检测)
Returns:
操作结果
Notes:
- 操作间最小间隔 100ms(避免 X server 压力)
- 单操作超时 30s(避免死锁)
- 操作日志带 app_id 前缀,便于排障
"""
driver 使用方式:
class WechatDriver(AppDriver):
async def send_text(self, to: str, content: str) -> str:
async def _op():
await self.xd.activate_window(self.instance.window_id)
await self.xd._open_session_by_name(to)
await self.xd._paste_via_xclip(content)
await self.xd._key("Return")
return f"local_{int(time.time())}_{random.randint(0,0xFFFFFF):06x}"
return await self.ctx.ui_scheduler.submit(_op, app_id=self.instance.app_id)
不进 UIScheduler 的操作:
- 读窗口列表(
xdotool search,只读) - 读进程状态(
pgrep,只读) - 读 DB(不涉及 X11)
- 截图(
scrot本身是只读快照,不争用焦点)
5.4 SendQueue 与 UIScheduler 的关系
| 组件 | 作用 | 粒度 |
|---|---|---|
SendQueue |
发送类操作的限流(防止频率过高触发风控) | 单实例:每个 driver 自己一个队列 |
UIScheduler |
X11 操作的串行化(防止多应用争用 X server) | 全局:所有 driver 共享 |
两者正交:一个操作可能先经 SendQueue 限流,再经 UIScheduler 串行执行。
[现状] 当前只有一个全局 SendQueue,用于单应用发送限流;
UIScheduler尚未实现,X11 操作在 HTTP 层并发调用时存在焦点/剪贴板冲突风险。S1 阶段可保持SendQueue不变,S2 引入多实例后再把SendQueue下放到每个AppInstance,同时新增全局UIScheduler。
6. 接口规范
6.1 路由分层
/api/* 通用路由(无 app_id,框架级)
/api/status 框架状态(聚合所有应用摘要)
/api/apps 应用实例列表 / 注册新实例
/api/apps/{app_id} 单实例状态 / 控制
/api/apps/{app_id}/start 启动实例
/api/apps/{app_id}/stop 停止实例
/api/apps/{app_id}/restart 重启实例
/api/kinds 支持的应用类型列表
/api/screenshot 全屏截图(所有应用窗口)
/api/diagnostic/items 通用诊断项 + 所有实例诊断项聚合
/api/diagnostic/run/{id} 执行诊断
/api/diagnostic/autofix/{id}
/api/apps/{app_id}/* 应用专属路由(由 driver.register_routes 挂载)
/api/apps/{app_id}/messages 微信消息拉取
/api/apps/{app_id}/contacts 微信联系人
/api/apps/{app_id}/send/text 微信发文本
/api/apps/{app_id}/login/qr 微信扫码登录
/api/apps/{app_id}/db/decrypt 微信 DB 解密
/api/apps/{app_id}/xhs/publish 小红书发笔记
/api/apps/{app_id}/xhs/search 小红书搜索
[现状] 当前所有路由都是
/api/*无前缀(如/api/send/text、/api/messages/since、/api/db/decrypt),且全部写死微信逻辑。本阶段目标:
- 新增
/api/apps/{app_id}/*路由层;- 将旧路由作为 alias 内部转发到
/api/apps/default/*;/api/status同时返回旧字段(取 default app)与新apps数组,保证面板零改动。
6.2 通用路由详述
GET /api/status
返回框架整体状态 + 所有实例摘要。
{
"bridge_version": "2.0.0",
"uptime_seconds": 3600,
"display": ":1",
"app_count": 2,
"apps": [
{
"app_id": "wx_a1",
"kind_id": "wechat",
"display_name": "工作微信",
"status": "running",
"login_state": "logged_in",
"capabilities": ["text_send", "db_read", "login_qr"]
},
{
"app_id": "xhs_b2",
"kind_id": "xiaohongshu",
"display_name": "小红书主号",
"status": "running",
"login_state": "need_login",
"capabilities": ["publish", "search"]
}
]
}
GET /api/apps
列出所有实例。
POST /api/apps
注册新实例。
// 请求
{
"kind_id": "xiaohongshu",
"display_name": "小红书小号",
"auto_start": true
}
// 响应
{
"app_id": "xhs_c3",
"kind_id": "xiaohongshu",
"display_name": "小红书小号",
"data_dir": "/config/apps/xhs_c3",
"status": "registered"
}
GET /api/apps/{app_id}
单实例详细状态。
{
"app_id": "wx_a1",
"kind_id": "wechat",
"display_name": "工作微信",
"status": "running",
"pid": 1234,
"window_id": "0x380000a",
"login_state": "logged_in",
"capabilities": ["text_send", "db_read", "login_qr", "media"],
"uptime_seconds": 1800,
"db_accessible": true,
"current_wxid": "wxid_abc",
"current_nickname": "张三"
}
POST /api/apps/{app_id}/start|stop|restart
控制实例生命周期。
GET /api/kinds
列出支持的 AppKind。
{
"kinds": [
{
"kind_id": "wechat",
"name": "微信",
"default_capabilities": ["text_send", "db_read", "login_qr", "media"],
"single_instance": false,
"installed": true
},
{
"kind_id": "xiaohongshu",
"name": "小红书",
"default_capabilities": ["publish", "search"],
"single_instance": false,
"installed": true
}
]
}
6.3 应用专属路由规范
由各 driver 通过 register_routes(router) 挂载,router 自动绑定 /api/apps/{app_id} 前缀。
driver 实现示例:
# drivers/wechat/routes.py
def register_routes(router: APIRouter) -> None:
@router.get("/messages/since")
async def get_messages(app_id: str, cursor: int = 0, limit: int = 50):
driver = app_manager.get_driver(app_id)
return await driver.get_messages_since(cursor, limit)
@router.post("/send/text")
async def send_text(app_id: str, req: SendTextRequest):
driver = app_manager.get_driver(app_id)
return await driver.send_text(req.to, req.content)
6.4 错误码扩展
新增框架级错误码:
| 错误码 | HTTP | 说明 |
|---|---|---|
APP_NOT_FOUND |
404 | app_id 不存在 |
APP_NOT_RUNNING |
503 | 实例未运行 |
APP_NOT_LOGGED_IN |
401 | 实例未登录 |
APP_KIND_UNKNOWN |
400 | 未知应用类型 |
APP_KIND_NOT_INSTALLED |
503 | 应用类型未安装 |
APP_ALREADY_EXISTS |
409 | 实例已存在(single_instance 冲突) |
UI_BUSY |
503 | UIScheduler 队列拥塞 |
APP_CAPABILITY_NOT_SUPPORTED |
501 | 该实例不支持请求的能力 |
[现状] 当前 bridge/models.py 中只有微信中心化错误码,如
WECHAT_NOT_RUNNING、WECHAT_NOT_LOGGED_IN等。拆分阶段应:
- 保留旧错误码作为微信 driver 的返回值;
- 新增框架级错误码上表;
- alias 路由命中时仍可使用旧错误码,避免面板/外部系统改动。
6.5 向后兼容层
现有无前缀路由(/api/messages、/api/send/text 等)保留为 alias,内部转发到 /api/apps/{default_app_id}/...。
默认实例选择规则:
- 若容器内只有一个实例,自动作为 default
- 若多实例,读取
/config/.woc-default-app(由面板或首次注册时写入) - 都没有则返回 404
alias 路由行为:
- 命中 alias 时日志打
DEPRECATEDwarning,提示调用方迁移 - alias 在 major 版本升级时下线(v3.0.0 移除)
6.6 与既有系统的兼容性约束
改造必须尊重以下已验证的硬约束,否则现有微信实例会损坏:
- 鉴权:
/api/bridge/:id/*外部调用依赖WOC_BRIDGE_API_TOKEN作为 Bearer token;修改.env后必须重建 panel 容器(而非重启)才能生效。 - DB 解密:微信 DB 仅兼容 Linux WeChat 4.x,使用 SQLCipher 4 参数(AES-256-CBC、PBKDF2-HMAC-SHA512、256000 rounds、mac_salt = salt XOR 0x3A)。解密能力保留在
drivers/wechat/内,不提前抽象。 - ptrace:自动提取 key 需要容器具备
SYS_PTRACEcapability 或--privileged,且宿主ptrace_scope=0。 - DB 路径:自动检测写死
/config/xwechat_files/<wxid>/db_storage/message/message_0.db;改为 driver 内部常量,不作为框架公共假设。 - 发送定位:微信
send_text/send_file使用display_name(备注/昵称)而非 wxid 搜索会话,该行为由WechatDriver封装,不进入框架公共层。 - 容器启动:当前 docker/autostart 只启动一个应用;bridge 框架不假设自己能启动多个应用,生命周期控制仍由 panel/docker 负责。
7. 资源调度与并发
7.1 X11 资源争用对策
| 资源 | 争用场景 | 对策 |
|---|---|---|
| 窗口焦点 | A 激活窗口时 B 失焦 | 所有 activate/key/click 经 UIScheduler 串行 |
| 剪贴板 | A 写入后 B 未读就被 C 覆盖 | paste 操作是"写剪贴板+立即 Ctrl+V"原子组,经 UIScheduler 串行 |
| 键盘事件 | A 的 Ctrl+V 发到 B 窗口 | activate 后立即操作,UIScheduler 保证原子 |
| 鼠标坐标 | A 的 click 坐标被 B 的 mousemove 改变 | mousemove + click 必须在同一个 UIScheduler 任务内 |
7.2 并发模型
HTTP 请求 (asyncio)
↓
driver 方法 (async)
↓
UIScheduler.submit(coro) ◀── 全局串行点(X11 操作)
↓
XdotoolBase._run (asyncio subprocess)
- HTTP 层并发:FastAPI 默认并发处理请求
- driver 层并发:每个实例的方法可并发调用
- UIScheduler 层串行:所有 X11 写操作排队执行
- DB 读取层并发:不涉及 X11,独立并发(受限于 SQLite 读写锁)
7.3 限流策略
| 层级 | 限流 | 配置 |
|---|---|---|
| HTTP 全局 | 单 IP 每秒 N 请求 | WOC_BRIDGE_MAX_CALLS_PER_SEC |
| 单实例发送 | 发送类操作最小间隔 | WOC_BRIDGE_SEND_DELAY_MS(每实例独立) |
| UIScheduler | 操作间最小间隔 | 固定 100ms(防止 X server 压力) |
| UIScheduler 队列长度 | 最多排队 N 个操作 | 50,超限返回 UI_BUSY |
8. 应用生命周期
8.1 实例状态机
register
┌─────────────────────────┐
│ ▼
┌────────┐ start ┌─────────┐ running ┌────────┐
│registered├────────►│starting├────────────►│running │
└────────┘ └────┬───┘ └───┬────┘
▲ │ fail │ stop
│ ▼ ▼
│ ┌────────┐ ┌────────┐
│ │crashed │ │stopped │
│ └───┬────┘ └───┬────┘
│ │ restart │ start
│ ▼ │
└──────────────────────────────────────────┘
8.2 启动流程
POST /api/apps注册实例 → 状态registeredPOST /api/apps/{app_id}/start→ 状态starting- driver.install()(首次启动,下载/解压)
- driver.start()(启动应用进程)
- 轮询 driver.find_window() 直到窗口出现(超时 60s)
- 状态
running - 启动失败 → 状态
crashed,记录错误
8.3 自动启动
容器启动时,AppManager 读取 /config/apps.json,对所有 auto_start=true 的实例按顺序执行启动流程。
顺序约束:实例间启动间隔 5 秒(避免同时启动多个 GUI 应用导致内存峰值)。
8.4 健康检查
AppManager 后台任务(每 30s):
- 遍历所有
running状态实例 - 调用
driver.is_running() - 失败则状态转
crashed - 记录崩溃次数与时间
- 若配置了自动恢复且未超限(默认 3 次/小时),触发 restart
8.5 数据卷布局
/config/
apps.json 实例注册表
.woc-default-app 默认实例 app_id(兼容层用)
apps/
wx_a1/
data/ 应用数据根(driver 自由组织)
wechat/
opt/wechat/wechat 微信二进制
xwechat_files/ 微信运行时数据
logs/ 该实例日志
state/ 该实例状态文件
xhs_b2/
data/
chromium/ 小红书专用 chromium user-data-dir
Default/
Cookies 小红书登录 cookie
logs/
state/
9. 应用间通信
9.1 显式通信(推荐)
应用之间通过外部调用方(如面板或用户脚本)显式编排:
# 伪代码:从小红书采集内容,发到微信
xhs_note = await bridge.get(f"/api/apps/xhs_b2/xhs/search?keyword=foo")
await bridge.post(f"/api/apps/wx_a1/send/text", json={"to":"文件助手","content":xhs_note})
优点:流程显式、可观测、可调试。
不提供应用间直接调用:driver 之间不互相感知,避免隐式依赖。
9.2 事件总线(未来扩展)
可选的发布订阅机制:
# driver 发布事件
await self.ctx.event_bus.publish("message_received", {"app_id": "wx_a1", "msg": {...}})
# 外部订阅
GET /api/events/stream (SSE)
P0 不实现,留待有明确需求时再做。
10. 部署模型
10.1 单容器多应用
容器 woc-app-multi
├─ bridge (:8088) 管理 2 个实例
├─ Xvfb (:1)
├─ KasmVNC (:3000)
├─ 微信进程 (wx_a1)
└─ chromium 进程 (xhs_b2)
适用于:单用户、资源受限(NAS)、应用间需要协同(同一桌面)。
10.2 多容器单应用(现有模式保留)
容器 woc-wx-<id1> 微信实例 1
容器 woc-wx-<id2> 微信实例 2
容器 woc-xhs-<id3> 小红书实例
适用于:多用户隔离、横向扩展、单应用崩溃不影响其他。
10.3 混合模式
面板支持两种模式并存:
- 轻量场景用单容器多应用(省资源)
- 隔离场景用多容器单应用(强隔离)
面板层提供创建实例时的"部署位置"选项:新建独立容器 or 加入已有容器的 bridge。
11. 演进路径
S0:当前现状
- bridge/server.py 约 2400 行单文件,全局单例
_state。 - 所有
/api/*路由直接写死微信逻辑,无app_id、无 driver 抽象。 - docker/autostart 一容器一应用;panel 通过 Docker API 管理生命周期。
S1:抽 core/ 与 driver 抽象(不改行为,本阶段重点)
目标:把 server.py 拆成 core/ + drivers/wechat/,建立 AppDriver 抽象,但保持单应用模式。
改动:
- 新建
core/目录,迁移config/context/errors/logging/models/send_queue/screenshot。 - 抽
XdotoolBase(通用 X11 操作)到core/xdotool_base.py。 - 定义
drivers/base.py的AppDriver抽象基类。 - 微信逻辑迁移到
drivers/wechat/,WechatDriver(AppDriver)用组合方式持有XdotoolBase。 - 微信路由迁移到
drivers/wechat/routes.py。 main.py读WOC_APP_TYPE,只注册一个 driver,创建一个AppInstance(app_id="default")。
验证:所有现有 API 行为不变,面板无感知;pytest 或现有集成测试全部通过。
S2:新路由层 + 向后兼容层
目标:引入 /api/apps/{app_id}/* 路由,旧路由作为 alias 转发。
改动:
- 新增
/api/apps/{app_id}/status、/api/apps/{app_id}/send/text等路由。 - 旧路径
/api/messages、/api/send/text等转为 alias,转发到/api/apps/default/...。 /api/status同时返回旧字段(取 default app)与新apps数组。- 旧字段日志打
DEPRECATEDwarning。
验证:面板与外部调用方无需改动即可工作;新路径可通过 /api/apps/default/* 访问。
S3:UIScheduler 与 SendQueue 正交化
目标:解决多实例共享 X server 时的焦点/剪贴板冲突。
改动:
- 新增
core/ui_scheduler.py,所有 X11 写操作经其串行执行。 - 每个
AppInstance持有独立SendQueue;UIScheduler全局唯一。 - 单应用模式下行为不变,多实例模式下才体现串行价值。
验证:高并发调用 /api/apps/default/send/text 不再出现剪贴板污染。
S4:AppManager 与单容器多实例(未来扩展)
目标:支持容器内注册多个 AppInstance。
改动:
- 实现
core/app_manager.py、core/app_instance.py、core/app_kind.py。 - 新增
/api/appsCRUD 路由。 /config/apps.json持久化。- 需同步改造 docker/autostart 以支持启动多个应用。
验证:可在同一容器内通过 API 注册并启动微信 + 小红书两个实例。
S5:新增 Xiaohongshu/Telegram Driver(框架可扩展性验证)
目标:落地非微信应用自动化。
改动:
- 实现
drivers/xiaohongshu/driver.py+routes.py(P0:publish/text、search)。 - 或实现
drivers/telegram/driver.py(优先浏览器/桌面版,若走原生协议则另议)。
验证:通过 /api/apps/{app_id}/xhs/publish 成功发笔记。
S6:面板适配与单容器多应用部署(不在本文档范围)
目标:面板支持多应用管理 UI 与"单容器多应用"部署选项。
改动(panel 层):
- 面板新增"应用实例"管理页。
- 桌面入口支持选择"进入哪个应用窗口"。
- 实例创建支持"新建独立容器"或"加入已有容器"。
S7:下线兼容层
目标:移除 alias,完成迁移。
前提:面板与所有已知适配器已切换到新路径。
改动:
- 删除旧路径 alias。
/api/status移除旧字段。- major 版本升级到 v3.0.0。
12. 不做的事
12.1 完全不做
- 不引入应用间隐式依赖:driver 之间不互相调用,编排由外部完成。
- 不做多容器 bridge 集群:一个 bridge 进程只管一个容器内的应用,跨容器调度由面板负责。
- 不做应用沙箱:应用间共享 X server 与文件系统(受 Linux 权限控制),不做额外隔离。
- 不做 RBAC:
app_id级别的权限控制留给面板层,bridge 层只做 Bearer Token 全局鉴权。 - 不改 KasmVNC 串流层:桌面串流保持现状(全屏共享),不按应用切分串流。
- 不做 OCR:不引入 tesseract,页面识别靠 URL + 坐标启发式。
12.2 本阶段先不做
- 不抽象 SQLCipher 解密:解密能力暂留
drivers/wechat/,待第二个 SQLCipher 应用出现再抽公共层。 - 不实现单容器多应用启动:docker/autostart 当前只启动一个应用,改造它需要 panel/docker 同步调整,不在本阶段 bridge 文档范围。
- 不新增非微信 driver:先完成框架抽象与微信 driver 迁移,再落地 Xiaohongshu/Telegram driver。
- 不替换全局
SendQueue:S1 保持现有全局队列,S3 再下放到每个AppInstance。 - 不引入新依赖:仍只使用 fastapi / uvicorn / pydantic / pillow / cryptography。
13. 关键决策记录
| 决策点 | 选择 | 理由 |
|---|---|---|
| 文档定位 | 目标架构 + [现状] 注释 |
既描述最终形态,又不掩盖当前代码未落地的事实 |
| 本阶段重点 | 先让 bridge 在"多容器单应用"模式下可工作 | docker autostart 当前只支持一容器一应用;先完成 bridge 框架抽象,再扩展单容器多应用 |
| 文档范围 | 只聚焦 bridge 层 | panel UI、docker 多应用启动机制另行设计 |
| 抽象颗粒度 | 一应用一 driver(粗粒度) | 业务流程差异大,能力复用价值低 |
| 容器内应用数 | 目标为多实例并存,当前先单实例 | 满足"一容器多应用"长期需求,但本阶段保留单实例运行 |
| 路由前缀 | /api/apps/{app_id}/* + alias 过渡 |
实例独立寻址 + 向后兼容 |
| XdotoolBase 风格 | 组合(driver 持有 self.xd) |
避免多继承混乱 |
| SQLCipher 解密位置 | 留 drivers/wechat/ | YAGNI,等第二个应用再抽 |
| X11 并发控制 | 全局 UIScheduler 串行 | 多应用争用同一 X server 必须串行;当前尚未实现 |
| 应用间通信 | 显式编排(无直接调用) | 流程可观测、可调试 |
| 部署模型 | 单容器多应用 + 多容器单应用并存 | 兼顾资源效率与隔离性;当前仅多容器单应用可用 |