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

103 KiB
Raw Blame History

WechatOnCloud Bridge 产品需求文档

字段 内容
文档标题 WechatOnCloud Bridge 产品需求文档
文档版本 v1.0
作者 TRAE Agent
创建日期 2026-07-17
最后更新日期 2026-07-17
状态 草稿
关联需求 bridge 全量能力基线(回溯性 PRD对应源码版本 BRIDGE_VERSION=1.0.1
评审人 产品 / 研发 / 测试 / 运维

说明:本 PRD 为回溯性文档bridge 模块已实现并运行。文档目标是基于 d:\WechatOnCloud-main\bridge\woc_bridge\ 源码建立完整、准确的需求基线,所有 API 路径、错误码、配置默认值、阈值均以源码为准。


1. 背景与目标

1.1 背景

bridge 是 WechatOnCloudWOC项目中运行在微信容器内的 FastAPI 服务,定位为 Panel管理前端与 WeChat 客户端之间的适配层。其核心职责包括:

  • DB 解密读取:通过纯 Python 实现 SQLCipher 4 解密微信 4.x Linux 本地 DB提供消息 / 联系人 / 群 / 朋友圈查询能力。
  • UI 自动化:基于 xdotool + OpenCV 驱动 WeChat 4.x Linux X11 窗口,完成消息发送、朋友圈、好友管理、撤回 / 转发等程序化操作。
  • 实时消息推送:通过 SSE 长连接替代客户端轮询。
  • 群发编排:串行复用 FlowOrchestrator + SendQueue含三层幂等与风控。
  • 聊天记录导出:支持 HTML / CSV / JSON / TXT 四种格式,同步与异步两种模式。

bridge 实现过程中代码迭代较多,但缺乏统一的需求基线文档,存在功能边界不清、阈值散落代码各处、错误码与配置项无集中说明等问题。本 PRD 用于补齐该文档缺口。

1.2 目标

  • 覆盖 bridge 全部对外 API 端点(≥ 40 个),逐一列明路径、方法、入参、出参、错误码。
  • 集中说明 27 个错误码、24 项环境变量配置、关键运行时阈值,建立可追溯的需求基线。
  • 明确 P0 / P1 功能的验收标准(采用 Given-When-Then 格式覆盖登录扫码、消息发送幂等、撤回时限、群发风控、好友自动通过规则决策、DB 解密、SSE 推送、导出 TTL、熔断与限流触发等关键路径。
  • 明确非功能阈值限流、SSE、熔断、幂等、风控、撤回时限、慢请求、大文件超时等所有数值来自源码。

1.3 非目标

  • 不涉及 Panel 侧(管理前端)需求。
  • 不涉及微信客户端WeChat 4.x Linux改造需求。
  • 不涉及本次代码实现(回溯性文档,代码已落地)。
  • 不涉及 channels 适配器Panel 侧调用 bridge 的 SDK需求。

2. 名词解释

术语 含义
bridge 运行在微信容器内的 FastAPI 服务,本 PRD 主体。监听 0.0.0.0:8088
Panel WechatOnCloud 的管理前端,通过 HTTP 调用 bridge 暴露的 API。
实例 一个 bridge 进程对应一个微信账号。多账号场景需多个容器实例隔离。
容器 bridge 运行的 Docker 容器,必须以 --cap-add=SYS_PTRACE 启动以允许内存扫描提取 DB 密钥。
SQLCipher 微信 4.x Linux 使用的加密 SQLite 实现WCDB参数见 5.3 节。
xdotool X11 自动化命令行工具bridge 的 UI 操作底层依赖。微信 4.x Linux 自绘 UI 不响应 Ctrl+V必须用 xdotool type 逐字符输入。
SSE Server-Sent Eventstext/event-stream 长连接,用于实时消息推送。
熔断器 CircuitBreaker连续失败达阈值后进入 OPEN 状态拒绝请求,经过恢复时间后进入 HALF_OPEN 试探。
幂等缓存 IdemCache基于 (flow_name, to_wxid, content, client_request_id) 的 SHA256 键TTL=300smax_size=1000防短时重复发送。
风控 群发场景的频率与配额限制:单批 ≤ 200、日频次 ≤ 3、批间隔 ≥ 7200s单条间隔抖动 2-4s。
WAL Write-Ahead LogSQLite 的预写日志。bridge 通过 mtime 感知 WAL 变化,发送后 DB 校验需等待 WAL 刷盘(约 1.5-2s
trace_id 12 字符 hex 字符串(如 a3b5c7d9e1f2),每请求生成,通过 ContextVar 贯穿日志。
SYS_PTRACE Linux capability允许进程 ptrace 其他进程。bridge 自动提取 DB 密钥需要扫描微信进程内存,依赖此能力(同时要求 /proc/sys/kernel/yama/ptrace_scope=0)。
flow UI 自动化的执行单元(如 SendTextFlowSendFileFlow),由 FlowOrchestrator 编排。
local_send_id bridge 本地生成的发送 ID格式 local_<unix秒>_<随机>),仅用于客户端幂等去重,不对应微信原生 msg_id,禁止用于 /api/media/{msg_id}

3. 用户与场景

3.1 目标用户角色

角色 说明 主要场景
Panel 管理员 通过浏览器操作 Panel 调用 bridge 的运营人员 扫码登录、群发营销、好友管理、发朋友圈、导出聊天记录、诊断排障
终端用户 被 bridge 自动化操作影响的微信账号所有者 收发消息、好友申请被自动通过、收到群发消息
API 调用方 第三方集成(如 channels 适配器)调用 bridge API 的程序 增量消息拉取、SSE 订阅、消息发送、状态查询
运维 负责容器部署、监控、故障排查 部署配置、监控 /metrics、诊断 autofix、密钥注入

3.2 用户故事

  • US-01作为 Panel 管理员,我希望扫码登录微信,以便 Panel 能远程操控该账号。
  • US-02作为 API 调用方,我希望按复合游标增量拉取消息,以便准确同步会话历史不重不漏。
  • US-03作为 API 调用方,我希望订阅 SSE 推送实时消息,以便降低拉取延迟并减少无效轮询。
  • US-04作为 Panel 管理员,我希望发送文本 / 图片 / 文件消息,以便程序化触达指定联系人。
  • US-05作为 Panel 管理员,我希望群发营销消息给一批联系人,以便批量触达且不被微信风控。
  • US-06作为 Panel 管理员,我希望配置好友自动通过规则,以便按黑/白名单、场景、关键词自动处理好友申请。
  • US-07作为 Panel 管理员,我希望发表朋友圈 / 分享公众号文章,以便程序化运营朋友圈。
  • US-08作为 Panel 管理员,我希望导出指定会话的聊天记录为 HTML/CSV/JSON/TXT以便备份或取证。
  • US-09作为运维我希望执行诊断与 autofix以便快速定位微信进程 / 登录态 / DB / xdotool 异常。
  • US-10作为运维我希望从 /metrics 抓取 Prometheus 指标,以便监控 bridge 运行状态。
  • US-11作为 API 调用方,我希望撤回 2 分钟内发送的消息,以便纠正错误发送。
  • US-12作为 Panel 管理员,我希望查询与修改好友备注,以便管理联系人元信息。

3.3 关键流程时序图

图 1扫码登录流程

sequenceDiagram
    participant Panel
    participant Bridge
    participant WeChat
    participant DB

    Panel->>Bridge: GET /api/status
    Bridge->>WeChat: pgrep -x wechat
    Bridge->>WeChat: xdotool search 微信
    Bridge-->>Panel: login_state=not_logged_in

    Panel->>Bridge: POST /api/login/qr/start
    Bridge->>WeChat: 激活窗口 + 截取二维码区域
    Bridge-->>Panel: qr_data_urldata:image/png;base64,...

    Panel->>Bridge: GET /api/login/qr/wait?timeout=30
    loop 每 2 秒
        Bridge->>WeChat: detect_login_state
        WeChat-->>Bridge: state
    end
    Bridge->>DB: 读取 self_info (wxid, nickname)
    Bridge-->>Panel: connected=true, credentials={wxid, nickname}

图 2消息发送全链路flow 路径)

sequenceDiagram
    participant Panel
    participant Route
    participant Orchestrator
    participant IdemCache
    participant Breaker
    participant SendQueue
    participant Flow
    participant DB

    Panel->>Route: POST /api/send/text {to_wxid, content, client_request_id}
    Route->>Route: 参数校验 + 登录态检测
    Route->>Route: 解析 display_name备注 > 昵称 > wxid5min TTL
    Route->>Orchestrator: send_text(to_wxid, content, display_name, client_request_id)

    Orchestrator->>IdemCache: get("send_text", to_wxid, content, client_request_id)
    alt 命中幂等
        IdemCache-->>Orchestrator: cached FlowResult
        Orchestrator-->>Route: skipped=true
    else 未命中
        Orchestrator->>Breaker: allow?
        alt OPEN
            Breaker-->>Orchestrator: reject
            Orchestrator-->>Route: error_code=BRIDGE_CIRCUITED (503)
        else CLOSED/HALF_OPEN
            Orchestrator->>SendQueue: enqueue(flow.run, delay_ms=同联系人1000/不同3000, wait_timeout=15000)
            SendQueue->>SendQueue: 限流检查1秒滑动窗口 max_calls_per_sec=10
            SendQueue->>Flow: run(FlowContext)
            Flow->>WeChat: activate + 搜索 + 进入会话 + 输入 + 回车
            Flow->>DB: 查询目标会话最新 (create_time, local_id) 基线
            Note over Flow,DB: 等 WAL 刷盘 1.5-2s轮询 DB 3s 校验内容匹配
            Flow-->>SendQueue: FlowResult(success, verified)
            SendQueue-->>Orchestrator: FlowResult
            alt 成功且 verified
                Orchestrator->>IdemCache: set(...)
                Orchestrator->>Breaker: record_success
            else 成功但 verify 失败
                Orchestrator->>Breaker: record_failure
            else 失败
                Orchestrator->>Orchestrator: session_cache.invalidate(to_wxid)
            end
            Orchestrator-->>Route: FlowResult
        end
    end
    Route-->>Panel: SendResponse(success, local_send_id, placeholder, verified, skipped)

图 3群发消息流程

sequenceDiagram
    participant Panel
    participant BatchRoute
    participant BatchWorker
    participant Orchestrator
    participant DB

    Panel->>BatchRoute: POST /api/send/batch {targets[200], content, client_request_id, abort_on_consecutive_fail=5}
    BatchRoute->>BatchWorker: submit_batch(req)
    BatchWorker->>BatchWorker: 风控校验:去重 ≤200 / 日频次 <3 / 间隔 ≥7200s
    alt 风控命中
        BatchWorker-->>BatchRoute: RATE_LIMITED(retry_after) / INVALID_PARAMS
    else 通过
        BatchWorker->>BatchWorker: batch_id 生成 + 持锁更新 _daily_count / _last_batch_end_time
        BatchWorker-->>BatchRoute: batch_id
    end
    BatchRoute-->>Panel: {batch_id, status_query_url}

    loop 串行(每个 target
        BatchWorker->>DB: 查 nickname 替换 {nickname} 占位符
        BatchWorker->>Orchestrator: send_text(to_wxid, content, client_request_id="{batch_id}:{to_wxid}")
        Note over Orchestrator: 复用单条幂等 + 熔断 + 入队 + DB 校验
        Orchestrator-->>BatchWorker: FlowResult
        alt 连续失败 ≥ abort_on_consecutive_fail
            BatchWorker->>BatchWorker: 整批中止,剩余标记 skipped
        else 非最后一条
            BatchWorker->>BatchWorker: sleep 2-4s 随机抖动
        end
    end

    Panel->>BatchRoute: GET /api/send/batch/{batch_id}/status
    BatchRoute-->>Panel: BatchSendStatus(total, processed, success, failed, skipped, progress, estimated_remaining_sec)

4. 功能需求

4.0 权限矩阵说明

  • 所有 /api/* 端点必须通过 Bearer Token 鉴权(环境变量 WOC_BRIDGE_API_TOKEN,由上游反向代理或 Panel 校验)。
  • Token 校验通过后授予全部功能权限,无细粒度角色划分。
  • /metrics/api/status/api/diagnostic/connectivity 允许无鉴权访问(健康检查与监控用)。
  • 本 PRD 不约束 Token 注入方式(由部署侧反代实现),仅约束业务行为。

FR-01 状态查询

  • 描述:聚合返回 bridge 运行状态,单次请求完成进程 / 窗口 / 登录态 / DB 可达性 4 类检测,并返回账号信息与客户端轮询建议参数。
  • 输入:无。
  • 输出StatusResponse,含 bridge_version / wechat_running / wechat_window_found / login_state / db_accessible / db_error_code / init_in_progress / init_progress_pct / current_wxid / current_nickname / uptime_seconds / display / max_batch_size / poll_interval_ms / send_queue_pending / media_supported / bridge_capabilities
  • 业务规则
    • 不抛业务错误:即使微信未运行 / DB 加密也返回 200调用方按字段判定下一步。
    • login_state 内联判定(not_running / not_logged_in / logged_in),避免重复 pgrep / xdotool。
    • DB 加密时细分 db_error_codeencrypted_key_ok / encrypted_no_key / key_extract_failed
    • current_wxid / current_nickname 仅在 db_accessible=truelogin_state=logged_in 时尝试读 DB失败静默为空字符串。
  • 异常与边界:组件未初始化时抛 BRIDGE_INTERNAL_ERROR(500)DB 读取通过 with_db_retry 装饰,自动重试。
  • 优先级P0。

FR-02 登录管理

  • 描述:提供扫码登录启动 / 等待、退出登录,以及微信进程重启能力。
  • 输入
    • POST /api/login/qr/start:无入参。
    • GET /api/login/qr/wait?timeout=30timeout 默认 30<1 抛 INVALID_PARAMS(400)>120 截断为 120。
    • POST /api/login/logout:无入参。
    • POST /api/wechat/restart:无入参。
  • 输出
    • QrLoginStartResultqr_data_urldata:image/png;base64,... / message / connected=false
    • QrLoginWaitResultconnected / message / qr_data_url / credentials={wxid, nickname}
    • LogoutResponsesuccess / message
    • RestartResponsesuccess / message / pid
  • 业务规则
    • 二维码有时效(微信约 60s 刷新),超时后调用方应重新调 qr/start
    • qr/wait 长轮询,每 2 秒检测一次登录态;not_running 时立即返回不再等待。
    • logout 幂等:未登录时返回 success=true + message="当前未登录,无需退出"
    • restart 流程pgrep 获取 PID → SIGTERM → 轮询等待新 PIDautostart 拉起30 秒超时抛 RESTART_TIMEOUT(408);不破坏登录态与数据卷。
  • 异常与边界
    • qr/start:窗口未找到抛 WINDOW_NOT_FOUND(503)
    • qr/waittimeout<1INVALID_PARAMS(400)
    • logoutUI 操作失败抛 LOGOUT_FAILED(500),窗口未找到抛 WINDOW_NOT_FOUND(503)
    • restart:超时抛 RESTART_TIMEOUT(408)pgrep 异常抛 BRIDGE_INTERNAL_ERROR(500)
  • 优先级P0。

FR-03 DB 解密与密钥管理

  • 描述:手动注入 64 位 hex 密钥、查询密钥缓存状态、显式触发后台密钥提取、查询初始化进度。
  • 输入
    • POST /api/db/decryptDbDecryptRequest{key: str(64-hex), salt?: str(32-hex)}
    • GET /api/db/key/status:无入参。
    • POST /api/db/initDbInitRequest{pid?, db_dir?, force=false}
    • GET /api/db/init/status:无入参。
  • 输出
    • DbDecryptResponsesuccess / verified / key_modeenc_keykey_material/ errorkey_mismatch / db_not_encrypted 等)。
    • DbKeyStatusResponsecached / sourceenv / api / auto_extract / file/ verified / key_prefix
    • DbInitResponsesuccess / statestarted / already_done / in_progress/ message / key_count
    • DbInitStatusResponsestateidle / running / success / failed/ progress_pct / message / key_count / error
  • 业务规则
    • 密钥必须是 64 位十六进制字符串,否则抛 INVALID_PARAMS(400)
    • DB 不存在抛 DB_NOT_FOUND(500)DB 不可读抛 DB_NOT_FOUND(500)permission明文 DB 返回 error="db_not_encrypted"
    • 用户未传 salt 时 bridge 自动读取当前 DB 的 salt读取失败抛 DB_ENCRYPTED(503)
    • 验证通过后按 salt 缓存到 KeyCache 并设置 source="api"
    • init 后台线程执行内存扫描,运行中重复调用返回 state="in_progress";已初始化且 force=false 返回 already_done
  • 异常与边界INVALID_PARAMS(400) / DB_NOT_FOUND(500) / DB_ENCRYPTED(503) / DB_KEY_INVALID(503)
  • 优先级P0。

FR-04 消息读取

  • 描述增量拉取、按会话拉取、关键词搜索、SSE 实时推送。
  • 输入
    • GET /api/messages/since?cursor=0&cursor_local_id=0&limit=50&is_sender=:复合游标 (create_time, local_id)limit 1-200 默认 50。
    • GET /api/messages/by_session?talker=&cursor=0&cursor_local_id=0&limit=50&direction=before&is_sender=directionbefore / afterlimit 1-200 默认 50。
    • GET /api/messages/search?keyword=&talker=&start_time=0&end_time=0&limit=50&is_sender=limit 1-200 默认 50。
    • GET /api/messages/stream:无入参,建立 SSE 长连接。
  • 输出
    • MessagesResponse / MessagesBySessionResponsemessages[] / next_cursor / next_cursor_local_id / has_more / talker
    • MessageSearchResponsemessages[] / total
    • SSE 流:sync / messages / status / heartbeat30s/ kicked 事件。
  • 业务规则
    • 复合游标(create_time, local_id) 共同定位分页边界,避免同秒消息重复 / 遗漏。客户端首次传 0下次用响应中的 next_cursor + next_cursor_local_id
    • has_more=true 当且仅当返回条数 ≥ limit,应立即继续拉取。
    • by_session 通过 talker 计算 Msg_<MD5(talker)> 表名直接查单表;before 模式返回升序(旧在前),after 模式返回降序之后转升序。
    • 群消息 content 形如 "wxid:\n正文"DbReader 拆出 sender 字段。
    • search 基于 SQL LIKE 模糊匹配,无法搜索 zstd 压缩消息(WCDB_CT_message_content==4keyword 中的 % / _ 已转义为字面量。
    • searchtotal 受每表 LIMIT 截断,可能小于实际命中数。
    • SSE内部 1s有订阅者/ 5s无订阅者轮询 DB mtime变化时读增量并广播。订阅者上限 MAX_SUBSCRIBERS=3,超限剔除最早订阅者并投递 kicked 事件。
    • SSE 断线补偿:客户端重连后用 sync 事件的 cursor 与本地 max(cursor, sync.cursor)/api/messages/since 补全。
  • 异常与边界INVALID_PARAMS(400)cursor<0、limit 越界、direction 非法、talker 为空、keyword 为空)/ DB_NOT_FOUND(500) / DB_ENCRYPTED(503)。SSE 不抛业务错误DB 不可读时通过 status 事件告知客户端。
  • 优先级P0since / stream / by_session/ P1search)。

FR-05 消息发送(单聊)

  • 描述:发送文本 / 图片 / 文件含撤回与转发experimental
  • 输入
    • POST /api/send/textSendTextRequest{to_wxid, content, display_name?, client_request_id=""}
    • POST /api/send/imageSendFileRequest{to_wxid, file_path, display_name?, client_request_id?}
    • POST /api/send/fileSendFileRequest{to_wxid, file_path, display_name?, client_request_id?}
    • POST /api/messages/revokeRevokeMessageRequest{talker, create_time, display_name?}
    • POST /api/messages/forwardForwardMessageRequest{talker, target_display_name, source_display_name?}
  • 输出
    • SendResponsesuccess / local_send_id / placeholder / verified / skipped / error
    • RevokeMessageResponse / ForwardMessageResponsesuccess / error / verified
  • 业务规则
    • 幂等键 client_request_id:空字符串表示不参与幂等校验。命中幂等缓存返回 skipped=true,避免重复发送。
    • display_name 解析优先级:显式传入 > 缓存5min TTLmax 1024 条)> contact.db 备注 remark > 昵称 nickname > wxid 本身。启动时预热最近 1000 个联系人。
    • 登录态前置检查:非 logged_in 直接抛 WECHAT_NOT_LOGGED_IN(401),避免 UI 操作引发不可预期行为。
    • 发送后 DB 校验:记录目标会话 (create_time, local_id) 基线 → 发送后轮询 DB间隔 0.2s,最多 3s→ 内容匹配校验防串号。校验失败抛 SEND_FAILED(500)
    • flow vs legacy 路径WOC_UI_BACKEND=flow(默认)走 FlowOrchestrator含幂等 / 熔断 / 会话缓存30s TTLmax 16/ 自适应延时(同联系人 1000ms / 不同 3000mslegacy 路径仅走 send_queue。
    • 文件路径白名单send/filesend/image):必须位于 /config/Desktop/ / /config/woc-uploads/ / /tmp/woc-files/ 内,且段级不含 ..os.path.realpath 解析后以白名单前缀开头。
    • 撤回时限110 秒(微信限制 2 分钟,预留 10s UI 操作余量)。now_ts - create_time > 110REVOKE_WINDOW_EXPIRED(409)
    • local_send_id 不对应微信原生 msg_id,禁止用于 /api/media/{msg_id}
  • 异常与边界
    • INVALID_PARAMS(400):参数为空 / 文件不存在 / 路径不在白名单。
    • WECHAT_NOT_LOGGED_IN(401):未登录。
    • REVOKE_WINDOW_EXPIRED(409):撤回超时。
    • RATE_LIMITED(429)发送限流命中1 秒滑动窗口 max_calls_per_sec=10或队列满。
    • SEND_FAILED(500)UI 操作失败或 DB 校验失败。
    • BRIDGE_CIRCUITED(503)DB 校验熔断器 OPEN。
    • SEND_TIMEOUT(503) / WINDOW_NOT_FOUND(503) / STATE_DIRTY(503) / X11_UNAVAILABLE(503) 等 Flow 错误码。
    • revoke / forward 为 experimental坐标为估算值需实测调优。
  • 优先级P0send/textsend/filesend/imagerevoke/ P2forward experimental

FR-06 群发消息

  • 描述:批量发送文本消息给一批联系人,含三层幂等与风控。
  • 输入
    • POST /api/send/batchBatchSendRequest{targets[1-200], content[1-2000], client_request_id?, abort_on_consecutive_fail=5(1-20)}
    • GET /api/send/batch/{batch_id}/status:路径参数 batch_id
    • POST /api/send/batch/{batch_id}/cancel:路径参数 batch_id
    • GET /api/send/batch?limit=20limit 1-100 默认 20。
  • 输出
    • 提交:{success, batch_id, status, status_query_url}
    • 查询:BatchSendStatus{batch_id, status, total, processed, success, failed, skipped, progress, estimated_remaining_sec, abort_reason, items[]}itemsBatchItemResult{to_wxid, status, error_code, error_message, verified}
    • 取消:{success, batch_id, status="cancelled"}
    • 列表:{batches: [BatchSendStatus, ...]}
  • 业务规则
    • 三层幂等
      • 批级:client_request_idNone 时自动生成 batch_<uuid.hex[:16]>),重复 batch_idINVALID_PARAMS(409)
      • 单条:内部按 f"{batch_id}:{to_wxid}" 作为 client_request_id 传给 orchestrator.send_text
      • orchestrator复用 IdemCacheTTL=300s
    • 风控参数
      • 单批 ≤ WOC_BATCH_MAX_PER_BATCH=200(去重后),超限抛 INVALID_PARAMS(400)
      • 日频次 ≤ WOC_BATCH_DAILY_LIMIT=3,超限抛 RATE_LIMITED(429) + retry_after 到次日 0 点。
      • 批间隔 ≥ WOC_BATCH_MIN_INTERVAL_SEC=7200 秒,不足抛 RATE_LIMITED(429) + retry_after
      • 单条间隔抖动:random.uniform(2.0, 4.0) 秒。
      • content 支持 {nickname} 占位符,发送前按目标联系人 remark > nickname > wxid 替换。
    • 风控状态:在 submit_batch 持锁时立即递增 _daily_count 与更新 _last_batch_end_time,防止并发提交绕过。
    • abort_on_consecutive_fail:连续失败达阈值(默认 5范围 1-20时整批中止剩余标记 skipped,状态置 failedabort_reason 给出原因。
    • cancel:标记 cancel_requested=true + task.cancel()。当前正在发送的条目标 unknown(消息可能已实际发送),剩余 pendingskipped。已结束状态调用返回 NOT_FOUND(404)
    • 状态机pendingrunningcompleted / failed / cancelled
    • estimated_remaining_sec:基于最近 10 条单条耗时均值 + 平均抖动 3s 估算。
  • 异常与边界
    • INVALID_PARAMS(400)targets 超过单批上限 / limit 越界。
    • INVALID_PARAMS(409)batch_id 重复。
    • RATE_LIMITED(429):日频次超限 / 批间隔不足。
    • NOT_FOUND(404)batch_id 不存在或已完成(查询 / 取消)。
    • 单条发送失败透传 SEND_FAILED / RATE_LIMITED / BRIDGE_CIRCUITED 等错误码到 BatchItemResult.error_code
  • 优先级P0。

FR-07 好友管理

  • 描述:好友申请查询 / 手动通过 / 自动通过规则配置 / 修改备注 / 添加好友。
  • 输入
    • GET /api/friends/requests?limit=50limit 1-200 默认 50。
    • POST /api/friends/acceptAcceptFriendRequest{stranger_wxid, nickname?}
    • GET /api/friends/auto_accept/config:无入参。
    • PUT /api/friends/auto_accept/configAcceptRuleConfig{enabled, accept_all, whitelist_wxids[], whitelist_nicknames[], keywords[], blacklist_wxids[], blacklist_nicknames[], allow_scenes[]}
    • GET /api/friends/auto_accept/status:无入参。
    • POST /api/contacts/{wxid}/remarkSetRemarkRequest{remark, display_name?}
    • POST /api/friends/addAddFriendRequest{keyword, message=""}
  • 输出
    • FriendRequestsResponserequests[FriendRequestItem{stranger_wxid, nickname, verify_message, scene, create_time}] / total
    • AcceptFriendResponse / SetRemarkResponse / AddFriendResponsesuccess / error / verified
    • AcceptRuleConfig:见输入。
    • AutoAcceptStatusrunning / enabled / processed_count / accepted_count / rejected_count / last_processed_time / cursor_create_time / cursor_local_id / db_readable / breaker_state
  • 业务规则
    • 规则引擎决策顺序AcceptRuleEngine.evaluate
      1. enabled=falseSKIP
      2. accept_all=trueACCEPT(跳过所有规则)
      3. stranger_wxidnickname 命中黑名单 → REJECT
      4. allow_scenes 非空且 scene 不在列表 → SKIP
      5. stranger_wxidnickname 命中白名单 → ACCEPT
      6. verify_message 包含任一 keywords(子串匹配)→ ACCEPT
      7. 无匹配 → SKIP
    • 配置热更新AcceptRuleEngineasyncio.Lock 保护读写,update_config 后同步更新 FriendRequestWatcher.enabled 状态并 start/stop watcher 协程(start() 幂等)。
    • 自动通过轮询间隔WOC_AUTO_ACCEPT_POLL_INTERVAL=3 秒。
    • 手动通过:经 send_queue 串行执行 UI 操作(定位好友申请条目 → 点击通过 → 确认),完成后用 _verify_accept_with_retry 校验 @stranger 后缀消失。
    • 修改备注post_verify 失效 display_name 缓存remark 已变)后查 DB 校验新值是否写入。
  • 异常与边界
    • INVALID_PARAMS(400)stranger_wxid / keyword / remark 为空、limit 越界。
    • WECHAT_NOT_LOGGED_IN(401):未登录。
    • SEND_FAILED(500)UI 操作失败。
    • RATE_LIMITED(429) / WINDOW_NOT_FOUND(503) 等透传错误码。
    • contacts/remark / friends/add 为 experimental坐标为估算值。
  • 优先级P0friends/acceptauto_accept/configauto_accept/statusfriends/requests/ P2friends/addcontacts/remark experimental

FR-08 联系人与群查询

  • 描述:联系人列表 / 详情、群聊列表、群成员。
  • 输入
    • GET /api/contacts?keyword=&limit=50keyword 模糊匹配 wxid / nickname / remark空串返回全部limit 1-200 默认 50。
    • GET /api/contacts/{wxid}:路径参数。
    • GET /api/groups?limit=50limit 1-200 默认 50。
    • GET /api/groups/{wxid}/members:路径参数(wxid 形如 xxxxx@chatroom)。
  • 输出
    • ContactsResponsecontacts[Contact{wxid, nickname, remark, avatar_url, type, alias, encrypt_username, quan_pin, pin_yin_initial, big_head_url, small_head_url, description, local_type, verify_flag, delete_flag, chat_room_type}] / total
    • Contact:单条记录(同上)。
    • GroupsResponsegroups[Contact] / total(群聊判定 username LIKE '%@chatroom')。
    • GroupMembersResponsegroup_wxid / members[GroupMember{wxid, nickname, display_name, is_admin}] / total
  • 业务规则
    • get_contact_detail 不存在的 wxid 返回 CONTACT_NOT_FOUND(404),与 DB 不可达错误区分。
    • 群聊 username 也可用 /api/contacts/{wxid} 查询DbReader 视为联系人)。
    • 群 wxid 不存在或无成员记录时返回空列表而非错误。
  • 异常与边界INVALID_PARAMS(400)limit 越界)/ DB_NOT_FOUND(500) / DB_ENCRYPTED(503) / CONTACT_NOT_FOUND(404)
  • 优先级P0。

FR-09 朋友圈

  • 描述:朋友圈时间线读取、发表纯文字 / 图片、分享公众号文章、点赞 / 评论 / 删除。
  • 输入
    • GET /api/moments/timeline?cursor=0&limit=20limit 1-50 默认 20cursor ≥ 0。
    • POST /api/moments/publishMomentPublishRequest{content}
    • POST /api/moments/publish_imageMomentPublishImageRequest{image_path, content=""}
    • POST /api/moments/share_articleMomentShareArticleRequest{public_account, article_index=1, comment?}
    • POST /api/moments/likeMomentLikeRequest{moment_index=1}
    • POST /api/moments/commentMomentCommentRequest{comment, moment_index=1}
    • POST /api/moments/deleteMomentDeleteRequest{moment_index=1}
  • 输出
    • MomentsTimelineResponsemoments[MomentItem{moment_id, content, create_time, author_wxid}] / next_cursor / has_more / status
    • 发表响应:MomentPublishResponse / MomentPublishImageResponse / MomentShareArticleResponsesuccess / local_moment_id / placeholder=false / error
    • 互动响应:MomentLikeResponse / MomentCommentResponse / MomentDeleteResponsesuccess / error / verified
  • 业务规则
    • status 字段timelineWeChat 4.x 朋友圈 schema 未公开,需容错探测,可能值:ok / db_not_found / table_not_found / no_columns / no_time_column / db_encrypted / query_error
    • timeline 不抛 DB_NOT_FOUNDDB 不存在属正常情况(新账号),通过 status=db_not_found 告知。
    • image_path 白名单os.path.realpath 后必须以 /config/ / /tmp/ / /data/ 开头,且文件存在可读。
    • 所有写操作经 send_queue 串行执行,避免与发消息等 UI 操作竞态。
    • local_moment_id 格式 moment_<unix秒>_<随机>share_<unix秒>_<随机>,不对应微信原生朋友圈 ID。
    • like / commentpost_verify 与 UI 操作一起在 send_queue 内串行执行,避免 after 截图被下一个 UI 操作污染。
    • delete删除前捕获朋友圈数量基线post_verify 比对 MomentsPost 记录是否减少。
    • moment_index=1 表示最新一条。
  • 异常与边界
    • INVALID_PARAMS(400)content / image_path / public_account 为空、moment_index<1 / article_index<1limit 越界、cursor<0、路径不在白名单。
    • WECHAT_NOT_LOGGED_IN(401):未登录。
    • WINDOW_NOT_FOUND(503) / SEND_FAILED(500)
    • like / comment / delete / publish_image 为 experimental坐标为估算值需实测调优。
  • 优先级P0timelinepublishshare_article/ P2like / comment / delete / publish_image experimental

FR-10 媒体下载

  • 描述:联系人头像、消息媒体文件下载。
  • 输入
    • GET /api/media/avatar/{wxid}:路径参数。
    • GET /api/media/{msg_id}:路径参数(来自 /api/messages/since 返回的 msg_id)。
  • 输出Response:二进制内容 + Content-Typeimage/jpeg / audio/amr / video/mp4 / application/octet-stream 等)。
  • 业务规则
    • 头像:优先从 contact.dbbig_head_url / small_head_url / avatar_urlHTTP 链接则拉取透传二进制。
    • 头像 CDN 白名单:仅允许 wx.qlogo.cn / thirdwx.qlogo.cn / wxhead.clouddn.com / thirdqq.qlogo.cn / q.qlogo.cn
    • 头像大小上限_AVATAR_MAX_BYTES=10MB(头像通常 < 1MBContent-Length 预检 + 实际读取校验,urlopen 超时 15s。
    • 路由声明顺序敏感/api/media/avatar/{wxid} 必须在 /api/media/{msg_id} 之前声明,否则 FastAPI 会把 "avatar" 当作 msg_id 匹配。
    • 媒体 .dat 解密:微信 4.x 的 .dat 文件采用单字节 XOR 加密首字节为密文DbReader 通过对比已知图片格式 magic bytes 推导 XOR key 后逐字节解密。
  • 异常与边界
    • MEDIA_NOT_FOUND(404):未找到联系人 / 无有效头像 URL / 头像 URL 校验失败 / 头像下载失败 / 媒体文件未找到或解析失败。
    • DB_ENCRYPTED(503) / DB_NOT_FOUND(500)
  • 优先级P1。

FR-11 聊天记录导出

  • 描述:导出指定会话消息为 HTML / CSV / JSON / TXT支持同步与异步两种模式。
  • 输入
    • GET /api/messages/export?talker=&format=html&limit=1000&include_media=false&media_inline=false&start_time?&end_time?:同步,limit 1-1000。
    • POST /api/messages/exportExportRequest{talker, format="html", limit=1000(1-100000), include_media=false, media_inline=false, start_time?, end_time?}:异步。
    • GET /api/messages/export/{task_id}/status:路径参数。
    • GET /api/messages/export/{task_id}/download:路径参数。
    • DELETE /api/messages/export/{task_id}:路径参数。
  • 输出
    • 同步:StreamingResponseContent-Disposition: attachment 触发下载MIME 按格式(text/html / text/csv / application/json / text/plain)。
    • 异步提交:ExportTaskStatus{task_id, status="running", progress=0.0}
    • 状态查询:ExportTaskStatus{task_id, status, progress, total, processed, download_url?, expires_at?, error_message?}
    • 下载:FileResponse(单文件或 zip
    • 取消:{success, task_id, status="cancelled"}
  • 业务规则
    • 同步阈值limit ≤ _SYNC_EXPORT_LIMIT=1000,超限必须走异步。
    • 异步并发:全局同时只允许 1 个任务运行(_MAX_CONCURRENT_EXPORT_TASKS=1),提交时即置 running(避免并发提交绕过)。
    • 异步分页:每批 _EXPORT_BATCH_SIZE=200 条,流式写入主文件。
    • TTL:完成后 expires_at = now + export_task_ttl_sec=72002 小时),过期后懒清理(删除任务目录与文件)。
    • 媒体打包include_media=truemedia_inline=false 时拷贝解密后的 .dat 媒体到 media/ 子目录并打 zipmedia_inline=true 时图片转 base64 内联(仅 HTML仅适合少量图片
    • HTML 渲染:仿微信气泡样式(本人 #95EC69 右对齐、对方 #FFFFFF 左对齐、系统居中灰),相邻消息间隔 > 5 分钟插入时间分隔条,媒体缺失写 [媒体缺失] 占位。
    • CSV:首行写 UTF-8 BOM 让 Excel 正确识别编码。
    • JSON:结构化字段,含 media_pathcount 在尾部写实际条数(流式无法预知)。
    • TXT[时间] 昵称: 内容 纯文本,群消息用 sender 字段。
    • 导出目录WOC_EXPORT_DIR=/config/woc-export,每个任务建 <task_id>/ 子目录。
    • 懒清理GET /{task_id}/status 时触发,清理 completed / failed / cancelled 状态的过期任务。
    • 取消:取消运行中的任务(asyncio_task.cancel(),等待 5s+ 删除任务目录 + 内存移除。completed 状态调用也会提前回收文件。
    • 进程退出lifespan finallycancel_all_export_tasks_on_shutdown(),取消所有运行中任务。
    • 失败兜底failed 状态设置 expires_at = now + 300s5 分钟)确保被懒清理,避免内存泄漏。
  • 异常与边界
    • INVALID_PARAMS(400)talker 为空、format 非法、limit 越界、start_time > end_time、任务不存在。
    • RATE_LIMITED(429):已有异步导出任务运行中(同步端点也会被拒绝,避免 DB I/O 抢占),retry_after=30
    • STATE_DIRTY(503):下载时任务未完成。
    • BRIDGE_INTERNAL_ERROR(500):导出文件丢失。
    • DB_NOT_FOUND(500) / DB_ENCRYPTED(503)
  • 优先级P0。

FR-12 诊断与监控

  • 描述:连通性检查、诊断项定义、单项检查执行、自动修复。
  • 输入
    • GET /api/diagnostic/connectivity:无入参。
    • GET /api/diagnostic/items:无入参。
    • POST /api/diagnostic/run/{check_id}:路径参数。
    • POST /api/diagnostic/autofix/{check_id}:路径参数。
  • 输出
    • ConnectivityResponsereachable / latency_ms / error
    • {items: [DiagnosticItem{check_id, name, severity, description, auto_repairable}]}(共 4 项)。
    • DiagnosticRunResultcheck_id / passed / severity / message / auto_repairable / repair_plan?
  • 业务规则
    • 诊断项清单DIAGNOSTIC_ITEMS
      check_id name severity auto_repairable
      wechat_running 微信进程检查 critical true
      login_state 登录状态检查 critical false
      db_accessible 数据库可达性 error true
      xdotool_available xdotool 可用性 error true
    • connectivity MVP 阶段恒返回 reachable=true / latency_ms=0 / error=null
    • run 行为:不抛业务错误的诊断项,即使 passed=false 也返回 200 + 详细 messagerepair_plandb_accessible=encrypted/unreadable 时非空。
    • autofix 行为
      • wechat_runningpkill -x wechat → 等 2s 让 autostart 拉起 → 未拉起则 bridge 显式 start_wechat(timeout=10s) 兜底。
      • xdotool_available:不可修复,返回 passed=false + message="xdotool 不可用,请重建容器"
      • db_accessibleneed_init / key_invalid 时尝试自动提取 key失败返回 repair_plan
      • 调用方收到 passed=true 后应间隔 3-5s 调 run 确认实际状态。
    • Prometheus /metrics:暴露 12 项指标(见 5.5 节)。
  • 异常与边界INVALID_PARAMS(400):未知 check_id 或该项不可自动修复。
  • 优先级P1。

FR-13 截图

  • 描述:截取完整 X 桌面并返回 PNG用于调试观察微信实际界面状态。
  • 输入:无。
  • 输出ResponseContent-Type=image/pngbody 为 PNG 二进制。
  • 业务规则
    • qr_capture.capture_full_screenshot 完成,底层调 xdotool getwindowgeometry + importImageMagick
    • 截取整个 X 桌面(DISPLAY_state.config.display 指定)。
    • /api/login/qr/start 区别:本接口截全屏,qr/start 截二维码区域。
    • 大屏幕截图可能 > 1MB调用方注意带宽。
  • 异常与边界WINDOW_NOT_FOUND(503)X 会话未就绪。其他异常由全局兜底处理器返回 BRIDGE_INTERNAL_ERROR(500)
  • 优先级P1。

5. 非功能需求

5.1 性能

维度 阈值 / 参数 来源
发送限流 1 秒滑动窗口 max_calls_per_sec=10,超出抛 RATE_LIMITED(429) + retry_after SendQueue._check_rate_limit
发送队列 max_queue_size=100,满时入队抛 RATE_LIMITED(429) + retry_after=3 SendQueue.__init__
队列入队等待超时 send_queue_wait_timeout_ms=15000,超时抛 BridgeError(TIMEOUT) + retry_after FlowOrchestrator.send_text
发送间隔 send_delay_ms=3000(不同联系人)/ 1000(同联系人,会话缓存命中) config.py / orchestrator._SAME_CONTACT_DELAY_MS
SSE 轮询 有订阅者 1.0s,无订阅者 5.0sDB mtime 未变化时跳过 SQL streamer.POLL_INTERVAL_ACTIVE/IDLE
SSE 批量 BATCH_LIMIT=200 条 / 轮 streamer.BATCH_LIMIT
SSE 队列 QUEUE_MAXSIZE=100,满时丢弃最旧事件 streamer.QUEUE_MAXSIZE
SSE 心跳 HEARTBEAT_INTERVAL=30.0s streamer.HEARTBEAT_INTERVAL
SSE 订阅上限 MAX_SUBSCRIBERS=3,超限剔除最早订阅者并投递 kicked 事件 streamer.MAX_SUBSCRIBERS
慢请求阈值 2000ms,超过升级为 WARNING 日志并打 ⚠️ 标记 app._SLOW_REQUEST_MS
大文件超时自适应 30 + size_mb * 1.5,上限 180s小文件≤10MB固定 30s send_file._compute_timeout
DB 校验超时 3.0s(轮询间隔 0.2s routes/send._verify_sent_to_talker
文件发送 DB 校验 30s 总超时,轮询间隔 2s,等待 WAL 刷盘 2s send_file._DB_VERIFY_*
WeChat 重启超时 30s routes/login.wechat_restart
登录扫码等待 默认 30s,上限 120s routes/login.login_qr_wait
撤回时限 110s(微信 2 分钟限制,预留 10s UI 操作余量) routes/send._REVOKE_WINDOW_SEC
头像下载 urlopen 超时 15s,大小上限 10MB media._fetch_avatar_url
导出同步阈值 limit ≤ 1000 export._SYNC_EXPORT_LIMIT
导出异步并发 全局同时 1 个任务 export._MAX_CONCURRENT_EXPORT_TASKS
导出分页 每批 200 export._EXPORT_BATCH_SIZE
导出 TTL 7200s2 小时) config.export_task_ttl_sec
导出 limit 上限 100000 models/export.ExportRequest.limit
群发单批 ≤ 200(去重后) config.batch_max_per_batch
群发日频次 ≤ 3 config.batch_daily_limit
群发批间隔 ≥ 7200s2 小时) config.batch_min_interval_sec
群发单条抖动 random.uniform(2.0, 4.0) batch_worker._BATCH_JITTER_*
群发单条内容上限 2000 字符 models/batch_send.BatchSendRequest.content.max_length
abort_on_consecutive_fail 默认 5,范围 1-20 models/batch_send.BatchSendRequest
display_name 缓存 TTL 300smax 1024 条,启动预热 1000 个联系人 routes/send._DISPLAY_NAME_*

5.2 可用性

熔断器CircuitBreaker

状态机:CLOSED → 连续失败达 failure_threshold → OPEN → 经过 recovery_timeout → HALF_OPEN → 一次成功 CLOSED / 一次失败 OPEN

bridge 实例化的熔断器:

熔断器名 failure_threshold recovery_timeout 用途 来源
db_verify 10 30.0s DB 校验失败熔断高并发下更宽容OPEN 时 send_text / send_file 返回 BRIDGE_CIRCUITED(503) orchestrator.db_verify_breaker
accept_verify 5 60.0s 好友自动通过失败熔断(比 send_text 的 30s 更长) app._state.accept_breaker
默认值 5 30.0s 未显式指定参数时使用 CircuitBreaker.__init__

服务启动门控s6 登录检测)

bridge 进程由 s6 服务管理器(bridge/s6/woc-bridge/run)拉起,仅在检测到微信登录主窗口后才启动,避免登录前狂刷日志:

  • s6 启动脚本轮询(每 3spgrep -f /config/wechat/opt/wechat/wechat 获取微信进程 PID再用 xdotool search --pid 枚举该进程所有窗口,取面积最大者。
  • 登录检测阈值:主窗口面积 ≥ 300000 像素(登录后主界面面积通常 > 300000扫码 / 登录窗口面积较小),命中后 sleep 60 等待微信完全就绪再启动 bridge。
  • 最长等待 600sMAX_WAIT=600),超时则 bridge 不启动,进入 sleep infinity 休眠,待用户重启容器或手动 s6-svc -u 唤醒。
  • 启动前还会校验 ptrace 权限:尝试 echo 0 > /proc/sys/kernel/yama/ptrace_scope;失败则通过 CapEff bit 12 检测是否持有 SYS_PTRACE capability。

注意:此 300000 阈值用于服务启动门控(登录检测),与 5.5 节 UI 自动化中 _find_main_window_idmin_area=10000(过滤隐藏 / 加载小窗)是两个不同阈值,不可混淆。

启动清场

lifespan 启动时调 xdotool._full_cleanup_on_startup(),上次崩溃可能残留脏状态(搜索框打开 / 输入框有内容)。失败 3 次仅告警不阻塞启动,提示人工 VNC 接入。

Watchdog

  • 检查间隔 10.0s,连续失败 fail_threshold=2 触发 autofix。
  • autofix 策略:pgrep -x wechatkill -TERM 所有 PID不直接启动避免与 autostart 竞态),让 autostart watchdog 拉起。
  • 失败仅告警不抛异常,避免拖垮 bridge 主流程。

ResourceReaper

  • 调试截图目录 /tmp/woc_debug,检查间隔 3600s1 小时)。
  • 文件最大存活 24h(按 mtime 删除超 TTL 的文件)。
  • 总量上限 100MB,超限按 LRU最旧 mtime删除。
  • 仅当 WOC_UI_DEBUG_SHOTS=true 时调试截图才写盘。

重试策略

  • max_attempts=2(含首次,即最多重试 1 次)。
  • 指数退避:base_delay * (2 ** attempt)base_delay=1.0smax_delay=30.0s
  • 抖动:× [0.75, 1.25] 均匀分布,避免雷同请求同时重试。
  • 只对 RETRYABLE_CODES 中的错误码重试(如 SEND_TIMEOUT / WINDOW_NOT_FOUND),永久错误不重试。

会话缓存SessionCache

  • LRU 多会话TTL 30.0smax 16 个会话。
  • 命中时 SendTextFlow 跳过 activate / click_search_box / type_query / open_session 四步,直接进入 focus_input
  • 失败时仅失效当前联系人缓存,避免误伤其它缓存命中。

幂等缓存IdemCache

  • 键:SHA256(f"{flow_name}|{to_wxid}|{content}|{client_request_id}")64 字符 hex不截断
  • TTL 300s5 分钟max 1000 条。
  • LRU 淘汰 + TTL 过期双策略,保证内存占用有上限且旧数据自动失效。

5.3 安全性

SQLCipher 参数(微信 4.x Linux

参数 说明
PAGE_SIZE 4096 字节 SQLite 页大小
SALT_SIZE 16 字节 第 1 页前 16 字节为 salt
IV_SIZE 16 字节 AES-CBC IV
HMAC_SIZE 64 字节 HMAC-SHA512
RESERVE_SIZE 80 字节 IV(16) + HMAC(64)
ROUND_COUNT 256000 PBKDF2-HMAC-SHA512 迭代轮数
MAC_SALT_XOR 0x3A mac_salt = salt XOR 0x3APBKDF2-SHA512 2 轮派生 mac_key
加密算法 AES-256-CBC 页解密
HMAC 算法 HMAC-SHA512 页完整性校验
密钥形态 enc_key(已派生)或 key_material(原始,需 PBKDF2 派生) _resolve_page1_key_material 双重尝试
WAL 合并 加密 WAL 帧结构:frame_header(24) + page(4096),按 salt 匹配合并 decrypt_wal

解密时不做逐页 HMAC 验证(参考 wechat-cli-main微信运行时 DB 页面可能因 WAL 并发写入等原因 HMAC 不匹配,但 key 本身正确AES 解密仍可得到有效数据)。

Bearer Token 鉴权

  • 所有 /api/* 端点(除 /api/status/api/diagnostic/connectivity/metrics)必须通过 Bearer Token 鉴权。
  • Token 通过环境变量 WOC_BRIDGE_API_TOKEN 配置,由上游反向代理或 Panel 校验。
  • Token 校验通过后授予全部功能权限,无细粒度角色划分(已知约束,见 10.4)。

文件路径白名单

场景 白名单目录 校验方式 来源
send/filesend/image /config/Desktop/ / /config/woc-uploads/ / /tmp/woc-files/ 段级不含 .. + os.path.realpath 解析后以白名单前缀开头 send_file._FILE_PATH_WHITELIST + _validate_file_path
moments/publish_image /config/ / /tmp/ / /data/ os.path.realpath 后以白名单前缀开头 + 文件存在可读 routes/moments._SAFE_IMAGE_DIRS
头像 URL CDN 域名白名单(见 FR-10 urllib.parse.urlparse + hostname 检查 media._AVATAR_ALLOWED_HOSTS + _validate_avatar_url

容器能力

  • 必须以 --cap-add=SYS_PTRACE 启动容器,允许 bridge 扫描微信进程内存提取 DB 密钥。
  • 同时要求 /proc/sys/kernel/yama/ptrace_scope=0(否则非 root 进程无法 ptrace 其他进程)。

xdotool 输入约束

  • 微信 4.x Linux 自绘 UI 不响应 Ctrl+V,必须用 xdotool type 逐字符输入文本。
  • 方法名保留 _paste_via_xclip 以维持调用点稳定,但实际不使用 xclip。

5.4 兼容性

维度 要求
WeChat 客户端 仅兼容 WeChat 4.x Linux/config/xwechat_files 数据目录)
CPU 架构 amd64 + arm64
X 会话 VNC DISPLAY=:1(由 _state.config.display 指定)
UI 自动化依赖 xdotool(必装,缺失抛 xdotool_available 诊断失败)+ opencv-python(图像匹配)+ ImageMagickimport 截图)+ xdpyinfoX11 探测)
Python 3.10+(用 from __future__ import annotations + tuple[str, float] 等 PEP 604 语法)
FastAPI requirements.txt 约束(本 PRD 不涉及版本号)
浏览器 bridge 为 API 服务,不涉及前端兼容性
数据库 SQLite 3通过 cryptography 库解密 SQLCipher 4 加密 DB
反向代理 nginx 须关闭 SSE 缓冲(响应头 X-Accel-Buffering: no

5.5 可观测性

Prometheus 指标(共 12 项,/metrics 端点)

指标名 类型 标签 说明
woc_send_total Counter flow_name, result(success/failed/skipped) 发送总数
woc_send_duration_seconds Histogram flow_name 发送耗时分布
woc_send_failed_by_state_total Counter flow_name, state 按失败状态分类的失败计数
woc_image_match_confidence Histogram kind OpenCV 模板匹配置信度
woc_db_verify_duration_seconds Histogram - DB 校验耗时
woc_send_queue_pending Gauge - 发送队列待处理数
woc_wechat_running Gauge - WeChat 进程运行态1/0
woc_circuit_state Gauge name 熔断器状态0=closed/1=open/2=half_open
woc_session_cache_hit_total Counter result(hit/miss) 会话缓存命中
woc_adaptive_wait_timeout_total Counter kind 自适应等待超时计数
woc_send_duration_first_seconds Histogram - 首次发送耗时(无缓存)
woc_send_duration_cached_seconds Histogram - 缓存命中后的发送耗时

trace_id

  • 每请求生成 12 字符 hex trace_idrandom.getrandbits(48)),通过 ContextVar 贯穿整个请求生命周期。
  • 日志格式:%(asctime)s [%(levelname)s] [%(trace_id)s] %(name)s: %(message)s,由 TraceFilter 自动注入。

请求日志分级

  • INFO正常请求含耗时、关键参数摘要
  • WARNING慢请求> 2000ms,带 ⚠️ 标记)、限流命中、熔断 OPEN、业务异常BridgeError)。
  • ERROR未捕获异常、发送后 DB 校验失败(消息可能未进入目标会话)。
  • DEBUG限流通过、缓存命中、轮询细节。

5.6 国际化与无障碍

不涉及。bridge 为 API 服务,无终端用户界面;所有 message 字段为中文,供调用方参考。


6. 交互与设计要求

6.1 UI 自动化六层架构

bridge 通过程序化操作微信 X11 窗口实现 UI 自动化,非用户可视化界面。架构分六层:

名称 职责 关键参数
L1 Backend xdotool + opencv 底层驱动 子进程超时 3.0spgrep / xdpyinfo
L2 Locator YAML profile + 图像 / 几何兜底 + 熔断 模板置信度阈值、min_area=10000窗口面积过滤min 200×200
L3 Actions 图像优先几何兜底 + 截图缓存 debug 截图写盘(WOC_UI_DEBUG_SHOTS=true 时)
L4 Flow 9 状态机 + SessionCache30s/16 LRU+ 清场 SendTextFlow / SendFileFlow
L5 Orchestrator 幂等 + 熔断 + 会话缓存 + 队列编排 db_verify_breakersession_cacheidem_cache
L6 Capability 业务入口(如 send_text / send_file 暴露给 routes 层调用

横切关注点

组件 参数 来源
circuit_breaker failure_threshold=5/10recovery_timeout=30~60s CircuitBreaker + orchestrator + app
idem_cache TTL 300smax_size=1000 IdemCache
retry max_attempts=2base_delay=1.0smax_delay=30.0s、jitter [0.75, 1.25] RetryPolicy
metrics 12 项 Prometheus 指标 ui/metrics.py
trace trace_id 12-hex ui/trace.py
watchdog 间隔 10sfail_threshold=2 WeChatWatchdog
resource_reaper 间隔 3600smax_age=24hmax_total=100MB ResourceReaper

6.2 关键交互态映射

触发条件 调用方处理建议
loading队列等待 send_queue_pending > 0,入队后等待执行 退避重试,参考 retry_after
空态 查询返回空列表(如群无成员、会话无消息) 正常态,不重试
错误态(业务异常) BridgeError 抛出HTTP 状态码 400/401/404/409/429/500/503 code 字段判定,RATE_LIMITEDretry_after 退避,BRIDGE_CIRCUITED / WECHAT_NOT_RUNNING 等需等待恢复
熔断态 BRIDGE_CIRCUITED(503) DB 校验熔断器 OPEN等待 recovery_timeout 后 HALF_OPEN 试探
SSE kicked 订阅者超 MAX_SUBSCRIBERS=3 上限被剔除 关闭连接,按需重连
SSE status: db_accessible=false DB 加密 / 退出 / 不可读 降级到 /api/messages/since 轮询,等待 status: db_accessible=true 恢复

6.3 前端原型

不涉及。bridge 为 API 服务无前端原型图。Panel 侧前端设计由 Panel PRD 约束。


7. 数据与接口需求

7.1 接口清单表

路径 方法 入参 出参 错误码
/api/status GET StatusResponse BRIDGE_INTERNAL_ERROR(500)
/api/login/qr/start POST QrLoginStartResult WINDOW_NOT_FOUND(503)BRIDGE_INTERNAL_ERROR(500)
/api/login/qr/wait GET timeout=30 QrLoginWaitResult INVALID_PARAMS(400)
/api/login/logout POST LogoutResponse LOGOUT_FAILED(500)WINDOW_NOT_FOUND(503)
/api/wechat/restart POST RestartResponse RESTART_TIMEOUT(408)BRIDGE_INTERNAL_ERROR(500)
/api/db/decrypt POST DbDecryptRequest{key, salt?} DbDecryptResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/db/key/status GET DbKeyStatusResponse -
/api/db/init POST DbInitRequest{pid?, db_dir?, force} DbInitResponse BRIDGE_INTERNAL_ERROR(500)
/api/db/init/status GET DbInitStatusResponse -
/api/messages/since GET cursor, cursor_local_id, limit=50, is_sender? MessagesResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/messages/stream GET SSE StreamingResponse 不抛业务错误
/api/messages/by_session GET talker, cursor, cursor_local_id, limit=50, direction=before, is_sender? MessagesBySessionResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/messages/search GET keyword, talker?, start_time, end_time, limit=50, is_sender? MessageSearchResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/send/text POST SendTextRequest{to_wxid, content, display_name?, client_request_id} SendResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)RATE_LIMITED(429)SEND_FAILED(500)BRIDGE_CIRCUITED(503)SEND_TIMEOUT(503)
/api/send/image POST SendFileRequest{to_wxid, file_path, display_name?, client_request_id?} SendResponse 同上
/api/send/file POST SendFileRequest{to_wxid, file_path, display_name?, client_request_id?} SendResponse 同上
/api/messages/revoke POST RevokeMessageRequest{talker, create_time, display_name?} RevokeMessageResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)REVOKE_WINDOW_EXPIRED(409)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/messages/forward POST ForwardMessageRequest{talker, target_display_name, source_display_name?} ForwardMessageResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/send/batch POST BatchSendRequest{targets, content, client_request_id?, abort_on_consecutive_fail=5} {success, batch_id, status, status_query_url} INVALID_PARAMS(400/409)RATE_LIMITED(429)
/api/send/batch/{batch_id}/status GET batch_id BatchSendStatus NOT_FOUND(404)
/api/send/batch/{batch_id}/cancel POST batch_id {success, batch_id, status="cancelled"} NOT_FOUND(404)
/api/send/batch GET limit=20 {batches: [BatchSendStatus]} INVALID_PARAMS(400)
/api/contacts GET keyword, limit=50 ContactsResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/contacts/{wxid} GET wxid Contact CONTACT_NOT_FOUND(404)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/groups GET limit=50 GroupsResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/groups/{wxid}/members GET wxid GroupMembersResponse DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/contacts/{wxid}/remark POST SetRemarkRequest{remark, display_name?} SetRemarkResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/friends/add POST AddFriendRequest{keyword, message=""} AddFriendResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/friends/requests GET limit=50 FriendRequestsResponse INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/friends/accept POST AcceptFriendRequest{stranger_wxid, nickname?} AcceptFriendResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)RATE_LIMITED(429)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/friends/auto_accept/config GET AcceptRuleConfig BRIDGE_INTERNAL_ERROR(500)
/api/friends/auto_accept/config PUT AcceptRuleConfig AcceptRuleConfig BRIDGE_INTERNAL_ERROR(500)
/api/friends/auto_accept/status GET AutoAcceptStatus BRIDGE_INTERNAL_ERROR(500)
/api/moments/timeline GET cursor=0, limit=20 MomentsTimelineResponse INVALID_PARAMS(400)DB_ENCRYPTED(503)
/api/moments/publish POST MomentPublishRequest{content} MomentPublishResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/moments/publish_image POST MomentPublishImageRequest{image_path, content=""} MomentPublishImageResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/moments/share_article POST MomentShareArticleRequest{public_account, article_index=1, comment?} MomentShareArticleResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/moments/like POST MomentLikeRequest{moment_index=1} MomentLikeResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/moments/comment POST MomentCommentRequest{comment, moment_index=1} MomentCommentResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/moments/delete POST MomentDeleteRequest{moment_index=1} MomentDeleteResponse INVALID_PARAMS(400)WECHAT_NOT_LOGGED_IN(401)WINDOW_NOT_FOUND(503)SEND_FAILED(500)
/api/media/avatar/{wxid} GET wxid Response(二进制) MEDIA_NOT_FOUND(404)DB_ENCRYPTED(503)DB_NOT_FOUND(500)
/api/media/{msg_id} GET msg_id Response(二进制) MEDIA_NOT_FOUND(404)DB_ENCRYPTED(503)DB_NOT_FOUND(500)
/api/diagnostic/connectivity GET ConnectivityResponse -
/api/diagnostic/items GET {items: [DiagnosticItem]} -
/api/diagnostic/run/{check_id} POST check_id DiagnosticRunResult INVALID_PARAMS(400)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/diagnostic/autofix/{check_id} POST check_id DiagnosticRunResult INVALID_PARAMS(400)
/api/screenshot POST Response(image/png) WINDOW_NOT_FOUND(503)BRIDGE_INTERNAL_ERROR(500)
/api/messages/export GET talker, format=html, limit=1000, include_media, media_inline, start_time?, end_time? StreamingResponse INVALID_PARAMS(400)RATE_LIMITED(429)DB_NOT_FOUND(500)DB_ENCRYPTED(503)
/api/messages/export POST ExportRequest ExportTaskStatus INVALID_PARAMS(400)RATE_LIMITED(429)
/api/messages/export/{task_id}/status GET task_id ExportTaskStatus INVALID_PARAMS(400)(任务不存在)
/api/messages/export/{task_id}/download GET task_id FileResponse INVALID_PARAMS(400)STATE_DIRTY(503)BRIDGE_INTERNAL_ERROR(500)
/api/messages/export/{task_id} DELETE task_id {success, task_id, status="cancelled"} INVALID_PARAMS(400)
/metrics GET Prometheus exposition -

接口总数48 个端点(去重路径后)。

7.2 错误码表

数据源:d:\WechatOnCloud-main\bridge\woc_bridge\models\base.pyERROR_CODES 字典。源码共定义 27 个错误码(任务描述称 25 个,实际多出 DB_LOCKED / LOGOUT_FAILED / DB_INIT_IN_PROGRESS 三个)。

错误码 HTTP 状态 含义
INVALID_PARAMS 400 参数校验失败(空值 / 越界 / 格式错误 / 路径不在白名单)
AMBIGUOUS_CONTACT 400 联系人歧义(搜索结果不唯一)
WECHAT_NOT_LOGGED_IN 401 微信未登录,无法执行 UI 操作
CONTACT_NOT_FOUND 404 指定 wxid 不存在
MEDIA_NOT_FOUND 404 媒体文件 / 头像未找到
REVOKE_WINDOW_EXPIRED 409 消息超过 110 秒撤回时限
RATE_LIMITED 429 限流命中(发送 / 群发 / 导出)
LOGIN_TIMEOUT 408 登录扫码超时
RESTART_TIMEOUT 408 微信重启 30 秒内未拉起新进程
SEND_FAILED 500 UI 操作失败或发送后 DB 校验失败
BRIDGE_INTERNAL_ERROR 500 bridge 内部错误(组件未初始化等)
DB_VERIFY_FAILED 500 DB 密钥验证失败
DB_NOT_FOUND 500 未找到微信 DB 文件或文件不可读
LOGOUT_FAILED 500 退出登录 UI 操作失败
DB_LOCKED 503 DB 被锁定(其他进程持有写锁)
DB_ENCRYPTED 503 DB 已加密,需 SQLCipher 密钥
DB_NEED_INIT 503 DB 需初始化(密钥提取未完成)
DB_INIT_IN_PROGRESS 503 DB 初始化进行中
DB_KEY_INVALID 503 DB 密钥无效
WECHAT_NOT_RUNNING 503 微信进程未运行
WINDOW_NOT_FOUND 503 微信窗口未找到
SEND_TIMEOUT 503 发送超时
STATE_DIRTY 503 状态脏(如导出任务未完成就尝试下载)
ELEMENT_NOT_FOUND 503 UI 元素未找到
BRIDGE_CIRCUITED 503 熔断器 OPEN拒绝请求
X11_UNAVAILABLE 503 X11 不可用
X11_TEMP_UNAVAILABLE 503 X11 临时不可用

注:routes/batch.py 中的 NOT_FOUND 通过 BridgeError(code="NOT_FOUND", http_status=404) 显式指定 HTTP 状态码,但未在 ERROR_CODES 表中登记(查表时回落到 500因此必须显式传 http_status=404)。

7.3 配置项表

数据源:d:\WechatOnCloud-main\bridge\woc_bridge\config.pyBridgeConfig.from_args_and_env。共 24 项环境变量(含 3 个命令行参数)。

命令行参数3 项)

参数 默认值 说明
--listen 0.0.0.0:8088 监听地址
--wechat-db /config 微信数据目录
--display :1 X DISPLAY

环境变量24 项)

环境变量 默认值 说明
WOC_BRIDGE_SEND_DELAY_MS 3000 两次发送之间的最小间隔毫秒
WOC_BRIDGE_MAX_CALLS_PER_SEC 10 每秒最大调用次数
WOC_BRIDGE_MAX_QUEUE_SIZE 100 发送队列最大深度
WOC_BRIDGE_SEND_QUEUE_WAIT_TIMEOUT_MS 15000 入队等待超时毫秒
WOC_BRIDGE_MAX_BATCH_SIZE 50 客户端单次拉取建议上限(仅状态返回)
WOC_BRIDGE_POLL_INTERVAL_MS 2000 客户端轮询建议间隔(仅状态返回)
WOC_DB_KEY "" 64 位 hex SQLCipher 密钥(空表示不注入)
WOC_DB_KEY_AUTO_EXTRACT true 是否启用自动内存扫描提取密钥
WOC_UI_BACKEND flow UI 自动化后端:flow(新六层架构)/ legacy(旧 xdotool_driver
WOC_UI_DEBUG_SHOTS false 调试截图写盘开关,生产环境必须 false
WOC_AUTO_ACCEPT_ENABLED false 好友自动通过全局开关
WOC_AUTO_ACCEPT_ALL false 通过所有申请(忽略以下规则)
WOC_AUTO_ACCEPT_WHITELIST_WXIDS "" 白名单 wxid 列表(逗号分隔)
WOC_AUTO_ACCEPT_WHITELIST_NICKNAMES "" 白名单昵称列表(精确匹配,逗号分隔)
WOC_AUTO_ACCEPT_KEYWORDS "" 验证消息关键词列表(子串匹配,逗号分隔)
WOC_AUTO_ACCEPT_BLACKLIST_WXIDS "" 黑名单 wxid 列表
WOC_AUTO_ACCEPT_BLACKLIST_NICKNAMES "" 黑名单昵称列表
WOC_AUTO_ACCEPT_ALLOW_SCENES "" 允许的场景列表(空=不限制)
WOC_AUTO_ACCEPT_POLL_INTERVAL 3 自动通过轮询间隔秒数
WOC_EXPORT_DIR /config/woc-export 导出文件根目录
WOC_EXPORT_TASK_TTL_SEC 7200 导出任务 TTL 秒数2 小时)
WOC_BATCH_MAX_PER_BATCH 200 单批目标数上限(去重后)
WOC_BATCH_DAILY_LIMIT 3 每日群发批次数上限
WOC_BATCH_MIN_INTERVAL_SEC 7200 相邻两批群发最小间隔秒数2 小时)

注:列表类配置(如 WOC_AUTO_ACCEPT_WHITELIST_WXIDS)由 _parse_list 按逗号分隔解析。布尔值识别 true/1/yes/on(其余视为 false)。

7.4 关键数据模型

Message

字段 类型 说明
msg_id str 消息 ID
talker str 会话对方 wxid群消息为 chatroom id
sender str 实际发送者 wxid群消息中为成员 wxid
is_sender bool 是否为本机发送
type int 微信原始消息类型
render_type str 渲染类型:text / image / voice / video / file / system
content str 消息内容文本
create_time int 消息时间戳Unix 秒)
session_type str 会话类型:p2p / group

Contact

字段 类型 说明
wxid str 联系人 wxid
nickname str 昵称
remark str 备注
avatar_url str 头像 URL
type str person / group / official / system
alias Optional[str] 微信号 / 别名
encrypt_username Optional[str] 加密用户名
quan_pin Optional[str] 全拼
pin_yin_initial Optional[str] 拼音首字母
big_head_url Optional[str] 高清头像 URL
small_head_url Optional[str] 缩略头像 URL
description Optional[str] 个性签名 / 描述
local_type Optional[int] 联系人类型标记
verify_flag Optional[int] 认证标记(0x08 = 已认证公众号)
delete_flag Optional[int] 删除标记
chat_room_type Optional[int] 群类型标记

SendTextRequest

字段 类型 说明
to_wxid str 目标 wxid
content str 文本内容
display_name Optional[str] 用于微信搜索框定位会话的显示名,不传时自动按 备注 → 昵称 → wxid 查找
client_request_id str 客户端幂等去重 ID空字符串表示不参与幂等校验

BatchSendRequest

字段 类型 约束 说明
targets list[str] min_length=1, max_length=200 目标 wxid 列表(去重后 ≤ 200
content str min_length=1, max_length=2000 消息内容(支持 {nickname} 占位符)
client_request_id Optional[str] - 批级幂等 IDNone 时自动生成 batch_<uuid.hex[:16]>
abort_on_consecutive_fail int ge=1, le=20, 默认 5 连续失败 N 次中止整批

AcceptRuleConfig

字段 类型 默认 说明
enabled bool false 全局开关
accept_all bool false 通过所有申请(忽略以下规则)
whitelist_wxids list[str] [] 白名单 wxid 列表
whitelist_nicknames list[str] [] 白名单昵称列表(精确匹配)
keywords list[str] [] 验证消息关键词列表(子串匹配)
blacklist_wxids list[str] [] 黑名单 wxid 列表
blacklist_nicknames list[str] [] 黑名单昵称列表
allow_scenes list[str] [] 允许的场景(空=不限制)

ExportRequest

字段 类型 约束 说明
talker str required 会话 wxid 或群 id
format Literal["html","csv","json","txt"] 默认 html 导出格式
limit int ge=1, le=100000, 默认 1000 导出消息数上限
include_media bool 默认 false 是否打包媒体文件zip
media_inline bool 默认 false 媒体是否内联(仅 HTMLbase64 嵌入)
start_time Optional[int] - 起始时间戳Unix 秒,含)
end_time Optional[int] - 结束时间戳Unix 秒,含)

8. 验收标准

AC-01扫码登录

Given 微信进程已运行但未登录(login_state=not_logged_in When 调用 POST /api/login/qr/start Then 返回 200 + qr_data_urldata:image/png;base64, 开头,connected=falsemessage="请使用微信扫描二维码"

AC-02登录等待成功

Given 用户已扫描二维码并确认登录 When 调用 GET /api/login/qr/wait?timeout=30 Then 在 30 秒内返回 connected=truecredentials.wxidcredentials.nickname 非空,qr_data_url 为空字符串。

AC-03消息发送幂等命中

Given 同一 client_request_idPOST /api/send/text 请求已成功执行(verified=true When 在 300 秒内再次调用 POST /api/send/text 使用相同 to_wxid / content / client_request_id Then 返回 success=trueskipped=trueverified=true,且不触发实际 UI 操作SendQueue 不入队)。

AC-04撤回时限触发

Given 一条消息的 create_time 距当前时间超过 110 秒 When 调用 POST /api/messages/revoke 传入该 talkercreate_time Then 返回 HTTP 409错误码 REVOKE_WINDOW_EXPIREDmessage 含"已为 UI 操作预留 10s 余量,阈值收紧到 110s"。

AC-05群发风控触发单批上限

Given WOC_BATCH_MAX_PER_BATCH=200 When 提交 POST /api/send/batchtargets 去重后为 201 个 wxid Then 返回 HTTP 400错误码 INVALID_PARAMSmessage 含"超过单批上限 200"。

AC-06群发风控触发日频次

Given 当日已成功提交 3 次群发(WOC_BATCH_DAILY_LIMIT=3 When 再次提交 POST /api/send/batch Then 返回 HTTP 429错误码 RATE_LIMITEDdetails.retry_after > 0 且指向次日 0 点。

AC-07群发风控触发批间隔

Given 上一次群发结束时间距今 < 7200 秒(WOC_BATCH_MIN_INTERVAL_SEC=7200 When 提交 POST /api/send/batch Then 返回 HTTP 429错误码 RATE_LIMITEDmessage 含"群发间隔不足"details.retry_after > 0。

AC-08好友自动通过规则决策黑名单优先

Given AcceptRuleConfig.enabled=trueblacklist_wxids=["wxid_black"]whitelist_wxids=["wxid_black"](同一 wxid 同时在黑/白名单) When 收到 stranger_wxid="wxid_black" 的好友申请 Then 规则引擎返回 REJECT(黑名单优先于白名单)。

AC-09好友自动通过规则决策场景过滤

Given AcceptRuleConfig.enabled=trueaccept_all=falseallow_scenes=["1","14"],未配置白名单与关键词 When 收到 scene="3" 的好友申请 Then 规则引擎返回 SKIP(不在允许场景列表)。

AC-10DB 解密成功

Given 微信 DB 已加密,存在匹配的 64 位 hex 密钥 When 调用 POST /api/db/decrypt 传入该 key Then 返回 success=trueverified=truekey_mode ∈ {enc_key, key_material}、error=null,且后续 GET /api/db/key/status 返回 cached=truesource="api"verified=true

AC-11DB 解密失败key 不匹配)

Given 微信 DB 已加密 When 调用 POST /api/db/decrypt 传入不匹配的 key Then 返回 success=trueverified=falsekey_mode=nullerror="key_mismatch",且 GET /api/db/key/status 返回 cached=false

AC-12SSE 推送新消息

Given 客户端已建立 GET /api/messages/stream 连接并收到 sync 事件 When 微信收到新消息DB mtime 变化 Then 客户端在 POLL_INTERVAL_ACTIVE=1.0s 内收到 messages 事件,data.messages 非空,data.next_cursor 大于上次游标。

AC-13SSE 订阅者超限剔除

Given 当前已有 3 个 SSE 订阅者(MAX_SUBSCRIBERS=3 When 第 4 个客户端订阅 GET /api/messages/stream Then 最早订阅者收到 kicked 事件(data.reason="max_subscribers_reached")并断开连接,新订阅者正常收到 sync 事件。

AC-14导出 TTL 过期清理

Given 一个导出任务 status="completed"expires_at 已小于当前时间 When 调用 GET /api/messages/export/{task_id}/status Then 触发懒清理,任务从内存移除、任务目录被删除,再次调用返回 INVALID_PARAMS(400) + message="任务不存在或已过期"

AC-15熔断器触发

Given db_verify_breaker 当前为 CLOSED,连续 10 次 send_text 校验失败(failure_threshold=10 When 第 11 次调用 POST /api/send/text Then 返回 HTTP 503错误码 BRIDGE_CIRCUITEDerror="db_verify circuit breaker open",且不实际执行发送。

AC-16限流触发

Given WOC_BRIDGE_MAX_CALLS_PER_SEC=101 秒内已发起 10 次 POST /api/send/text 且均进入 SendQueue 执行 When 第 11 次请求在同一秒窗口内入队执行 ThenBridgeError(RATE_LIMITED)HTTP 429details.retry_after ≥ 1 秒。

AC-17发送队列满

Given WOC_BRIDGE_MAX_QUEUE_SIZE=100,队列已积压 100 个任务 When 第 101 个请求入队 Then 返回 HTTP 429错误码 RATE_LIMITEDmessage 含"发送队列已满100/100"details.retry_after=3

AC-18复合游标防重复

Given 同一秒内收到 3 条消息(create_time 相同,local_id 分别为 1/2/3客户端已拉取前 2 条并保存 next_cursor=1000, next_cursor_local_id=2 When 客户端用该游标调用 GET /api/messages/since?cursor=1000&cursor_local_id=2 Then 仅返回 local_id > 2 的第 3 条消息,不重复返回前 2 条。

AC-19导出并发限制

Given 已有 1 个异步导出任务 status="running" When 再次 POST /api/messages/exportGET /api/messages/export(同步) Then 返回 HTTP 429错误码 RATE_LIMITEDdetails.retry_after=30message 含"已有导出任务运行中"。

AC-20群发连续失败中止

Given BatchSendRequest.abort_on_consecutive_fail=3,群发任务执行中连续 3 个 target 发送失败 When 第 3 次失败发生 Then 整批中止,状态置 failedabort_reason 含"连续失败 3 次,达到阈值 3整批中止",剩余 pending 项标记 skipped


9. 排期与里程碑

本 PRD 为回溯性文档bridge 模块已实现并上线。本节列出关键节点占位,供后续迭代参考。

里程碑 状态 说明
需求基线建立 已完成 本 PRDv1.0)建立完整需求基线
源码对齐核对 已完成 所有 API 路径 / 错误码 / 配置项 / 阈值均与源码核对一致
评审与确认 待启动 需产品 / 研发 / 测试 / 运维联合评审
后续迭代排期 待定 新需求按本基线增量管理,需更新变更记录

10. 风险与依赖

10.1 技术依赖

依赖 说明 影响
SYS_PTRACE capability 容器必须以 --cap-add=SYS_PTRACE 启动 缺失则自动内存扫描提取密钥失败,必须降级到手动注入 key
ptrace_scope=0 /proc/sys/kernel/yama/ptrace_scope 必须为 0 非 root 进程无法 ptrace 其他进程,密钥提取失败
WeChat 4.x Linux 仅兼容该版本 微信版本升级可能破坏 UI 自动化坐标与 DB schema
xdotool 必装UI 自动化底层依赖 缺失则所有 send/* / moments/* / friends/* 等接口不可用
opencv-python 图像匹配定位依赖 缺失则 L2 Locator 图像兜底失效,仅几何兜底可用
ImageMagickimport 截图依赖 缺失则 /api/screenshot/api/login/qr/start 不可用
xdpyinfo X11 可用性探测依赖 缺失则 watchdog X11 检测失败
Xvnc / openbox X 会话与窗口管理器 缺失则微信窗口无法显示,所有 UI 操作失败
cryptography SQLCipher 解密依赖 缺失则 DB 解密失败,所有 DB 查询接口不可用
prometheus_client /metrics 端点依赖 缺失则 /metrics 禁用,仅记 warning

10.2 外部依赖

依赖 说明
腾讯微信客户端 WeChat 4.x Linux 版本。客户端版本升级可能破坏 UI 自动化坐标 / 模板、DB schema、SQLCipher 参数、消息存储格式等。bridge 与微信版本强耦合,无版本协商机制。
头像 CDNwx.qlogo.cn 等) 联系人头像下载依赖外网可达CDN 故障时 /api/media/avatar/{wxid} 返回 MEDIA_NOT_FOUND(404)

10.3 风险与应对

风险 影响 应对策略
UI 自动化脆弱(坐标 / 模板依赖) 微信版本升级或分辨率变化导致 UI 操作失败 1. 模板截图分目录管理(profiles/wechat_4.0_<resolution>/light/
2. 几何兜底 + 图像兜底双策略
3. experimental 接口明确标注需实测调优
群发封号 高频群发触发微信风控,账号受限或封禁 1. 风控参数:单批 ≤ 200 / 日频次 ≤ 3 / 批间隔 ≥ 7200s
2. 单条间隔抖动 2-4s
3. {nickname} 占位符个性化内容
4. abort_on_consecutive_fail 阈值中止
DB 密钥提取失败 自动提取失败,所有 DB 查询接口不可用 1. 三层降级env WOC_DB_KEY → API POST /api/db/decrypt 手动注入 → 自动内存扫描
2. db_accessible 诊断项 + autofix 引导
3. repair_plan 字段提示具体操作
并发串号 高并发场景下消息发到错误会话 1. 复合游标 (create_time, local_id) 防基线误匹配
2. 发送后 DB 校验内容匹配(防 talker 字段误判)
3. SessionCache 失效时仅清当前联系人,不误伤其他缓存
WAL 刷盘延迟 发送后立即查 DB 看不到新消息,校验失败 1. 等 WAL 刷盘 1.5-2s 后再轮询
2. DB 校验超时 3s间隔 0.2s),允许 15 次轮询
3. 校验失败不阻塞返回,仅记 warning + verified=false
多实例隔离不足 多账号场景下实例间状态串扰 1. 一个 bridge 进程对应一个微信账号
2. 多账号需多容器实例隔离
3. 数据卷与配置独立
Token 无细粒度权限 Token 泄露后授予全部功能 1. Token 通过环境变量配置,避免硬编码
2. 由上游反代实现细粒度路由鉴权
3. 监控异常调用模式(/metrics 暴露发送计数)
SSE 订阅者累积 僵尸连接累积导致内存泄漏 1. MAX_SUBSCRIBERS=3 上限,超限剔除最早订阅者
2. 队列满时丢弃最旧事件
3. 客户端断开自动取消订阅
慢请求拖垮 event loop UI 操作耗时过长阻塞其他请求 1. DB 同步操作用 asyncio.to_thread 包装
2. 慢请求阈值 2000ms 升级 WARNING 日志
3. 大文件超时自适应30 + size_mb × 1.5,上限 180s
调试截图磁盘占满 WOC_UI_DEBUG_SHOTS=true 时截图累积 1. 生产环境必须 false
2. ResourceReaper 每小时清理max_age 24hmax_total 100MB
3. LRU 删除最旧文件

10.4 业务视角风险与治理建议

本节为 v1.2 业务视角优化新增,站在实际业务运营角度识别技术风险表之外的深层业务风险,并给出治理方向。

业务风险 1合规与账号封禁业务连续性风险

  • 风险描述bridge 通过 UI 自动化操作微信客户端,本质上属于「模拟人工操作」,可能违反《腾讯微信软件许可及服务协议》中关于自动化工具的条款。一旦被腾讯风控识别,轻则限制功能、重则永久封号,直接危及依赖该账号的全部业务(如客户触达、社群运营)。
  • 业务影响:账号封禁 = 业务中断,且历史聊天记录/联系人资产可能无法迁移,损失不可逆。
  • 治理建议
    1. 业务侧建立「账号分级」策略,核心账号不接入自动化,仅用低价值账号承载自动化群发;
    2. 群发频次应低于风控参数下限(当前 200/3/7200s 是技术上限,业务建议按 50/1/14400s 运营);
    3. 文案去重与个性化({nickname} 占位符)避免批量同质化内容触发风控;
    4. 建立「账号轮换池」,单账号日触达量设上限,避免单账号过载;
    5. 法律侧评估数据导出(聊天记录解密)的合规性,明确数据归属与使用边界。

业务风险 2消息送达可靠性缺口SLA 风险)

  • 风险描述UI 自动化本质是「模拟点击 + DB 校验」存在两类缺口①UI 操作成功但 DB 校验超时(verified=false业务方无法确认是否真送达②DB 校验通过但实际微信窗口已失焦/串号。当前 SendResponse.verified 三态placeholder/skipped/verifiedverified=false 时业务方若直接重试,可能造成重复发送
  • 业务影响:重复发送 = 客户体验灾难(营销骚扰),漏发 = 业务漏单,二者都损害业务信誉。
  • 治理建议
    1. 业务方必须基于 client_request_id 幂等键做去重,对 verified=false 的响应采用「延迟二次确认」而非立即重试(建议 30s 后查 /api/messages/since 确认);
    2. 关键业务消息(如订单通知)不应走 UI 自动化通道,应优先评估是否有官方 API 通道;
    3. 建立「业务送达率」监控指标(成功率 = verified / total与技术指标 send_total 区分;
    4. 群发场景 abort_on_consecutive_fail 应根据业务容忍度调优,连续失败可能预示 UI 失效,继续发送只会放大损失。

业务风险 3可扩展性瓶颈容量风险

  • 风险描述bridge 架构为「单进程单账号」UI 自动化经 _ui_lock + SendQueue 强串行化,单实例理论吞吐上限 = max_calls_per_sec=10 × 可用时间,但实际受 UI 操作耗时30s Flow 超时)制约,单账号并发能力极低。多账号需多容器,资源与运维成本线性增长。
  • 业务影响:业务规模扩张时,账号数量与服务器成本同步上升,无法通过「加机器」线性扩容单个账号的吞吐。
  • 治理建议:
    1. 业务侧明确「单账号合理负载」基线(建议 ≤ 100 条/小时),超载需求拆分到多账号;
    2. 群发非实时场景应充分利用「批间隔 ≥ 7200s」错峰避免高峰集中
    3. 评估「只读场景」(消息读取/导出/联系人查询)与「写场景」(发送/朋友圈)分离部署的可行性——只读走 DB 解密无 UI 依赖,可水平扩展;
    4. 长期看UI 自动化是脆弱替代方案,应推动官方 API 接入或自建 IM 中台降低对微信客户端的耦合。

业务风险 4数据一致性双源风险数据可信度风险

  • 风险描述bridge 同时存在「DB 解密读取」与「UI 自动化操作」两条数据链路。发送校验依赖 DB 反查,但 DB 写入由微信客户端异步完成WAL 刷盘延迟可能出现「UI 已发送但 DB 未落盘」或「DB 已落盘但 UI 实际未发」的瞬态不一致。消息读取SSE走 DB发送校验也走 DB二者基线对齐依赖复合游标一旦游标错位会放大不一致。
  • 业务影响:业务方基于 DB 数据做决策(如「已发送则扣库存」),若 DB 与实际不符会导致误判。
  • 治理建议
    1. 业务侧不应将 verified=true 作为唯一可信源,关键业务动作(扣款/扣库存)应二次确认;
    2. 数据导出(/api/messages/export)结果应标注「基于本地 DB 解密,可能与腾讯云端存在差异」;
    3. WAL 刷盘窗口1.5-2s内的消息应标记为「待确认」避免业务方读到半成品状态。

业务风险 5运维脆弱性与人工介入成本TCO 风险)

  • 风险描述UI 自动化对分辨率、模板图、坐标比例高度敏感微信客户端小版本升级即可能破坏模板匹配需人工重新截图校准。启动门控300000 阈值、ptrace 权限、X11 可用性等任一环节故障都需要人工 VNC 接入排查。当前 repair_plan 仅给文字提示,无自动恢复闭环。
  • 业务影响:系统「能跑」但「不敢动」,每次微信升级或分辨率变化都是一次运维事件,长期 TCO总拥有成本高于纯 API 方案。
  • 治理建议
    1. 运维侧建立「微信版本锁定」策略,升级前在测试环境验证 UI 自动化兼容性;
    2. 模板图与坐标比例纳入版本管理,变更需回归测试(已有 tests/ 目录,应扩展 UI 流程测试);
    3. 完善 diagnostic/autofix 自动恢复闭环,减少人工 VNC 介入频次;
    4. 建立巡检机制:定期跑 diagnostic/connectivity + 试发一条测试消息,提前发现 UI 失效。

业务场景完整性补充

经业务视角审视,以下边界场景应在评审时重点关注(已在功能需求或验收标准中体现,此处汇总提示):

  • 登录态过期/被踢LoginGuard 检测到未登录时后台任务暂停,但前端 API 调用会收到 WECHAT_NOT_LOGGED_IN(401),业务方需有重新扫码的运维 SOP
  • 群发中途取消cancel 后当前条目标 unknown(可能已发送),剩余标 skipped,业务方需处理「部分成功」的幂等续传;
  • 导出任务 TTL 过期7200s 后下载链接失效,业务方需在 TTL 内下载或重新发起;
  • 熔断期间请求BRIDGE_CIRCUITED(503) 时业务方应熔断自身并告警,而非重试雪崩;
  • 限流期间请求RATE_LIMITED(429)Retry-After 头,业务方应遵循退避而非丢弃。

优先级合理性审视

  • P0阻塞主流程状态查询、登录、DB 解密、消息读取、消息发送——合理,这些是 bridge 存在的基石;
  • P1影响主流程群发、好友管理、联系人、导出、诊断——合理属于高价值业务能力
  • P2体验优化朋友圈、媒体下载、截图——合理朋友圈为高敏感操作封号风险降级 P2 有助于引导业务方审慎使用;
  • 建议:群发虽为 P1但因封号风险高建议在文档与运营规范中明确「高频群发为高风险操作」引导业务方优先用低频个性化触达。

11. 变更记录

版本 日期 修改人 摘要
v1.0 2026-07-17 TRAE Agent 初稿,基于 d:\WechatOnCloud-main\bridge\woc_bridge\ 源码回溯建立需求基线。涵盖 13 类功能需求、48 个 API 端点、27 个错误码、23 项环境变量配置、20 条验收标准。所有 API 路径 / 错误码 / 配置默认值 / 阈值均以源码为准。
v1.1 2026-07-17 TRAE Agent 真实性复盘校验,修正接口/阈值/约束等不一致项。修正 7.3 节环境变量计数 23→24实际 config.py 解析 24 项);新增「附录:真实性复盘结论」含七项核对范围、修正明细与存疑项独立核实结论。
v1.2 2026-07-17 TRAE Agent 业务视角优化 + 关键约束更正。①更正主窗口面积阈值为双阈值:300000s6 登录检测/服务启动门控,bridge/s6/woc-bridge/run+ 10000UI 窗口过滤5.2 节新增「服务启动门控s6 登录检测)」小节。②业务视角优化:补充群发封号/UI 自动化脆弱/DB 密钥提取/并发串号等业务风险与应对策略至第 10 章风险与依赖。③扩展真相源至全 bridge/ 目录(含 s6 脚本)。

附:存疑项与源码对齐说明

编写过程中发现的与任务描述不符或需进一步确认的事项,均在此处说明,便于评审时核对。

  1. 错误码数量:任务描述称 25 个,源码 models/base.pyERROR_CODES 字典实际定义 27 个(多出 DB_LOCKEDLOGOUT_FAILEDDB_INIT_IN_PROGRESS)。本 PRD 按 27 个登记。
  2. NOT_FOUND 错误码:任务描述列出 NOT_FOUND(404),但该错误码未在 ERROR_CODES 表中登记routes/batch.py 通过 BridgeError(code="NOT_FOUND", http_status=404) 显式指定 HTTP 状态码绕过查表。本 PRD 在 7.2 节末尾以注解说明。
  3. 导出端点路径:任务描述称 /api/export/*,源码实际路径为 /api/messages/export/*router = APIRouter(prefix="/api/messages/export"))。本 PRD 按源码路径登记。
  4. 主窗口面积阈值(双阈值)bridge 存在两个不同语义的窗口面积阈值,不可混淆:
    • 300000 像素s6 启动脚本 bridge/s6/woc-bridge/run登录检测 / 服务启动门控阈值。脚本轮询微信进程窗口,当最大窗口面积 ≥ 300000 时判定已登录,sleep 60 后启动 bridge600s 未达阈值则不启动。源码依据:bridge/s6/woc-bridge/run line 25-26if [ "$area" -ge 300000 ]; then)。
    • 10000 像素UI 自动化中 _find_main_window_idmin_area 参数(默认 10000且 min 200×200用于过滤隐藏 / 加载小窗,确保点击操作落在真实主窗口上。源码依据:ui/backends/xdotool.py line 251、ui/drivers/window.py line 137。
    • 本 PRD 在 5.2 节「服务启动门控」登记 300000,在 5.5 节 / 7.3 节 UI 自动化约束登记 10000
  5. 熔断器 recovery_timeout 范围:任务描述称 30~300s源码实际范围为 30~60sdb_verify_breaker.recovery_timeout=30.0accept_breaker.recovery_timeout=60.0,默认值 30.0)。本 PRD 按 30~60s 登记。
  6. bridge 版本号:源码 version.pyBRIDGE_VERSION="1.0.1",本 PRD 文档版本为 v1.0(按规范命名),二者不冲突——前者是代码版本,后者是文档版本。
  7. BRIDGE_CAPABILITIES 能力清单:源码共登记 18 项能力(含 text_senddb_decryptsse_pushmoment_publishmoment_share_articlemessage_searchmessage_by_sessionmoment_timelinemessage_revokemessage_forwardcontact_remarkfriend_addmoment_likemoment_commentmoment_deletemoment_publish_imageimage_sendfile_sendbatch_sendmessages_export),其中 batch_sendmessages_export 为占位声明。
  8. /api/messages/export 同步端点无 response_model:源码未显式声明 response_model,直接返回 StreamingResponse,本 PRD 按实际行为登记。
  9. XOR_KEY 推导:微信 4.x .dat 媒体文件采用单字节 XOR 加密首字节为密文DbReader 通过对比已知图片格式 magic bytes 推导 XOR key。具体推导算法在 db/reader.py 中实现,本 PRD 仅说明机制,未列详细推导逻辑。
  10. /metrics 端点未在 7.1 接口表中单独列出鉴权要求:根据 4.0 节权限矩阵说明,/metrics 允许无鉴权访问(监控用),与 /api/status 同。

附录:真实性复盘结论

本附录为 v1.1 变更引入的独立复盘章节,基于 bridge/woc_bridge/ 全量源码再次核对 PRD 全部数据,确保零臆造。

A.1 复盘元信息

字段 内容
复盘日期 2026-07-17
复盘执行方 TRAE Agent
真相源 d:\WechatOnCloud-main\bridge\ 全量源码(含 woc_bridge/ Python 代码与 s6/woc-bridge/run 启动脚本)
核对项总数 7 类共 42 项明细核对
通过项数 40
修正项数 2 项(环境变量计数 23→24主窗口面积阈值双阈值更正
存疑/推断项 4 条(均来自源码与任务描述的差异,已逐条核实并标注结论)

A.2 核对范围与结果

核对范围 源码依据 核对明细数 通过 修正
1. 接口清单routes/ 12 文件) routes/{status,login,db,messages,send,batch,contacts,diagnostic,export,media,moments,screenshot}.py 12 文件 / 48 端点 12 0
2. 错误码models/base.py models/base.pyERROR_CODES 字典 27 错误码 1 0
3. 配置项config.py config.pyBridgeConfig.from_args_and_env 24 环境变量 + 3 命令行参数 0 1
4. 非功能阈值 messaging/send_queue.pymessaging/streamer.pyui/circuit_breaker.pyui/idem_cache.pyui/retry.pyapp.py 12 阈值 12 0
5. 关键约束参数 db/decryptor.pyroutes/send.pyui/flows/send_file.pyroutes/export.pyui/backends/xdotool.pyui/drivers/window.pybridge/s6/woc-bridge/run 8 参数组 7 1
6. 好友自动通过规则引擎决策顺序 models/contact.pyAcceptRuleEngine.evaluate 1 决策链 1 0
7. 文档规范合规性 doc/产品需求文档规范.md 7 合规项 7 0

A.3 修正明细列表

# 核对项 修正前 修正后 源码依据文件
1 7.3 节环境变量计数 "23 项环境变量"(出现于第 1.2 节目标、7.3 节说明文字、7.3 节小标题共 3 处) "24 项环境变量" bridge/woc_bridge/config.pyBridgeConfig.from_args_and_envos.environ.get 调用计数为 24 项(含 WOC_BATCH_MIN_INTERVAL_SEC,此前文档说明文字漏计)
2 主窗口面积阈值 v1.1 误判"源码中未找到 300000",仅登记 min_area=10000 更正为双阈值300000s6 登录检测 / 服务启动门控,bridge/s6/woc-bridge/run line 25-26+ 10000UI 窗口过滤,ui/backends/xdotool.py line 251 bridge/s6/woc-bridge/runui/backends/xdotool.pyui/drivers/window.py

v1.0 变更记录行中"23 项环境变量配置"作为历史记录保留原样,不回溯修改;本次修正仅作用于 1.2 节目标、7.3 节说明文字与 7.3 节小标题三处现行描述。 注v1.1 复盘遗漏了 bridge/s6/ 目录下的 shell 脚本,仅搜索了 woc_bridge/ Python 代码,导致误判 300000 阈值不存在。v1.2 已将真相源扩展至全 bridge/ 目录并更正。

A.4 通过项关键确认(抽样)

核对项 PRD 登记值 源码实际值 源码依据
错误码总数 27 27INVALID_PARAMS/AMBIGUOUS_CONTACT=400, WECHAT_NOT_LOGGED_IN=401, CONTACT_NOT_FOUND/MEDIA_NOT_FOUND=404, REVOKE_WINDOW_EXPIRED=409, LOGIN_TIMEOUT/RESTART_TIMEOUT=408, RATE_LIMITED=429, SEND_FAILED/BRIDGE_INTERNAL_ERROR/DB_VERIFY_FAILED/DB_NOT_FOUND/LOGOUT_FAILED=500, DB_LOCKED/DB_ENCRYPTED/DB_NEED_INIT/DB_INIT_IN_PROGRESS/DB_KEY_INVALID/WECHAT_NOT_RUNNING/WINDOW_NOT_FOUND/SEND_TIMEOUT/STATE_DIRTY/ELEMENT_NOT_FOUND/BRIDGE_CIRCUITED/X11_UNAVAILABLE/X11_TEMP_UNAVAILABLE=503 models/base.pyERROR_CODES
接口端点总数 48 48覆盖 routes/ 12 文件) routes/*.py
限流阈值 10 次/秒 max_calls_per_sec=10 config.py / messaging/send_queue.py
队列上限 100 QUEUE_MAXSIZE=100 messaging/streamer.pyconfig.pymax_queue_size=100
入队等待超时 15000ms send_queue_wait_timeout_ms=15000 config.py
队列满 retry_after 3 retry_after=3 messaging/send_queue.py
SSE 心跳 30s HEARTBEAT_INTERVAL=30.0 messaging/streamer.py
SSE 订阅上限 3 MAX_SUBSCRIBERS=3 messaging/streamer.py
轮询间隔 ACTIVE=1.0s / IDLE=5.0s POLL_INTERVAL_ACTIVE=1.0 / POLL_INTERVAL_IDLE=5.0 messaging/streamer.py
SSE 批量拉取 200 BATCH_LIMIT=200 messaging/streamer.py
幂等缓存 TTL=300s / max=1000 IdemCache(ttl=300.0, max_size=1000) ui/idem_cache.py
重试策略 max_attempts=2 / base=1.0 / max=30.0 / jitter[0.75, 1.25] RetryPolicy(max_attempts=2, base_delay=1.0, max_delay=30.0) + random.uniform(0.75, 1.25) ui/retry.py
群发风控 单批≤200 / 日≤3 / 批间隔≥7200s batch_max_per_batch=200 / batch_daily_limit=3 / batch_min_interval_sec=7200 config.py
熔断器 db_verify failure_threshold=10 / recovery_timeout=30.0s CircuitBreaker("db_verify", failure_threshold=10, recovery_timeout=30.0) ui/orchestrator.py line 118-120
熔断器 accept failure_threshold=5 / recovery_timeout=60.0s CircuitBreaker("accept_verify", failure_threshold=5, recovery_timeout=60.0) app.py line 528-579
熔断器默认 failure_threshold=5 / recovery_timeout=30.0s CircuitBreaker 默认参数 ui/circuit_breaker.py
SessionCache TTL=30s / max=16 SessionCache(ttl=30.0, max_size=16) ui/orchestrator.py line 115-116
SQLCipher PAGE_SIZE 4096 PAGE_SIZE=4096 db/decryptor.py
SQLCipher KEY_SIZE 32 KEY_SIZE=32 db/decryptor.py
SQLCipher SALT_SIZE 16 SALT_SIZE=16 db/decryptor.py
SQLCipher IV_SIZE 16 IV_SIZE=16 db/decryptor.py
SQLCipher HMAC_SIZE 64 HMAC_SIZE=64 db/decryptor.py
SQLCipher RESERVE_SIZE 80 RESERVE_SIZE=80 db/decryptor.py
SQLCipher ROUND_COUNT 256000 ROUND_COUNT=256000 db/decryptor.py
SQLCipher MAC_SALT_XOR 0x3A MAC_SALT_XOR=0x3A db/decryptor.py
SQLCipher 算法 AES-256-CBC + HMAC-SHA512 AES-256-CBC + HMAC-SHA512 + PBKDF2-HMAC-SHA512 db/decryptor.py
撤回时限 110s _REVOKE_WINDOW_SEC = 110 routes/send.py line 761
大文件超时 30 + size_mb×1.5,上限 180s _BASE_TIMEOUT_SEC=30.0 / _PER_MB_TIMEOUT_SEC=1.5 / _MAX_TIMEOUT_SEC=180.0 ui/flows/send_file.py
大文件阈值 10MB _LARGE_FILE_THRESHOLD_MB=10.0 ui/flows/send_file.py
文件路径白名单 /config/Desktop//config/woc-uploads//tmp/woc-files/ _FILE_PATH_WHITELIST = ("/config/Desktop/", "/config/woc-uploads/", "/tmp/woc-files/") ui/flows/send_file.py
导出 limit ≤100000 _SYNC_EXPORT_LIMIT=1000(同步)/ 路由 limit 参数上限 100000 routes/export.py
导出并发 1 _MAX_CONCURRENT_EXPORT_TASKS=1 routes/export.py
导出批大小 200 _EXPORT_BATCH_SIZE=200 routes/export.py
导出 TTL 7200s WOC_EXPORT_TASK_TTL_SEC=7200 config.py / routes/export.py
服务启动门控阈值(登录检测) 300000 像素 if [ "$area" -ge 300000 ]; then + sleep 60 后启动 bridgeMAX_WAIT=600 超时不启动 bridge/s6/woc-bridge/run line 25-26
UI 窗口过滤阈值 min_area=10000 _find_main_window_id(self, window_title, min_area: int = 10000);过滤条件 if area >= min_area and w >= 200 and h >= 200if area < 10000 or geom.width < 100 or geom.height < 100: ui/backends/xdotool.py line 250-316 / ui/drivers/window.py line 137
DB 校验超时 30s / 间隔 2s / WAL 刷盘 2s _DB_VERIFY_TIMEOUT_SEC=30.0 / _DB_VERIFY_INTERVAL_SEC=2.0 / _DB_VERIFY_WAL_FLUSH_SEC=2.0 ui/flows/send_file.py
好友规则引擎决策顺序 enabled→accept_all→黑名单→allow_scenes→白名单→关键词→SKIP AcceptRuleEngine.evaluate 决策链一致 models/contact.py
文档元信息 8 字段 8 字段(标题/版本/作者/创建日期/最后更新日期/状态/关联需求/评审人) doc/产品需求文档规范.md 第 2 节
章节结构 11 章 11 章齐全且顺序一致 doc/产品需求文档规范.md 第 3 节
标题层级 不超过四级 不超过四级(#### doc/产品需求文档规范.md 第 5.2 节
Mermaid 流程图 3 个时序图 3 个 Mermaid 时序图(登录扫码、消息发送全链路、群发流程) doc/产品需求文档规范.md 第 5.2 节
GWT 验收标准 AC-01 ~ AC-20 20 条 Given-When-Then doc/产品需求文档规范.md 第 4.8 节

A.5 存疑/推断项独立核实结论

# 存疑点 任务描述 源码核实结论 处理
1 错误码数量 27 vs 25 27 个。源码 models/base.pyERROR_CODES 字典共登记 27 个错误码,比任务描述的 25 个多出 DB_LOCKEDLOGOUT_FAILEDDB_INIT_IN_PROGRESS 三个。 PRD 按 27 个登记,与源码一致;不修正
2 导出端点路径 /api/export/* /api/messages/export/*。源码 routes/export.pyrouter = APIRouter(prefix="/api/messages/export") PRD 按 /api/messages/export/* 登记正确;不修正
3 主窗口面积阈值 10000 vs 300000 双阈值(均真实存在,语义不同)300000 是 s6 启动脚本 bridge/s6/woc-bridge/run登录检测 / 服务启动门控阈值line 25-26 if [ "$area" -ge 300000 ]; then,命中后 sleep 60 启动 bridgeMAX_WAIT=60010000 是 UI 自动化 _find_main_window_idmin_area 参数(ui/backends/xdotool.py line 251、ui/drivers/window.py line 137用于过滤隐藏 / 加载小窗。 修正v1.1 初版误判"未找到 300000"v1.2 已更正为双阈值。5.2 节新增「服务启动门控」登记 3000005.5/7.3 节保留 10000
4 熔断 recovery_timeout 30~60s vs 30~300s 30~60s。源码 ui/orchestrator.pydb_verify_breakerrecovery_timeout=30.0app.pyaccept_breakerrecovery_timeout=60.0CircuitBreaker 默认 recovery_timeout=30.0未找到 300s 配置 PRD 表格"30~60s"登记正确;不修正

A.6 复盘结论

  • 本次复盘覆盖 PRD 全部数据维度,共发现 2 处需修正项:①环境变量计数 23→24v1.1 修正②主窗口面积阈值双阈值更正v1.2 修正v1.1 因未检索 bridge/s6/ 脚本而误判)。
  • 任务描述提及的四个存疑点经独立核实后3 项(错误码数量、导出端点路径、熔断 recovery_timeoutPRD 现行登记与源码一致1 项(主窗口面积阈值)经 v1.2 扩展真相源至 bridge/s6/ 后更正为双阈值。
  • v1.0 变更记录行中的"23 项环境变量配置"作为历史记录保留,不回溯修改,修正通过 v1.1 / v1.2 变更记录行体现。
  • 复盘后 PRD 数据与 bridge/ 全量源码(含 Python 代码与 s6 启动脚本)一致性达到 100%(修正项已闭环),可作为 bridge 模块的需求基线进入评审流程。