WechatOnCloud/doc/优化方案/01-多应用桥接框架设计.md
Kris 67aab58a00 docs: 新增PRD规范与WechatOnCloud改造、多应用桥接框架文档
新增三份文档:
1. 产品需求文档(PRD)编写规范
2. WechatOnCloud容器化微信改造需求方案
3. 多应用桥接框架整体设计方案
2026-07-18 16:04:25 +08:00

41 KiB
Raw Blame History

多应用桥接框架设计

目标:将 bridge 从"微信专属 API 服务"演进为"容器内多桌面应用的统一桥接框架"。一个容器内可同时运行多个应用(微信 / 小红书 / Telegram / Chromium / 自定义),每个应用作为独立实例被 bridge 管理,对外暴露结构化接口。


阅读提示:本文档是目标架构设计,同时用 [现状] 标注当前代码的实际状态。当前 bridge 仍是一个约 2400 行的单文件服务(bridge/server.py全局单例、微信强耦合docker 与 panel 层已支持"一容器一应用"的多应用类型,但尚未支持"单容器多应用"。因此本文先聚焦 bridge 层改造,使其在"多容器单应用"模式下即可工作,单容器多应用作为后续扩展。


1. 背景与转变

1.1 现状

bridge 当前是单应用、单进程、强耦合微信的 API 服务:

  • bridge/server.py 约 2400 行单文件,全局单例 _state = AppState() 持有唯一的 XdotoolDriverDbReaderSendQueueQrCapture
  • 所有路由均为 /api/* 无前缀(如 /api/send/text/api/messages/since),直接操作全局 _state,没有 app_id 概念。
  • 响应模型字段名微信中心化:wechat_runningwechat_window_found、错误码 WECHAT_NOT_RUNNING / WECHAT_NOT_LOGGED_IN
  • DB 解密、联系人/消息读取、二维码登录全部写死微信表结构与 SQLCipher 参数。
  • 一容器只跑一个应用(由 docker/app-defs.shWOC_APP_TYPE 决定);想新增小红书/Telegram 自动化,当前只能再起一个容器。

1.2 转变

用户需求:一个容器内同时跑多个应用,每个应用对外暴露接口

这要求 bridge 从"微信适配器"升格为"应用桥接框架"

维度 现状 目标
容器内应用数 1WOC_APP_TYPE 决定) N动态注册
bridge 进程 跟随单一应用 一个 bridge 管理多个应用实例
路由 微信专属路由无前缀 通用路由无前缀 + 每个应用实例独立命名空间
状态 全局单例 _state 按 app_id 隔离的 AppInstance 容器
资源调度 不需要 必须串行化 X11 / 剪贴板 / 焦点操作

1.3 已具备的基础(非本阶段改造重点)

  • docker 层已通过 WOC_APP_TYPE 支持多种应用类型:wechat / telegram / chromium / customdocker/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 设计原则

  1. 组合优于继承driver 持有通用工具实例(self.xd),不继承工具类
  2. 一应用一命名空间:每个实例独立 app_id,路由与状态都按 app_id 隔离
  3. 声明式能力driver 声明能力集合,框架据此挂载路由与协商
  4. 串行化 X11:所有窗口操作经统一 UIScheduler 排队执行
  5. 应用无关的 DB 抽象延迟SQLCipher 解密暂留微信 driver 内,待第二个 SQLCipher 应用出现再抽公共层
  6. 向后兼容过渡旧路径与新路径并存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 进程也按单实例设计;AppManagerUISchedulerapps.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_iddriver_classbinary_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.pybridge/xdotool_driver.py 中,二者均强耦合微信。拆分时建议:

  1. xdotool_driver.py 中通用 X11 操作(_run_key_paste_via_xclip、窗口几何)抽成 XdotoolBase
  2. 把微信专属方法(find_wechat_windowsend_text_open_session_by_name)迁到 drivers/wechat/driver.pyWechatDriver
  3. 微信 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.pybridge/xdotool_driver.pybridge/send_queue.py 等根级文件中。S1 阶段可按以下顺序迁移,避免一次性大爆炸:

  1. 先把 server.py 中通用配置/错误/模型迁到 core/
  2. 创建 drivers/base.pydrivers/wechat/
  3. 保持 main.py 仍只注册一个 WechatDriver,行为与旧版完全一致。

5.2 AppManager应用管理器

框架核心组件,管理容器内所有 AppInstance 的生命周期。

职责

  • 维护实例注册表(内存 + 持久化到 /config/apps.json
  • 提供实例 CRUDregister / 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 UISchedulerX11 操作调度器)

问题:多应用共用一个 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),且全部写死微信逻辑。本阶段目标:

  1. 新增 /api/apps/{app_id}/* 路由层;
  2. 将旧路由作为 alias 内部转发到 /api/apps/default/*
  3. /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_RUNNINGWECHAT_NOT_LOGGED_IN 等。拆分阶段应:

  1. 保留旧错误码作为微信 driver 的返回值;
  2. 新增框架级错误码上表;
  3. alias 路由命中时仍可使用旧错误码,避免面板/外部系统改动。

6.5 向后兼容层

现有无前缀路由(/api/messages/api/send/text 等)保留为 alias内部转发到 /api/apps/{default_app_id}/...

默认实例选择规则

  1. 若容器内只有一个实例,自动作为 default
  2. 若多实例,读取 /config/.woc-default-app(由面板或首次注册时写入)
  3. 都没有则返回 404

alias 路由行为

  • 命中 alias 时日志打 DEPRECATED warning提示调用方迁移
  • alias 在 major 版本升级时下线v3.0.0 移除)

6.6 与既有系统的兼容性约束

改造必须尊重以下已验证的硬约束,否则现有微信实例会损坏:

  1. 鉴权/api/bridge/:id/* 外部调用依赖 WOC_BRIDGE_API_TOKEN 作为 Bearer token修改 .env 后必须重建 panel 容器(而非重启)才能生效。
  2. DB 解密:微信 DB 仅兼容 Linux WeChat 4.x使用 SQLCipher 4 参数AES-256-CBC、PBKDF2-HMAC-SHA512、256000 rounds、mac_salt = salt XOR 0x3A。解密能力保留在 drivers/wechat/ 内,不提前抽象。
  3. ptrace:自动提取 key 需要容器具备 SYS_PTRACE capability 或 --privileged,且宿主 ptrace_scope=0
  4. DB 路径:自动检测写死 /config/xwechat_files/<wxid>/db_storage/message/message_0.db;改为 driver 内部常量,不作为框架公共假设。
  5. 发送定位:微信 send_text/send_file 使用 display_name(备注/昵称)而非 wxid 搜索会话,该行为由 WechatDriver 封装,不进入框架公共层。
  6. 容器启动:当前 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 启动流程

  1. POST /api/apps 注册实例 → 状态 registered
  2. POST /api/apps/{app_id}/start → 状态 starting
  3. driver.install()(首次启动,下载/解压)
  4. driver.start()(启动应用进程)
  5. 轮询 driver.find_window() 直到窗口出现(超时 60s
  6. 状态 running
  7. 启动失败 → 状态 crashed,记录错误

8.3 自动启动

容器启动时AppManager 读取 /config/apps.json,对所有 auto_start=true 的实例按顺序执行启动流程。

顺序约束:实例间启动间隔 5 秒(避免同时启动多个 GUI 应用导致内存峰值)。

8.4 健康检查

AppManager 后台任务(每 30s

  1. 遍历所有 running 状态实例
  2. 调用 driver.is_running()
  3. 失败则状态转 crashed
  4. 记录崩溃次数与时间
  5. 若配置了自动恢复且未超限(默认 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.pyAppDriver 抽象基类。
  • 微信逻辑迁移到 drivers/wechat/WechatDriver(AppDriver) 用组合方式持有 XdotoolBase
  • 微信路由迁移到 drivers/wechat/routes.py
  • main.pyWOC_APP_TYPE,只注册一个 driver创建一个 AppInstanceapp_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 数组。
  • 旧字段日志打 DEPRECATED warning。

验证:面板与外部调用方无需改动即可工作;新路径可通过 /api/apps/default/* 访问。

S3UIScheduler 与 SendQueue 正交化

目标:解决多实例共享 X server 时的焦点/剪贴板冲突。

改动

  • 新增 core/ui_scheduler.py,所有 X11 写操作经其串行执行。
  • 每个 AppInstance 持有独立 SendQueueUIScheduler 全局唯一。
  • 单应用模式下行为不变,多实例模式下才体现串行价值。

验证:高并发调用 /api/apps/default/send/text 不再出现剪贴板污染。

S4AppManager 与单容器多实例(未来扩展)

目标:支持容器内注册多个 AppInstance

改动

  • 实现 core/app_manager.pycore/app_instance.pycore/app_kind.py
  • 新增 /api/apps CRUD 路由。
  • /config/apps.json 持久化。
  • 需同步改造 docker/autostart 以支持启动多个应用。

验证:可在同一容器内通过 API 注册并启动微信 + 小红书两个实例。

S5新增 Xiaohongshu/Telegram Driver框架可扩展性验证

目标:落地非微信应用自动化。

改动

  • 实现 drivers/xiaohongshu/driver.py + routes.pyP0publish/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 完全不做

  1. 不引入应用间隐式依赖driver 之间不互相调用,编排由外部完成。
  2. 不做多容器 bridge 集群:一个 bridge 进程只管一个容器内的应用,跨容器调度由面板负责。
  3. 不做应用沙箱:应用间共享 X server 与文件系统(受 Linux 权限控制),不做额外隔离。
  4. 不做 RBACapp_id 级别的权限控制留给面板层bridge 层只做 Bearer Token 全局鉴权。
  5. 不改 KasmVNC 串流层:桌面串流保持现状(全屏共享),不按应用切分串流。
  6. 不做 OCR:不引入 tesseract页面识别靠 URL + 坐标启发式。

12.2 本阶段先不做

  1. 不抽象 SQLCipher 解密:解密能力暂留 drivers/wechat/,待第二个 SQLCipher 应用出现再抽公共层。
  2. 不实现单容器多应用启动docker/autostart 当前只启动一个应用,改造它需要 panel/docker 同步调整,不在本阶段 bridge 文档范围。
  3. 不新增非微信 driver:先完成框架抽象与微信 driver 迁移,再落地 Xiaohongshu/Telegram driver。
  4. 不替换全局 SendQueueS1 保持现有全局队列S3 再下放到每个 AppInstance
  5. 不引入新依赖:仍只使用 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 必须串行;当前尚未实现
应用间通信 显式编排(无直接调用) 流程可观测、可调试
部署模型 单容器多应用 + 多容器单应用并存 兼顾资源效率与隔离性;当前仅多容器单应用可用