docs: 新增PRD规范与WechatOnCloud改造、多应用桥接框架文档

新增三份文档:
1. 产品需求文档(PRD)编写规范
2. WechatOnCloud容器化微信改造需求方案
3. 多应用桥接框架整体设计方案
This commit is contained in:
Kris 2026-07-18 16:04:25 +08:00
parent 3effe1338c
commit 67aab58a00
7 changed files with 9679 additions and 0 deletions

1405
doc/bridge-prd-v1.0.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,264 @@
# 产品需求文档PRD规范
> 本规范用于约束 ForcePilot 平台产品需求文档Product Requirements Document简称 PRD的编写、评审与维护确保需求表达清晰、可追溯、可验收。
---
## 1. 目的与适用范围
### 1.1 目的
- 统一 PRD 的结构、粒度与写作风格,降低沟通成本
- 明确需求从提出到交付的文档化要求,支撑研发、测试、设计协同
- 为后续的需求变更管理、验收与回溯提供基线
### 1.2 适用范围
- 适用于 ForcePilot 平台所有新增功能、功能优化、重构类需求
- Bug 修复、文案调整等小改动可使用精简版 PRD见第 7 节)
- 紧急线上故障修复可先口头/IM 沟通,事后补齐文档
---
## 2. 文档基本信息
每份 PRD 必须包含以下元信息:
| 字段 | 说明 |
| --- | --- |
| 文档标题 | 简洁明确,体现功能主体与意图 |
| 文档版本 | 采用 `v主版本.次版本`,如 v1.0、v1.1 |
| 作者 | 姓名 + 工号/账号 |
| 创建日期 | YYYY-MM-DD |
| 最后更新日期 | YYYY-MM-DD |
| 状态 | 草稿 / 评审中 / 已确认 / 已归档 |
| 关联需求 | 关联的需求 ID、Issue 链接或 PR 链接 |
| 评审人 | 产品、研发、测试、设计等关键评审人 |
---
## 3. PRD 标准结构
一份完整的 PRD 应按以下顺序组织章节,无内容的章节标注「不涉及」并保留标题:
1. 背景与目标
2. 名词解释
3. 用户与场景
4. 功能需求
5. 非功能需求
6. 交互与设计要求
7. 数据与接口需求
8. 验收标准
9. 排期与里程碑
10. 风险与依赖
11. 变更记录
---
## 4. 各章节编写规范
### 4.1 背景与目标
- **背景**:说明需求来源(用户反馈、业务目标、技术债等),避免空泛描述
- **目标**:使用可度量的指标或明确的终态描述,避免「提升体验」这类无法验证的表述
- **非目标**:明确本次不做的事项,防止范围蔓延
### 4.2 名词解释
- 列出文档中出现的领域术语、缩写、业务概念
- 与既有文档/代码中的术语保持一致,避免同义多词
### 4.3 用户与场景
- 明确目标用户角色知识库管理员、普通使用者、API 调用方)
- 描述典型使用场景,采用「作为…我希望…以便…」的用户故事格式
- 复杂流程需配流程图或时序图
### 4.4 功能需求
- 采用**需求项编号**(如 FR-01、FR-02便于评审与追溯
- 每个需求项包含:
- 需求描述
- 输入 / 输出
- 业务规则
- 异常与边界情况
- 优先级P0 / P1 / P2
- 禁止将多个独立功能合并为一个需求项
- 涉及权限的需求需明确角色与权限矩阵
### 4.5 非功能需求
按需覆盖以下维度,无要求时显式标注「不涉及」:
- 性能(响应时间、吞吐量、并发数)
- 可用性SLA、容灾
- 安全性(鉴权、数据加密、审计)
- 兼容性浏览器、API 版本、依赖服务版本)
- 可观测性(日志、指标、告警)
- 国际化与无障碍
### 4.6 交互与设计要求
- 引用原型图/设计稿链接,避免在 PRD 中重复描述视觉细节
- 明确关键交互逻辑loading 态、空态、错误态、确认弹窗)
- 与设计规范文档保持一致
### 4.7 数据与接口需求
- 涉及新增/变更的数据模型需列出字段说明
- 接口需求需说明:路径、方法、入参、出参、错误码
- 与既有 API 规范保持一致,避免破坏性变更;如必须破坏需显式标注并给出迁移方案
### 4.8 验收标准
- 每个 P0/P1 功能需求必须对应至少一条可执行的验收标准
- 验收标准应可被测试用例直接覆盖,避免主观表述
- 推荐使用 Given-When-Then 格式
### 4.9 排期与里程碑
- 列出关键节点:设计完成、开发完成、联调完成、测试完成、上线
- 标注负责人与预期日期,日期变更需同步更新变更记录
### 4.10 风险与依赖
- 识别技术依赖、外部服务依赖、资源依赖
- 识别潜在风险并给出应对策略
### 4.11 变更记录
- 每次文档修订需追加一行:版本、日期、修改人、修改内容摘要
- 已确认状态的文档变更需重新触发评审
---
## 5. 写作与格式规范
### 5.1 语言风格
- 使用简洁的书面中文,避免口语化与歧义
- 使用「必须 / 应当 / 可以」区分强制、推荐、可选级别
- 术语统一,避免中英文混用造成的歧义
### 5.2 Markdown 格式
- 标题层级不超过四级(`####`
- 表格用于结构化数据,列表用于步骤或枚举
- 代码、字段名、接口路径使用反引号包裹
- 流程图、时序图使用 Mermaid 语法,确保可渲染
### 5.3 图表规范
- 所有图表需有图题与编号(如:图 1 用户登录流程)
- 截图需标注来源与版本,避免使用过期截图
- 图表中的文字应可被复制检索,关键流程图优先使用 Mermaid
### 5.4 链接规范
- 引用内部文档使用相对路径
- 引用代码位置使用可点击的文件链接
- 外部链接需注明访问日期或版本
---
## 6. 优先级定义
| 级别 | 含义 | 验收要求 |
| --- | --- | --- |
| P0 | 必须完成,阻塞上线 | 必须有验收标准与测试用例 |
| P1 | 应当完成,影响主流程 | 必须有验收标准 |
| P2 | 可以完成,体验优化 | 可简化验收 |
---
## 7. 精简版 PRD
适用于改动范围小、影响面有限的需求,至少包含:
1. 背景与目标1-2 句)
2. 功能需求(编号 + 描述 + 优先级)
3. 验收标准
4. 排期
---
## 8. 评审与维护流程
### 8.1 评审流程
1. 作者完成草稿,状态置为「评审中」
2. 产品、研发、测试、设计分别评审,提出问题在文档中批注
3. 作者汇总意见并修订,更新版本号
4. 全部意见 resolved 后,状态置为「已确认」,进入开发
### 8.2 变更管理
- 已确认的 PRD 如需变更,必须更新「变更记录」并通知相关方
- 涉及范围、排期、验收标准的变更需重新评审
- 开发过程中发现的需求偏差,应在变更记录中记录并同步
### 8.3 归档
- 功能上线且验收通过后PRD 状态置为「已归档」
- 归档文档不再修改,后续迭代新建版本或新文档
---
## 9. 存放与命名规范
### 9.1 存放位置
- PRD 文档统一存放于 `docs/vibe/<版本号>/需求文档/` 目录下
- 关联的设计稿、原型图链接至 `docs/vibe/<版本号>/原型图/` 目录
### 9.2 命名规范
- 文件名格式:`<模块>-<功能>-PRD-v<版本>.md`
- 示例:`knowledgebase-import-PRD-v1.0.md`
- 全部使用小写英文与连字符,避免空格与中文文件名
---
## 10. 模板速查
```markdown
# <功能名称> 产品需求文档
| 字段 | 内容 |
| --- | --- |
| 文档版本 | v1.0 |
| 作者 | |
| 创建日期 | |
| 最后更新日期 | |
| 状态 | 草稿 |
| 关联需求 | |
| 评审人 | |
## 1. 背景与目标
### 1.1 背景
### 1.2 目标
### 1.3 非目标
## 2. 名词解释
## 3. 用户与场景
## 4. 功能需求
### FR-01 <需求标题>
- 描述:
- 输入:
- 输出:
- 业务规则:
- 异常与边界:
- 优先级P0
## 5. 非功能需求
## 6. 交互与设计要求
## 7. 数据与接口需求
## 8. 验收标准
- AC-01Given… When… Then…
## 9. 排期与里程碑
## 10. 风险与依赖
## 11. 变更记录
| 版本 | 日期 | 修改人 | 摘要 |
| --- | --- | --- | --- |
| v1.0 | | | 初稿 |
```
---
## 11. 检查清单
PRD 提交评审前,作者需逐项确认:
- [ ] 文档元信息完整
- [ ] 目标可度量,非目标已明确
- [ ] 功能需求已编号且粒度合理
- [ ] 每个 P0/P1 需求有对应验收标准
- [ ] 非功能需求已逐项确认或标注「不涉及」
- [ ] 接口与数据变更已标注破坏性影响
- [ ] 图表可渲染、链接可访问
- [ ] 命名与存放符合本规范

View File

@ -0,0 +1,939 @@
# 多应用桥接框架设计
> 目标:将 bridge 从"微信专属 API 服务"演进为"容器内多桌面应用的统一桥接框架"。一个容器内可同时运行多个应用(微信 / 小红书 / Telegram / Chromium / 自定义),每个应用作为独立实例被 bridge 管理,对外暴露结构化接口。
---
> **阅读提示**:本文档是**目标架构设计**,同时用 `[现状]` 标注当前代码的实际状态。当前 bridge 仍是一个约 2400 行的单文件服务([bridge/server.py](file:///D:/WechatOnCloud-main/bridge/server.py)全局单例、微信强耦合docker 与 panel 层已支持"一容器一应用"的多应用类型,但尚未支持"单容器多应用"。因此本文先聚焦 bridge 层改造,使其在"多容器单应用"模式下即可工作,单容器多应用作为后续扩展。
---
## 1. 背景与转变
### 1.1 现状
bridge 当前是**单应用、单进程、强耦合微信**的 API 服务:
- [bridge/server.py](../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](../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](../docker/app-defs.sh))。
- panel 的 `Instance.appType` 已能标识实例承载的应用类型,并透传给容器环境变量([panel/server/src/store.ts](../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](../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 进程也按单实例设计;`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 一一绑定。
```python
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/server.py) 和 [bridge/xdotool_driver.py](../bridge/xdotool_driver.py) 中,二者均强耦合微信。拆分时建议:
> 1. 把 `xdotool_driver.py` 中通用 X11 操作(`_run`、`_key`、`_paste_via_xclip`、窗口几何)抽成 `XdotoolBase`
> 2. 把微信专属方法(`find_wechat_window`、`send_text`、`_open_session_by_name`)迁到 `drivers/wechat/driver.py``WechatDriver`
> 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.py](../bridge/server.py)、[bridge/xdotool_driver.py](../bridge/xdotool_driver.py)、[bridge/send_queue.py](../bridge/send_queue.py) 等根级文件中。S1 阶段可按以下顺序迁移,避免一次性大爆炸:
> 1. 先把 `server.py` 中通用配置/错误/模型迁到 `core/`
> 2. 创建 `drivers/base.py``drivers/wechat/`
> 3. 保持 `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`
```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 排队执行。
```python
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 使用方式**
```python
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](../bridge/send_queue.py),用于单应用发送限流;`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
返回框架整体状态 + 所有实例摘要。
```json
{
"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
注册新实例。
```json
// 请求
{
"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}
单实例详细状态。
```json
{
"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。
```json
{
"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 实现示例**
```python
# 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](../bridge/models.py) 中只有微信中心化错误码,如 `WECHAT_NOT_RUNNING`、`WECHAT_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](../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 事件总线(未来扩展)
可选的发布订阅机制:
```python
# 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](../bridge/server.py) 约 2400 行单文件,全局单例 `_state`
- 所有 `/api/*` 路由直接写死微信逻辑,无 `app_id`、无 driver 抽象。
- [docker/autostart](../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` 数组。
- 旧字段日志打 `DEPRECATED` warning。
**验证**:面板与外部调用方无需改动即可工作;新路径可通过 `/api/apps/default/*` 访问。
### S3UIScheduler 与 SendQueue 正交化
**目标**:解决多实例共享 X server 时的焦点/剪贴板冲突。
**改动**
- 新增 `core/ui_scheduler.py`,所有 X11 写操作经其串行执行。
- 每个 `AppInstance` 持有独立 `SendQueue``UIScheduler` 全局唯一。
- 单应用模式下行为不变,多实例模式下才体现串行价值。
**验证**:高并发调用 `/api/apps/default/send/text` 不再出现剪贴板污染。
### S4AppManager 与单容器多实例(未来扩展)
**目标**:支持容器内注册多个 `AppInstance`
**改动**
- 实现 `core/app_manager.py`、`core/app_instance.py`、`core/app_kind.py`。
- 新增 `/api/apps` CRUD 路由。
- `/config/apps.json` 持久化。
- 需同步改造 [docker/autostart](../docker/autostart) 以支持启动多个应用。
**验证**:可在同一容器内通过 API 注册并启动微信 + 小红书两个实例。
### S5新增 Xiaohongshu/Telegram Driver框架可扩展性验证
**目标**:落地非微信应用自动化。
**改动**
- 实现 `drivers/xiaohongshu/driver.py` + `routes.py`P0publish/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. **不做 RBAC**`app_id` 级别的权限控制留给面板层bridge 层只做 Bearer Token 全局鉴权。
5. **不改 KasmVNC 串流层**:桌面串流保持现状(全屏共享),不按应用切分串流。
6. **不做 OCR**:不引入 tesseract页面识别靠 URL + 坐标启发式。
### 12.2 本阶段先不做
1. **不抽象 SQLCipher 解密**:解密能力暂留 `drivers/wechat/`,待第二个 SQLCipher 应用出现再抽公共层。
2. **不实现单容器多应用启动**[docker/autostart](../docker/autostart) 当前只启动一个应用,改造它需要 panel/docker 同步调整,不在本阶段 bridge 文档范围。
3. **不新增非微信 driver**:先完成框架抽象与微信 driver 迁移,再落地 Xiaohongshu/Telegram driver。
4. **不替换全局 `SendQueue`**S1 保持现有全局队列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 必须串行;当前尚未实现 |
| 应用间通信 | 显式编排(无直接调用) | 流程可观测、可调试 |
| 部署模型 | 单容器多应用 + 多容器单应用并存 | 兼顾资源效率与隔离性;当前仅多容器单应用可用 |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff