33 KiB
WxAgent 远程控制面架构与部署说明 v1.0
日期:2026-09-12 状态:当前实现与真机验证基线;不是生产安全认证或规模验收报告。 关联协议:
WxAgent-远程协议草案-v1.0.md关联计划:WxAgent-远程多节点控制与白名单数据上报开发计划.md
本文说明当前仓库中 Go 控制面、React 管理端、Windows Desktop Agent、节点诊断 CLI 的实际架构、数据边界、部署步骤、验证方法和已知限制。命令示例中的 Token、密码、账号标识和路径均使用占位符;不要把真实凭据写入仓库、命令历史或日志。
1. 适用范围与结论
当前闭环已经支持:
- 多节点注册、Bearer Token 认证、心跳和在线状态;
- 控制面持久化任务队列、任务租约、幂等键、取消请求、结果和审计;
send-text远程文本发送;- 远程读取可见会话、联系人/群和当前 UI 可见消息历史;
- 节点本地白名单过滤、稳定身份校验、结果范围化保存和断线补传;
- React 管理页面登录、节点/任务/事件/审计查询和 AI 消息处理流程;
- AI 实时事件/定时读取编排、JSON Schema 输出校验和受控自动回复;
- Gitea Actions 构建并推送控制面容器镜像;
- 直接 TLS、节点 mTLS、客户端证书指纹撤销文件和外部
*_FILESecret 注入; - 数据文件 active/passive 单写锁、自动备份、保留期限和
/readyz就绪检查; - 有界的就绪/只读任务并发基线脚本。
当前明确不提供:
- 任意 Shell、PowerShell、文件系统或代码执行;
- 控制面直接修改节点白名单;
- 非白名单会话、完整微信数据库或数据库密钥上传;
- 远程全量数据库历史读取;消息读取只来自当前微信 UI 可见历史;
- active-active 多实例、跨节点复制和托管式 Secret/PKI 服务;
/api/v1/sessions/open的远程导航能力。当前该接口可能返回409 CapabilityDisabled,不影响只读任务接口。
2. 总体架构
┌──────────────────────────┐
│ 浏览器 / 远程 Web 管理端 │
│ 登录、节点、任务、审计 │
└────────────┬─────────────┘
│ HTTPS(内网验证可显式使用私有 HTTP)
┌────────────▼─────────────┐
│ Go Control Plane │
│ API / Web / 认证 / 审计 │
│ 任务队列 / 租约 / JSON存储 │
└────────────┬─────────────┘
│ 节点主动出站 HTTP/HTTPS
│ 当前实现:心跳 + 最长30秒任务长轮询
┌────────────▼─────────────┐
│ Windows Desktop Agent │
│ 注册、心跳、轮询、结果上报 │
│ 本地任务账本 / 事件队列 │
└────────────┬─────────────┘
│ 同一微信窗口进入单一命令队列
┌────────────▼─────────────┐
│ FlaUI.UIA3 / Win32 │
│ 已登录、未锁定的交互桌面 │
└────────────┬─────────────┘
│
Weixin.exe
本机诊断入口:WxAgent.Host(只读/诊断)
本机常驻入口:双击 WxAgent.Tray → WxAgent.Service(127.0.0.1:5088)
2.1 组件与代码边界
| 组件 | 目录 | 技术 | 职责 |
|---|---|---|---|
| Core | node-agent/WxAgent.Core |
.NET 8 | 协议模型、白名单、账号上下文、任务账本、事件队列和纯逻辑规则 |
| Windows | node-agent/WxAgent.Windows |
.NET 8 Windows | FlaUI/UIA、Win32、微信会话/消息读取和写操作 |
| Service | node-agent/WxAgent.Service |
.NET 8 Windows | 本机 HTTP API、权限、操作队列、远程 Agent Hosted Service |
| Host | node-agent/WxAgent.Host |
.NET 8 Windows | 只读/诊断 CLI;不提供 serve 或配置修改 |
| Tray | node-agent/WxAgent.Tray |
.NET 8 Windows | 交互式用户会话中的常驻托盘、服务生命周期和配置重载 |
| Control Plane | control-plane |
Go 1.23 | 节点 API、Web API、任务/事件/审计持久化和静态页面服务 |
| Web | control-plane/web |
React/Vite | 控制面管理页面,构建后嵌入 Go 二进制 |
当前控制面是单进程 MVP:Go 二进制内嵌 React 静态资源,数据写入一个 JSON 文件。没有独立数据库、消息队列、节点入站端口或本地 Web/MCP 暴露到远程网络。
2.2 当前传输模型与计划模型的区别
开发计划保留了未来 mTLS WebSocket/等价长连接的架构方向;当前代码实际使用 HTTP/HTTPS 请求:
- 节点用 Bearer Token 调用注册接口;
- 节点周期性发送心跳,默认间隔 10 秒;
- 节点按账号轮询任务,服务端支持
wait_seconds=0..30的长轮询; - 节点确认、开始、续租并回传最终结果;
- 节点重启后从本地账本补传已完成但未确认的结果。
因此,部署当前版本不需要给 Windows 节点开放公网入站端口,也不需要部署 RabbitMQ。生产切换到 HTTPS/mTLS 或长连接前,应先完成独立协议、证书生命周期和回归验收。
3. 请求与任务流
3.1 远程写任务
Web 用户登录
→ POST /v1/tasks
→ 控制面原子写入 Pending
→ 节点轮询取得租约 lease_generation
→ 节点写入 remote-task-ledger.json
→ POST ack,中心变为 Accepted
→ 节点写入 Running,再 POST start
→ 复核活动账号、UI 状态和目标会话
→ 执行单窗口 UIA 写操作
→ 节点先保存最终结果,再 POST result
→ Web GET /v1/tasks/{task_id}
任务绑定 node_id + account_id + kind + idempotency_key。同一节点/账号/幂等键使用不同载荷会返回 IdempotencyConflict;重复提交相同载荷返回原任务,不创建第二个任务。
send-text 的节点白名单载荷为:
{
"target_id": "session_item_文件传输助手",
"text": "仅用于已批准测试会话的文本",
"confirmed": true
}
节点不接受 Shell、PowerShell、路径或任意代码。发送消息属于可能产生副作用的写操作,旧执行者是否停止无法证明时进入 ResultUnconfirmed,不得自动重放。
3.2 远程只读任务
远程读取复用同一任务队列,不增加节点入站读取 API:
| HTTP 接口 | 节点任务 kind | 用途 |
|---|---|---|
POST /v1/reads/sessions |
read-sessions |
读取当前可见会话,结果按白名单过滤 |
POST /v1/reads/contacts |
read-contacts |
读取联系人或群,结果按联系人稳定 ID 过滤 |
POST /v1/reads/messages |
read-messages |
读取一个已授权会话的当前 UI 可见历史 |
公共接口先由控制面校验请求结构并创建任务;节点执行前再次按当前本地配置校验:
- 全局上报开关必须开启;
- 目标账号必须存在且启用;
- 会话必须
enabled=true、identityVerified=true; - 数据类型必须被授权;
- 消息任务的
chat_id必须精确匹配唯一授权会话; - 会话读取结果按
AutomationId过滤;联系人读取结果按数据库稳定联系人 ID 过滤。
分页参数 limit 为 1–200,默认 50;offset 不得为负。单次任务请求体上限为 64 KiB,最终结果上限为 512 KiB。消息结果不是完整数据库历史,也不承诺跨页面的事务快照。
3.3 任务状态与租约
Pending → Accepted → Running → Succeeded
├→ Failed
└→ ResultUnconfirmed
未开始且可证明无副作用:Pending/Accepted → Cancelled 或 Expired
当前默认参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| 任务租约 | 30 秒 | 控制面 LeaseTTL |
| 心跳间隔 | 10 秒 | 节点周期 |
| 心跳超时 | 45 秒 | Web 查询节点时标记 Offline |
| Web 会话 | 8 小时 | 控制面内存会话 |
| 任务轮询窗口 | 0–30 秒 | wait_seconds |
| 备份间隔 | 5 分钟 | WXAGENT_CONTROL_PLANE_BACKUP_INTERVAL |
| 事件正文 | 16 KiB | 节点协议上限 |
| 任务载荷 | 64 KiB | 节点/中心共同限制 |
| 任务结果 | 512 KiB | 含读取内容 |
取消请求只设置 cancel_requested_at,不把任务直接伪造为已取消。终态不能被旧租约或旧结果覆盖;节点重启发现 Accepted/Running 无最终结果时,生成 ResultUnconfirmed,不重放 UI 操作。
4. 认证、授权与隐私边界
4.1 两类身份
| 身份 | 认证方式 | 可访问接口 |
|---|---|---|
| Web 用户 | POST /v1/auth/login 换取内存 Bearer 会话 |
Web 节点、任务、读取、事件和审计接口 |
| Windows 节点 | 节点专用 Authorization: Bearer <node-token> |
注册、心跳、任务轮询/租约/结果和事件上报 |
节点 Token 按 node_id 固定绑定。请求路径、注册体和 Token 中的节点 ID 不一致时拒绝。Token 只从启动环境读取,不写入控制面 JSON 数据文件;密码按当前 MVP 配置从环境读取并在内存中比较。
所有请求生成或传递 X-Correlation-Id;错误只返回固定错误码、通用消息和 Correlation ID。控制面响应设置 Cache-Control: no-store、X-Content-Type-Options: nosniff 和 Referrer-Policy: no-referrer。
4.2 Agent 连接是数据授权边界
Agent 安装并成功连接控制面即完成节点及数据同步授权,无需额外用户确认。连接注册会为当前已验证账号授予规范化只读数据范围;远程 Web 不能扩大到原始数据库、密钥或未脱敏内容,也不能改变只读边界。
有效数据授权仍要求:
- 节点连接认证成功;
- 账号身份已验证且稳定;
- 数据来自只读数据库/UI 快照的规范化记录;
- 数据类型属于当前同步协议允许范围。
非验证账号、原始微信数据库、数据库密钥、未脱敏 UI 树和普通日志中的完整内容仍不得进入中心 API、中心文件或节点待发送队列。连接授权只覆盖数据同步;验证写操作、消息监听和其他副作用继续由各自运行开关控制。
任务结果只有读取任务可以携带 content。读取结果在节点账本中与连接授权代次一起保存,补传时沿用该代次并再次经过当前节点认证检查;没有连接授权时只保存/回传控制元数据。
4.3 标识与数据范围
- 会话读取使用微信当前 UI 暴露并确认的稳定
AutomationId,当前示例形如session_item_*;不使用昵称、PID、窗口句柄或 UIA RuntimeId 作为持久授权身份。 - 联系人读取使用只读联系人数据库提供的稳定联系人 ID;显示名称只用于展示或辅助定位。
- 消息读取要求指定一个精确
chat_id,且该 ID 在当前可见会话中唯一。 - 当前只读消息历史来自微信 UI 可见内容,不是数据库全量扫描,也不通过远程接口暴露数据库密钥或原始数据库。
- 默认真机验证只使用“文件传输助手”“Hao 豪”“吉祥三宝”“消息测试专用群组”。未得到明确授权不得操作其他真实联系人或群聊。
5. 控制面接口清单
5.1 健康检查与 Web 接口
GET /healthz
POST /v1/auth/login
GET /v1/nodes
GET /v1/nodes/{node_id}
POST /v1/tasks
GET /v1/tasks?node_id=&account_id=&limit=
GET /v1/tasks/{task_id}
POST /v1/tasks/{task_id}/cancel
POST /v1/reads/sessions
POST /v1/reads/contacts
POST /v1/reads/messages
GET /v1/events
GET /v1/audit
GET /v1/ai/tools
GET /v1/ai/flows
POST /v1/ai/flows
GET /v1/ai/runs
GET /
/healthz 不要求认证,只返回 status、协议版本和 Correlation ID。其余业务接口要求 Web Bearer 会话。
读取请求的公共字段:
{
"node_id": "windows-direct-20260911",
"account_id": "<verified-account-id>",
"idempotency_key": "read-sessions-20260912-001",
"limit": 50,
"offset": 0,
"not_after": "2026-09-12T09:00:00Z"
}
各接口追加字段:
// /v1/reads/contacts
{"groups_only": false, "contains": "file"}
// /v1/reads/messages
{"chat_id": "session_item_文件传输助手", "include_content": true}
提交成功返回 202 Accepted:
{
"task_id": "<task-id>",
"status": "Pending",
"duplicate": false,
"state_version": 1
}
随后使用 GET /v1/tasks/{task_id} 读取状态和结果。不要用重复提交代替轮询,也不要把 Pending 或 Running 当作业务成功。
5.2 节点接口
POST /v1/nodes/register
POST /v1/nodes/{node_id}/heartbeat
GET /v1/nodes/{node_id}/tasks?account_id=&wait_seconds=0..30
POST /v1/nodes/{node_id}/tasks/{task_id}/ack
POST /v1/nodes/{node_id}/tasks/{task_id}/start
POST /v1/nodes/{node_id}/tasks/{task_id}/renew
POST /v1/nodes/{node_id}/tasks/{task_id}/result
POST /v1/nodes/{node_id}/events
节点注册上报协议版本、Agent 版本、能力、白名单计数和账号摘要;心跳上报微信进程/登录/锁屏状态、活动账号、队列长度和配置版本。节点接口不能由 Web Bearer 会话调用。
5.3 常见错误
| 错误码 | 处理 |
|---|---|
Unauthorized |
检查 Web 会话或节点 Token |
NodeIdentityMismatch |
Token、路径和请求体中的节点 ID 不一致 |
NodeNotReady / AccountNotReady |
等待节点注册并确认账号 |
ChatNotAuthorized |
本地白名单未允许 |
ChatIdentityUnconfirmed |
稳定身份不唯一或未重新确认 |
InvalidPagination |
limit 不在 1–200 或 offset 小于 0 |
IdempotencyConflict |
幂等键复用但任务参数不同 |
LeaseMismatch |
租约过期或执行代次不是当前值 |
ContentNotAllowed |
非读取任务试图回传内容 |
ResultUnconfirmed |
无法证明 UI/业务副作用或旧执行者已停止 |
CapabilityDisabled |
当前能力显式未启用,例如远程打开会话 |
6. 持久化与备份
6.1 控制面
容器内默认路径:
/data/control-plane-data.json
/data/control-plane-data.json.lock
/data/backups/control-plane-data.json.<unix-ns>.json
Store 在进程内 Mutex 和数据文件旁的非阻塞 flock 下执行读写;同一数据卷同时只允许一个活动控制面实例。写入临时文件、Sync 后原子替换,并在替换父目录后同步目录,文件权限为 0600。按 BackupInterval(默认 5 分钟)把旧版本写入备份目录并按 BackupCount 删除旧备份;任务、事件和审计按配置的保留时长在下一次写入前清理,运行中任务不会被清理。持久化对象包括:
PersistedState
├── nodes 节点注册、状态、账号摘要、心跳
├── tasks 任务载荷、租约、状态版本、结果
├── events 已接收白名单事件
└── audit 登录、任务和节点操作审计
当前没有数据库事务或 active-active HA;flock 提供的是共享卷上的 active/passive 单写保护。自动备份和按时长保留已实现,但异地复制、恢复演练和备份监控仍是部署门禁。升级/重建容器前应使用 scripts/control-plane-data.sh backup,恢复前停止活动实例并使用 restore,恢复时保持文件权限并确保只有一个控制面实例挂载该数据文件。
6.2 Windows 节点
建议部署目录:
C:\Users\Rogee\wx-agent\
├── WxAgent.Host.exe
├── WxAgent.Tray.exe
├── service.json
├── credentials.json
├── remote.json
├── data\remote-task-ledger.json
├── data\remote-event-queue.json
└── wxagent.log
remote.json 不提供 CLI 修改入口;远程任务账本记录任务指纹、最终结果、ReportingScopes 和是否已上报。节点重启时不重放未完成 UI 任务。Token、凭据文件和含读取结果的账本必须使用 Windows 用户 ACL 保护,不应复制到工单、日志或仓库。
7. Gitea Actions 构建与发布
7.1 前置条件
仓库:https://git.ipao.vip/rogee/wx-win-agent.git
工作流:.gitea/workflows/build-web-image.yml
镜像构建文件:control-plane/Dockerfile
在 Gitea 账号级 Actions Secrets 配置:
REGISTRY_TOKEN=<可推送 git.ipao.vip 容器仓库的 Token>
不要把 Token 放入 workflow、仓库变量、Dockerfile 或镜像层。工作流使用仓库所有者作为 Registry 用户名。
7.2 触发与标签
工作流在以下情况运行:
main分支 push;v*Git 标签 push;- Gitea Actions 手动
workflow_dispatch。
镜像名为:
git.ipao.vip/rogee/wx-win-agent
每次构建推送:
sha-<提交 SHA 前12位>
main 额外推送 latest;v1.0.0 额外推送 v1.0.0。部署环境应优先固定 SHA 或版本标签,只有开发环境使用 latest。
7.3 发布前本地检查
export PATH="$HOME/.dotnet:$PATH"
dotnet restore WxAgent.sln -p:EnableWindowsTargeting=true
dotnet test tests/node-agent/WxAgent.Core.Tests -c Release
dotnet test tests/node-agent/WxAgent.Service.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true
(cd control-plane && go test ./... && go vet ./...)
npm --prefix control-plane/web ci --no-audit --no-fund
npm --prefix control-plane/web run build
git diff --check
远程协议闭环检查:
./scripts/remote-control-smoke.sh
该脚本只使用测试节点/临时控制面,不操作真实微信联系人。
7.4 推送提交
确认工作树中没有凭据、临时配置和真机产物后:
git status --short --untracked-files=all
git diff --check
git add .
git diff --cached --stat
git commit -m "feat: document and publish remote control plane"
git push --set-upstream origin main
推送后在 Gitea Actions 查看构建日志,并核对 SHA 镜像是否存在。不要在未经检查的情况下使用 git add -f 把被忽略的 Token、日志、artifacts 或用户配置加入提交。
8. 控制面部署
8.1 构建本地镜像
docker build \
--file control-plane/Dockerfile \
--tag wxagent-control-plane:local \
control-plane
Dockerfile 分三阶段:Node 构建 React、Go 静态编译、distroless non-root 运行。容器监听 8090,数据卷为 /data,不需要在运行镜像中安装 Node 或 Go。
8.2 单机/内网验证部署
以下示例将控制面绑定到内网主机的 10.1.1.104:18090。真实 Token 和密码通过安全注入替换,不写入 shell 历史:
export NODE_TOKEN='<node-token>'
export WEB_PASSWORD='<web-password>'
mkdir -p /srv/wxagent/control-plane-data
docker rm -f wxagent-control-plane-direct 2>/dev/null || true
docker run -d \
--name wxagent-control-plane-direct \
--restart unless-stopped \
-p 10.1.1.104:18090:8090 \
-v /srv/wxagent/control-plane-data:/data \
-e WXAGENT_CONTROL_PLANE_ADDR=0.0.0.0:8090 \
-e WXAGENT_CONTROL_PLANE_DATA=/data/control-plane-data.json \
-e WXAGENT_NODE_TOKENS='{"windows-direct-20260911":"'"$NODE_TOKEN"'"}' \
-e WXAGENT_WEB_USERS='{"admin":"'"$WEB_PASSWORD"'"}' \
git.ipao.vip/rogee/wx-win-agent:sha-<12位SHA>
curl --fail http://10.1.1.104:18090/healthz
更推荐使用 Compose、systemd 或编排平台的 Secret 注入功能,避免 Token 出现在 docker inspect 的长期配置、进程列表和 shell 历史中。当前镜像支持 *_FILE Secret 文件注入;Secret 文件本身仍由部署平台负责保护和挂载。
单节点兼容环境变量也可用;对应的 *_FILE 形式优先从文件读取:
WXAGENT_NODE_ID
WXAGENT_NODE_TOKEN / WXAGENT_NODE_TOKEN_FILE
WXAGENT_WEB_USER
WXAGENT_WEB_PASSWORD / WXAGENT_WEB_PASSWORD_FILE
WXAGENT_NODE_TOKENS / WXAGENT_NODE_TOKENS_FILE
WXAGENT_WEB_USERS / WXAGENT_WEB_USERS_FILE
多节点/多 Web 用户使用 JSON map:
WXAGENT_NODE_TOKENS={"node-a":"token-a","node-b":"token-b"}
WXAGENT_WEB_USERS={"admin":"password-a","auditor":"password-b"}
8.3 生产 HTTPS/mTLS
当前 Go 进程支持直接 TLS。生产不得把明文端口直接暴露到公网;正式部署至少应:
- 配置 TLS 证书和私钥,服务端最低 TLS 1.3;
- 配置签发节点证书的客户端 CA,并启用
WXAGENT_MTLS_REQUIRE_NODE_CERT=true;Web 管理用户仍可只使用 HTTPS; - 节点使用 PEM 客户端证书和私钥,服务端按 CA 验证;
- 将已泄露或失效的客户端证书 DER-SHA256 指纹逐行写入撤销文件。可用
scripts/revoke-client-certificate.sh原子追加;控制面每次节点认证重新读取该文件,撤销文件缺失/不可读时启动或认证失败。Docker Secret 更新后需重建控制面容器; - Token 和密码通过
WXAGENT_NODE_TOKENS_FILE、WXAGENT_WEB_USERS_FILE等外部 Secret 文件注入,不进入镜像和控制面数据文件;本项目不实现 Token 轮换; - 仅允许管理端、节点网段访问对应路径;
- 对
/data做自动备份、异地复制、恢复演练和保留策略; - 完成失效证书、泄露响应、重放、中心重启、节点断线和并发规模测试。
allowInsecureHttp 只用于显式允许的私有 IP 内网验证;公网 HTTP 永远拒绝。它不是生产加密替代方案。
9. Windows 节点部署
9.1 构建 self-contained 发布包
Linux 构建 Windows 节点:
dotnet restore WxAgent.sln -p:EnableWindowsTargeting=true
dotnet publish node-agent/WxAgent.Host \
-c Release \
-r win-x64 \
--self-contained true \
-p:EnableWindowsTargeting=true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:PublishTrimmed=false
dotnet publish node-agent/WxAgent.Tray \
-c Release \
-r win-x64 \
--self-contained true \
-p:EnableWindowsTargeting=true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:PublishTrimmed=false
上传到已登录 Windows 用户目录(部署时可用 SSH/SCP,UI 操作不能在 Session 0 验证):
scp -r node-agent/WxAgent.Host/bin/Release/net8.0-windows10.0.19041.0/win-x64/publish/* \
rogee@10.1.1.101:'C:/Users/Rogee/wx-agent/'
scp -r node-agent/WxAgent.Tray/bin/Release/net8.0-windows10.0.19041.0/win-x64/publish/* \
rogee@10.1.1.101:'C:/Users/Rogee/wx-agent/'
升级前先停止托盘进程或交互式启动任务,避免覆盖被占用的单文件发布包;复制完成后校验 SHA-256,再启动 WxAgent.Tray.exe。
9.2 本机配置文件
service.json 至少包含本机监听、凭据和数据目录;远程配置建议单独保存:
{
"listenUrl": "http://127.0.0.1:5088",
"allowExternal": false,
"credentialFile": "C:\\Users\\Rogee\\wx-agent\\credentials.json",
"dataDirectory": "C:\\Users\\Rogee\\wx-agent\\data",
"remoteConfigurationFile": "C:\\Users\\Rogee\\wx-agent\\remote.json"
}
本机服务默认只监听 127.0.0.1:5088。除非有明确的隔离网络和访问控制,不要启用外部监听;该本机 API 不是控制面节点接入端口。
remote.json 结构如下:
{
"remote": {
"authAddress": "https://control.example.com",
"token": "<node-token>",
"nodeId": "windows-node-a",
"activeAccountId": "<verified-account-id>",
"allowInsecureHttp": false
},
"reporting": {
"enabled": true,
"configVersion": 1,
"accounts": [
{
"accountId": "<verified-account-id>",
"enabled": true,
"allowedChats": [
{
"type": "Private",
"chatId": "<verified-private-id>",
"enabled": true,
"identityVerified": true
}
]
}
]
}
}
当前版本不提供任何命令行配置入口,也不接受通过 CLI 修改远程凭据或上报白名单。Windows Agent 只通过双击 WxAgent.Tray.exe 启动;本机监听、访问凭据、验证写操作、后台消息监听、自动锁屏以及控制面连接(地址、节点 ID、Token、活动账号和 TLS 文件)均在“服务设置...”的“远程连接”页维护。保存远程连接时可将旧 remote.json 配置迁移到托盘管理的 service.json;上报范围由 Agent 连接授权和已验证账号身份统一确定,不再要求单独填写或确认白名单;不得用命令行伪造节点身份或绕过只读边界。
9.3 诊断与只读确认
以下命令仅用于诊断和只读检查,不修改配置、不启动常驻服务:
WxAgent.Host.exe doctor
WxAgent.Host.exe inspect-ui --output artifacts\ui-tree.json
WxAgent.Host.exe session list
WxAgent.Host.exe remote auth show --config C:\Users\Rogee\wx-agent\remote.json
WxAgent.Host.exe remote reporting show --config C:\Users\Rogee\wx-agent\remote.json
WxAgent.Host.exe remote probe run --config C:\Users\Rogee\wx-agent\remote.json
9.4 交互式启动要求
UIA Agent 必须运行在微信所在的已登录、未锁定 Windows 用户会话中。只允许由该用户双击 WxAgent.Tray.exe 启动,不使用 Windows Service、Session 0、serve --config 或任何启动参数。自动锁屏策略在“服务设置...”窗口中配置:
产品验收入口是资源管理器/开始菜单中的 WxAgent.Tray.exe 双击,不是命令行。
部署完成后至少执行:
WxAgent.Host.exe doctor
WxAgent.Host.exe inspect-ui --output artifacts\ui-tree.json
WxAgent.Host.exe smoke
smoke 会向默认测试会话发送唯一标记,是写操作;生产或真实联系人验证前必须明确确认目标。
10. 远程验证操作手册
10.1 控制面登录
curl --fail http://10.1.1.104:18090/healthz
curl --fail http://10.1.1.104:18090/readyz
curl --fail -c /tmp/wxagent.cookies \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<web-password>"}' \
http://10.1.1.104:18090/v1/auth/login
curl --fail -b /tmp/wxagent.cookies \
http://10.1.1.104:18090/v1/nodes
实际客户端也可把登录响应中的 access_token 作为 Authorization: Bearer 使用。不要把响应 Token 写进共享日志。
10.2 读取会话、联系人和消息
以下示例使用 Web Bearer Token 占位符;每个读取调用应使用新的、可追踪的幂等键:
BASE=http://10.1.1.104:18090
AUTH="Authorization: Bearer <web-access-token>"
curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"sessions-001","limit":50,"offset":0}' \
"$BASE/v1/reads/sessions"
curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"private-001","groups_only":false,"limit":50,"offset":0}' \
"$BASE/v1/reads/contacts"
curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"messages-001","chat_id":"<authorized-session-id>","include_content":true,"limit":20,"offset":0}' \
"$BASE/v1/reads/messages"
从响应取 task_id 后:
curl --fail -H "$AUTH" "$BASE/v1/tasks/<task-id>"
只有 status=Succeeded 且 result.content 存在时才读取内容;Failed、Cancelled、Expired 和 ResultUnconfirmed 都不能视为读取成功。
10.3 发送测试文本
只对已批准测试目标执行:
curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
-d '{
"node_id":"windows-direct-20260911",
"account_id":"<account-id>",
"kind":"send-text",
"idempotency_key":"send-20260912-001",
"payload":{
"target_id":"session_item_文件传输助手",
"text":"wxagent-controlled-test-<unique-marker>",
"confirmed":true
}
}' \
"$BASE/v1/tasks"
发送任务必须结合任务终态和微信本地历史读回确认;Succeeded 代表 Agent 已确认完成调用,不代替业务侧读回核验。
10.4 并发基线
在 staging 控制面和已注册测试节点上执行;默认只创建 read-sessions 读取任务,不执行写任务:
WXAGENT_SCALE_WEB_PASSWORD='<test-password>' \
scripts/control-plane-scale-smoke.py \
--base-url http://127.0.0.1:8090 \
--node-id node-a --account-id account-a \
--requests 100 --workers 10
只测就绪接口时不需要 Web 密码或节点参数:
scripts/control-plane-scale-smoke.py --mode ready \
--base-url https://control.example.com --requests 500 --workers 50
脚本输出成功数、HTTP 状态分布、p50/p95;每次压测应保存版本、配置、机器规格、数据文件大小和结果,不把结果直接当作 1000 路生产验收。
11. 故障排查
| 现象 | 优先检查 |
|---|---|
/healthz 失败 |
容器、端口映射、绑定地址、防火墙和 /data 权限 |
| Web 登录 401 | WXAGENT_WEB_USERS 是否为合法 JSON map,密码是否与当前容器一致 |
| 节点注册 401 | Token 是否与 node_id 一一绑定,节点是否读到最新 remote.json |
节点 Offline |
Windows 托盘进程、网络、心跳时间、控制面日志和节点 remote status show |
SessionLocked |
必须恢复微信所在的已登录、未锁定交互会话;Session 0 不算通过 |
WechatNotLoggedIn |
在微信桌面正常完成登录/手机确认,不绕过登录流程 |
AccountContextUnconfirmed |
重新确认活动账号和 UI 绑定,不能只看昵称 |
读取返回 ChatNotAuthorized |
检查全局/账号/会话开关、类型、稳定 ID 和 identityVerified |
| 读取返回空列表 | 先用本机 session list/联系人读取核对 ID 编码和当前可见范围 |
读取返回 ChatIdentityUnconfirmed |
当前 UI 中不存在唯一匹配的稳定会话,禁止用昵称替代 |
LeaseMismatch |
任务已过租约或有旧执行者;不要重复创建写任务掩盖问题 |
ResultUnconfirmed |
人工核对微信是否已产生副作用;不得自动重放写操作 |
| 修改 Token 后仍旧连接 | 节点远程地址/Token改变需要重启 Agent;控制面凭据改变需要重启容器 |
/sessions/open 返回 409 |
当前导航能力未启用;使用本地 UI 或现有只读读取任务,不把它当成传输故障 |
普通日志只记录错误码、类型、路径、耗时和 Correlation ID,不应通过日志回显消息正文、联系人名称、附件或凭据。排查时使用 Correlation ID 关联控制面审计和节点日志。
12. 当前验证证据与发布门禁
已完成的当前基线验证:
- 控制面 Docker 镜像构建成功,
/healthz返回ok/v1; - Go
go test ./...、go vet ./...通过; - Core 测试
168/168通过;Service 测试20/20通过;完整 solution Release build 通过; scripts/remote-control-smoke.sh通过,覆盖注册、心跳、任务租约、幂等、结果回传、读取结果保留和拒绝路径;- Windows 节点
windows-direct-20260911在已登录、未锁定会话中上线,注册read-sessions、read-contacts、read-messages能力; - 真机
send-text任务Pending → Running → Succeeded,并用文件传输助手本地历史读回唯一标记; - 真机远程读取:会话 1 条、私聊联系人 1 条(
filehelper)、群 1 条(53271859539@chatroom)、文件传输助手消息 3 条,四个读取任务均为Succeeded。
提交或部署新版本前必须重新执行相关测试。以下条件满足前不能标记生产完成:
- 在实际部署环境启用控制面 HTTPS、节点 HTTPS/mTLS 和 Secret 文件挂载;
- 客户端证书撤销、泄露响应和重新签发演练(不包含 Token 轮换);
- 自动备份/保留、异地复制、恢复演练、active/passive 故障切换、并发写入和容量上限验证;
- 节点/控制面重启、断网、租约过期和
ResultUnconfirmed人工核对; - 真实目标规模、长时间运行、数据保留和日志泄漏扫描;
- 当前微信版本变化后的
doctor、脱敏 UI 树和 smoke 回归。
13. 相关文件
control-plane/Dockerfile:控制面多阶段镜像构建;control-plane/server.go、protocol.go、store.go、tls.go:HTTP/TLS 路由、协议、持久化和证书撤销;control-plane/compose.production.yml:Docker Secret、TLS/mTLS 和保留配置的生产示例;control-plane/web/:React 管理端;.gitea/workflows/build-web-image.yml:Gitea 镜像发布;node-agent/WxAgent.Service/RemoteAgentHostedService.cs:节点注册、轮询、任务执行和补传;node-agent/WxAgent.Core/ReportingAuthorization.cs:统一本地白名单授权;node-agent/WxAgent.Core/RemoteTaskLedger.cs:本地任务结果账本;node-agent/WxAgent.Host/RemoteCliCommands.cs:远程认证、状态和白名单 CLI;docs/validation/Agent-Install-2026-09-11.md:Windows 安装与真实验证记录;scripts/remote-control-smoke.sh:Linux/临时控制面闭环检查;scripts/control-plane-scale-smoke.py:就绪/只读任务并发基线;scripts/control-plane-data.sh:控制面数据备份和锁保护的原子恢复;scripts/revoke-client-certificate.sh:计算客户端证书指纹并原子追加撤销列表。