feat: add account-scoped data synchronization
This commit is contained in:
@@ -4,6 +4,10 @@
|
||||
> 日期:2026-09-03
|
||||
> 目标:使用 C# 独立实现 wxautox4 商业版的业务功能;只兼容功能,不兼容 Python API。第一阶段不实现 Windows Service 和正式控制面。
|
||||
|
||||
## 后续专项计划
|
||||
|
||||
- [会话消息同步与分账号存储开发计划](WxAgent-会话消息同步与分账号存储开发计划.md):2026-09-21 新增,现已完成 P0–P4 实现与白名单测试账号验收;长期 endurance、断电和多控制面 HA 仍按专项记录作为后续运维范围。不改变本文第一阶段范围,后续会话消息持久化范围以专项计划为准。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
可以采用以下开发方式:
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
# WxAgent 会话消息同步与分账号存储开发计划
|
||||
|
||||
> 版本:0.1
|
||||
> 日期:2026-09-21
|
||||
> 状态:P0–P4 已实现并完成本轮真机验收;长期 endurance、断电和多控制面 HA 属于后续运维专项,不在本轮通过结论中冒充。
|
||||
> 目标:页面读取不再依赖现场微信操作;通过授权范围内的后台增量同步,提供可用、可追踪新鲜度、账号隔离的会话和消息查询。
|
||||
|
||||
## 1. 背景与问题定义
|
||||
|
||||
本计划承接 [基础开发计划](WxAgent-CSharp-开发计划.md) 和 [远程控制与白名单上报计划](WxAgent-远程多节点控制与白名单数据上报开发计划.md)。本专项对会话/消息查询存储作增量扩展,不改变微信只读数据库边界、单窗口 UI 命令队列、显式账号切换和白名单原则,不建设 SaaS 或跨账号聚合界面。
|
||||
|
||||
2026-09-21 排查得到的事实:
|
||||
|
||||
- 节点在线、微信已登录且桌面未锁定时,仍出现多次 `read-sessions` 成功但 `items=[]`。
|
||||
- `RemoteAgentHostedService.ExecuteReadTaskAsync` 返回的是 UI 可见会话与授权范围的交集,不是账号完整会话目录。
|
||||
- `MessagesView` 进入页面/切换账号时清空状态,再提交远程读任务;成功结果整体替换列表。
|
||||
- 之前的只读及发送 smoke 证明单次链路可用,不证明持续同步、完整性或长时间可靠性。
|
||||
- 此次空列表已按 unknown/partial/complete 分层处理;平台副本、后台同步和旧读取回退均已通过本轮验收,仍不把单次结果当作长期稳定性承诺。
|
||||
|
||||
必须分别解决:
|
||||
|
||||
| 需求 | 实现手段 |
|
||||
| --- | --- |
|
||||
| 页面立即可读 | 平台持久化查询副本,不等待 Agent 现场读取 |
|
||||
| 新消息及时出现 | Agent 后台采集、增量上报、页面增量刷新 |
|
||||
| 不漏、不重、不串账号 | 稳定身份、游标、持久化确认、幂等及补查 |
|
||||
| 用户知道数据是否可信 | 完整性、最后成功同步时间、错误和授权状态分开表示 |
|
||||
|
||||
**缓存不能修复采集缺失;部分空快照不能充当全量删除指令。**
|
||||
|
||||
## 2. 方案与范围
|
||||
|
||||
### 2.1 本专项采用的开发方向
|
||||
|
||||
1. 平台使用“小型中心目录 + 每个已验证账号一个 SQLite 逻辑分片”。
|
||||
2. 每个账号库采用相同的平台自有 schema,不克隆微信内部表结构。
|
||||
3. Agent 解析本地数据,只上传已授权的规范化记录,不上传原始微信库或密钥。
|
||||
4. 页面读取平台副本;刷新请求只触发后台同步,不把 UI 自动化作为页面读取必经路径。
|
||||
5. 后台采集优先复用只读数据库能力;UIA 用于交互、诊断及明确标记为局部观察的补充数据。
|
||||
6. 首版单控制面进程、本机磁盘;复用现有 HTTP、认证和持久化上报机制,不引入 Redis、Kafka、微服务或通用分片框架。
|
||||
|
||||
这里的“账号”是平台稳定数据主体,而不是进程、窗口、节点或昵称。将来若引入租户,账号映射必须纳入所属授权域;本期不实现租户系统。
|
||||
|
||||
### 2.2 首版包含
|
||||
|
||||
- 授权会话目录、近期消息和同步状态的持久化及分页查询。
|
||||
- 有界初始化、消息增量、重连补传、版本与来源信息。
|
||||
- 部分/完整快照语义、权限收回、容量和保留策略。
|
||||
- 单账号备份恢复、schema 迁移、有限多账号负载验证。
|
||||
- 只允许已验证且当前允许采集的账号产生新数据;非活动账号只查已同步历史,不暗中切换微信账号。
|
||||
|
||||
### 2.3 不包含
|
||||
|
||||
- 无限制全量历史与附件上传、微信数据库文件镜像、协议破解或数据库写入。
|
||||
- 跨账号全文搜索、统一收件箱、跨账号统计、跨账号自动去重。
|
||||
- 多控制面实例共享写 SQLite、网络共享盘部署 SQLite、自动分布式迁移。
|
||||
- 本专项自动开放消息发送、修改发送安全门禁或恢复已暂缓的其他功能。
|
||||
- 将平台保留历史等同于微信当前状态;本地已删除且从未被观察到的数据无法恢复。
|
||||
|
||||
## 3. 目标链路与现有能力复用
|
||||
|
||||
```text
|
||||
Windows 微信本地数据(只读)
|
||||
→ 确认账号、来源版本和数据库证据
|
||||
→ 读取授权范围、标准化、增量扫描
|
||||
→ 持久化待发送批次及读取进度
|
||||
→ HTTPS 出站上报(重试前再校验权限)
|
||||
→ 平台账号库事务写入数据、去重记录和接收进度
|
||||
→ 持久化成功后 ACK
|
||||
→ Web 查询账号库并展示新鲜度
|
||||
```
|
||||
|
||||
| 现有位置 | 复用/变更方向 |
|
||||
| --- | --- |
|
||||
| `node-agent/WxAgent.Windows/WechatMessageDbReader.cs` | 复用消息解码;现有按最新记录限量查询不等于增量同步,需补分页、游标和分片覆盖 |
|
||||
| `node-agent/WxAgent.Windows/SqlCipherDatabaseReader.cs` | 保持只读、有界查询;验证 WAL 可见性与一致性 |
|
||||
| `node-agent/WxAgent.Host/WindowsAgentBackend.cs` | 提供已验证账号上下文的数据采集入口;禁止用昵称猜测归属 |
|
||||
| `node-agent/WxAgent.Core/RemoteEventQueue.cs`、`RemoteControlClient.cs` | 复用持久化队列、认证、重试前授权;评估批次 ACK 和容量语义,不能直接假设已有队列满足无遗漏同步 |
|
||||
| `node-agent/WxAgent.Service/RemoteAgentHostedService.cs` | 同步调度与任务/心跳互不长时间阻塞;保留原远程读任务作兼容和诊断 |
|
||||
| `control-plane/store.go`、`server.go` | 保留现有控制任务/节点状态,新增目录和账号数据访问;不向全量 JSON 快照持续堆积消息正文 |
|
||||
| `control-plane/web/src/main.jsx` | `MessagesView` 改为查询副本、后台刷新、明确状态,分离会话与消息请求代次 |
|
||||
|
||||
数据库采集不能通过轮流打开所有聊天来实现。文件变化/UIA 事件只作加速信号,低频增量轮询与补查负责兜底;不得因没有事件就认定没有新增数据。
|
||||
|
||||
## 4. 存储布局与身份
|
||||
|
||||
### 4.1 目录布局
|
||||
|
||||
```text
|
||||
control-plane-data/
|
||||
catalog.sqlite # 平台账号、已验证源绑定、分片登记、schema 状态
|
||||
accounts/<opaque-account-key>/data.sqlite
|
||||
attachments/<opaque-account-key>/... # 后续按需下载,不存消息库 BLOB
|
||||
backups/ # 受控、加密、有保留期
|
||||
```
|
||||
|
||||
- 文件路径由服务端生成,不直接拼接用户提交的账号名、微信号或任意路径;阻止路径穿越、符号链接逃逸及任意文件访问。
|
||||
- 目录库只登记路由/生命周期,消息、接收 ACK 和同步进度以账号库为事实源,不设计跨库分布式事务。
|
||||
- 现有 JSON 控制存储在首版继续承担节点、任务、审计等原职责;不在本专项顺带迁移全部控制面数据。
|
||||
- 分片创建使用明确的创建中/可用/失败状态;崩溃后可以校验并恢复,不把半建文件暴露给查询。
|
||||
|
||||
### 4.2 稳定身份与换机
|
||||
|
||||
当前本地账号上下文可能依赖数据库根目录指纹,不能把它直接视为跨设备稳定账号 ID。需要平台账号 ID 与已验证本地身份的显式映射:
|
||||
|
||||
- 相同昵称、备注或 UI `session_item_*` 不能证明是同一账号/会话。
|
||||
- 会话主键使用已验证 `chat_id`,UI AutomationId 仅为来源属性。
|
||||
- 换机、路径变化、数据库重建或重新绑定后,重新验证身份与来源代次;不得自动按昵称合并。
|
||||
- 首版同一平台账号只接受一个有效采集源代次;绑定变更后拒绝旧节点继续写入,防止混合采集。
|
||||
- 显式账号切换后,界面和请求立即切换隔离上下文;保留 A 账号历史不能让它短暂显示在 B 账号下。
|
||||
|
||||
### 4.3 每账号最小逻辑数据模型
|
||||
|
||||
具体 DDL 在 P1 冻结,以下是契约而非已存在的表:
|
||||
|
||||
| 表/记录 | 必要信息 |
|
||||
| --- | --- |
|
||||
| `conversations` | 稳定 chat_id、类型、名称、最后已知活动、来源、观察时间、目录状态 |
|
||||
| `messages` | 平台消息 ID、chat_id、源消息身份、方向/类型/正文、源时间、观察/入库时间、来源版本 |
|
||||
| `sync_state` | 来源代次、按来源分片/流划分的游标、扫描边界、最后成功时间、完整性、错误 |
|
||||
| `ingest_batches` | 来源代次、批次 ID、内容摘要、序列和已提交 ACK;有界保留且覆盖恢复窗口 |
|
||||
| `reporting_scopes` | 本地授权范围的已确认镜像、策略版本、有效期限和撤销状态;不能由平台扩大范围 |
|
||||
|
||||
要求:
|
||||
|
||||
- 一张消息表按 chat_id 索引,不照搬微信“一会话派生一张表”。
|
||||
- 消息分页使用 `(chat_id, source_time, message_id)` 等确定性键集顺序,不依赖不断增长的 offset;源时间不充当唯一同步游标。
|
||||
- 数据库 server_id 只有验证唯一性后才参与去重;无可靠 server_id 时使用来源代次、分片、会话与 local_id 的组合身份。不能用正文哈希把两次相同消息合并。
|
||||
- UI 指纹仅用于短期局部去重,不作为数据库永久消息 ID;无法证明同一条时不得强行合并 UI 与 DB 记录或重复触发下游动作。
|
||||
- 保留必要来源字段用于诊断,不默认保存原始行、非授权正文或原数据库 dump。
|
||||
|
||||
## 5. 同步协议与正确性
|
||||
|
||||
### 5.1 初始化与持续同步
|
||||
|
||||
1. 验证账号绑定、数据库 page 1 HMAC、schema 能力和授权版本;任何身份歧义先停止采集。
|
||||
2. 建立授权会话目录。完整会话元数据来源需真机验证;只能取得联系人时,不将“全部联系人”冒充“已有会话”。可以显示授权目标待同步占位,但状态必须明确。
|
||||
3. 首版建议初始化最近 7 天数据,存储保留 30 天;均为待容量基线验证的可配置默认值,不自动采集全部历史。
|
||||
4. 捕获初始化边界,使用稳定键分页;同步初始化之后的新数据,防止回填期间新消息被跳过。
|
||||
5. 按来源分片维护进度,发现新增分片;采用有界重叠扫描和幂等入库处理迟到记录,并定期补查。
|
||||
6. 若实测不存在可靠更新游标,明确限制可检测的历史修改范围;不能宣称所有旧消息更新/撤回均实时完整。
|
||||
7. 历史按需补取限于明确请求且授权、源端仍存在的数据;补取不绕过保留期限,扩展范围需明确配置。
|
||||
|
||||
### 5.2 批次、持久化与重传
|
||||
|
||||
- 批次带协议版本、平台/本地账号绑定、源代次、策略版本、序列、幂等 ID、正文摘要和游标范围。
|
||||
- Agent 必须先持久化待发数据再推进已采集进度;已采集游标与平台已确认游标分离。
|
||||
- 平台在同一账号库事务内写入记录、幂等凭据和 ACK 进度;事务持久化成功后才返回 ACK。
|
||||
- ACK 丢失时重发相同批次并返回相同确认;相同批次 ID 携带不同正文必须拒绝。
|
||||
- 每来源流首版按序发送,不跨缺口推进确认游标;检测重置、跳号和旧源代次,要求补传或重新初始化。
|
||||
- SQLite 崩溃恢复与平台备份回退都要校验游标;不能因为客户端游标更大就假装平台数据齐全。
|
||||
- 队列满、磁盘不足、数据过大时停止推进并报告背压;不能静默丢弃后继续报告已同步。若源数据在停机期间被清理,明确标记缺口。
|
||||
- 同步重试只针对幂等数据写入,不复用为发送消息等 UI 写操作的自动重试机制。
|
||||
|
||||
### 5.3 完整性与空结果
|
||||
|
||||
每次结果分别携带:`source`、`coverage`(unknown/partial/complete)、覆盖会话/时间范围、策略版本、观察时间、最后成功扫描时间、同步状态和错误码。
|
||||
|
||||
- `complete` 只针对声明的授权范围和时间窗口,不代表微信账号全部历史完整。
|
||||
- 空的 UI 快照、失败读取和未完成分页不删除已知会话/消息。
|
||||
- 同步成功但无新消息可以更新扫描成功时间;心跳在线不能更新数据同步时间。
|
||||
- 完整快照合并必须校验范围、策略版本和快照完成标记;缺席会话最多标记为不在当前目录,不自动删除历史正文。
|
||||
- 删除、撤回、保留期清理、授权撤销是不同事件,分别处理;未观察到源端变化则标记未知,不推断事实。
|
||||
|
||||
## 6. 查询接口与页面行为
|
||||
|
||||
以下接口已落地;协议字段和脱敏真机证据同步维护于 [P4 验收记录](validation/WxAgent-会话消息同步-P4-验收记录.md):
|
||||
|
||||
| 接口 | 行为 |
|
||||
| --- | --- |
|
||||
| `GET /v1/data/accounts/{id}/conversations` | 授权范围内分页查询持久化目录及同步状态,不触发 UIA |
|
||||
| `GET /v1/data/accounts/{id}/messages?chat_id={chatId}` | 稳定游标分页、限量返回,含覆盖范围和新鲜度 |
|
||||
| `GET /v1/data/accounts/{id}/sync-status` | 显示来源、积压、最近成功、缺口和错误 |
|
||||
| `POST /v1/data/accounts/{id}/refresh` | 合并重复请求,异步调度授权后台同步,立即返回受理状态 |
|
||||
|
||||
- 保留现有 `/v1/reads/*` 作诊断/过渡兼容,不让 Web 继续每次访问都创建一个现场任务。
|
||||
- 区分“未同步”“同步中”“授权范围内确实为空”“数据部分可用”“同步失败”“授权已失效”。
|
||||
- 刷新失败保留同账号仍有权访问的历史数据,显示最后成功时间和错误;不能清空后只显示“暂无会话”。
|
||||
- 离线仍可查看权限有效的历史;不得显示“实时”,不得因历史可读而放开发送。
|
||||
- 会话与消息请求分别取消/校验代次,避免共享 requestRef 导致相互吞掉响应;切换账号不能接收旧请求结果。
|
||||
- 首版页面可见时轻量轮询平台数据,页面隐藏后降频/暂停;不触发微信窗口切换。SSE 只有实测需要再加。
|
||||
- 新入库历史回填不应当作实时新消息重复驱动现有 AI/通知流程;同步结果明确区分 bootstrap/backfill/live/reconcile,事件投递同样幂等。
|
||||
|
||||
## 7. 授权、安全与保留策略
|
||||
|
||||
- Agent 在采集、序列化/入队及每次实际发送前检查本地 Reporting 范围和数据类型;未授权内容不进入平台、待发队列或普通日志。
|
||||
- 平台查询同时校验用户权限、平台账号归属和当前有效的上报授权镜像;账号文件隔离不替代 API 授权。
|
||||
- 撤销后 Agent 立即停止该范围采集/重传并清理待发正文;平台收到撤销后立即禁止相关历史查询,旧策略批次不能恢复授权。
|
||||
- 网络分区时平台无法瞬时获知本地撤销。P1 必须冻结有限的授权有效期、续期和失效行为:到期前是最后已知授权,到期后历史正文也拒绝访问,只展示同步状态。不能同时承诺“无限离线访问”和“本地撤销立即跨网络生效”。
|
||||
- 撤销及保留期清理覆盖会话预览、正文、附件、旧任务结果和事件副本;物理删除与备份过期策略另行记录,不能只删一个查询表。
|
||||
- “明文 SQLite”指平台可直接查询,不使用微信 SQLCipher 格式;要求数据目录/文件最小权限、HTTPS、受控服务身份,以及部署磁盘和备份加密,不公开数据库下载地址。
|
||||
- 不把微信密钥上传至平台;日志只记录计数、脱敏标识、错误码和关联 ID。
|
||||
- 大附件不进入消息表;设置账号级磁盘预算、待发预算、批次字节上限和保留期。超限明确报错,不能静默损坏同步连续性。
|
||||
|
||||
## 8. SQLite 运维与规模边界
|
||||
|
||||
- 首版一个控制面进程打开账号分片;每库单写者、短事务、有界 busy timeout 和重试预算,数据库写入幂等。
|
||||
- 启用 WAL,持久化 ACK 必须匹配实际耐久性设置(首版以 `synchronous=FULL` 为基线验证),不得在未落盘时宣称可恢复。
|
||||
- 设置全局分片连接预算、每库连接上限和空闲回收;不用“一账号一个永久线程/无限连接池”。
|
||||
- WAL checkpoint、保留清理、必要空间回收放在低负载时段;监控长读事务阻碍回收,不对每次请求执行 VACUUM。
|
||||
- 使用 SQLite 一致性备份机制,不直接复制活跃主文件而漏掉 WAL;恢复前完整性检查、schema 校验、账号绑定和授权复核,再做补同步。
|
||||
- 每账号 schema 独立记录版本,迁移失败隔离该库,不阻塞所有账号;备份与版本兼容检查先于不可逆迁移。
|
||||
- 分库不减少总容量:测量正文、索引、WAL、待发队列和备份占用后计算磁盘预算;SQLite 文件多并不等于可无限扩展。
|
||||
- 本机磁盘与单写入服务是部署边界,不把文件放在 SMB/NFS 上实现多实例共享写。
|
||||
- 出现持续单账号写拥塞、多实例共享写需求、跨账号查询主导或文件运维成本过高时,再评估 PostgreSQL 等;以指标决定,不按账号数拍脑袋迁移。
|
||||
|
||||
## 9. 分阶段任务与完成门槛
|
||||
|
||||
所有任务初始为未完成,按顺序交付;前一阶段安全门槛未通过,不切换生产读取路径。
|
||||
|
||||
### P0:修正空列表语义与诊断
|
||||
|
||||
- [x] 定位当前空列表:采集可见项数、授权匹配数、身份/联系人映射结果和错误,不记录未授权名称或正文。
|
||||
- [x] 明确局部读取契约;身份不确定不能包装为成功的“完整空列表”。
|
||||
- [x] 前端刷新失败/局部空结果不清空同账号有效历史;账号切换立即隔离,分离会话/消息请求代次。
|
||||
- [x] 加入成功空结果、部分结果、读取失败、过期响应和账号切换的最小回归测试。
|
||||
|
||||
完成门槛:能解释本次空列表的触发条件;没有以“保留旧数据”掩盖错误或跨账号展示。此阶段不宣称跨页面重载的持久化能力已经具备。
|
||||
|
||||
### P1:存储、身份与协议基线
|
||||
|
||||
- [x] 建立平台稳定账号映射、采集源代次、目录库和账号库 schema/索引。
|
||||
- [x] 选择并验证 `modernc.org/sqlite` Go SQLite 驱动及目标部署构建;不引入通用 ORM。
|
||||
- [x] 冻结批次、幂等、ACK、完整性、分页、策略版本及授权有效期契约。
|
||||
- [x] 实现账号库数据访问、增量 upsert、同事务确认和 API 权限检查。
|
||||
- [x] 使用合成数据验证迁移、重放、授权失效、目录创建崩溃恢复和备份恢复。
|
||||
|
||||
完成门槛:重放不重复、不同账号不可越权、ACK 后重启数据仍在、磁盘故障不假确认。新增数据库功能仅用于测试/影子验证,不改变现有页面默认路径。
|
||||
|
||||
### P2:Agent 只读数据库增量采集
|
||||
|
||||
- [x] 验证会话目录来源、page 1 HMAC、账号绑定、WAL 新记录、分片发现、源 ID 和迟到/修改可见性。
|
||||
- [x] 补齐初始化与增量分页、边界衔接、重叠扫描和有界补查;避免反复全表/全库扫描。
|
||||
- [x] 增加持久化待发批次、双进度、背压和崩溃续传;复用现有上报模块并补足缺失语义。
|
||||
- [x] 后台调度不抢桌面焦点、不自动切换账号,不阻塞心跳及业务任务。
|
||||
- [x] 身份失配、schema 不支持或数据源不可用时停止并报告;保留有权访问的历史但标记陈旧。
|
||||
|
||||
完成门槛:白名单测试对象新消息无需打开对应聊天即可采集;源端数据在约定覆盖范围内时重连可补齐,超范围缺口显式暴露。不将“读到最新 N 条”当成增量完整性验收。
|
||||
|
||||
### P3:平台查询与 Web 切换
|
||||
|
||||
- [x] 接入新增 GET 查询及刷新调度;Web 不再依赖现场读取任务完成。
|
||||
- [x] 展示数据覆盖范围、新鲜度、积压和错误;实现有限轮询及账号隔离。
|
||||
- [x] 明确历史回填与实时消息的下游处理,避免重复 AI 回复或通知。
|
||||
- [x] 对选定账号影子对比后切换;旧读取路径保留作诊断和显式回退。
|
||||
|
||||
完成门槛:页面重载历史仍在;源端不可用时不伪造空列表;切换微信页面不使平台列表消失;旧数据可读不放松发送校验。
|
||||
|
||||
### P4:容量、恢复与上线验收
|
||||
|
||||
- [x] 设置保留期/容量预算、连接上限、清理与 checkpoint,验证慢查询和写入积压。
|
||||
- [x] 演练账号库备份恢复、平台回退后游标对账、撤销授权和备份保留处理。
|
||||
- [x] 完成 §10 的专项最小验证并记录未测范围,不把单次 smoke 当作长期稳定性结论。
|
||||
- [x] 更新协议、部署说明、操作手册和脱敏验收证据;已验收测试账号启用同步,其他账号保持默认关闭。
|
||||
|
||||
P2–P4 脱敏验收记录:[`docs/validation/WxAgent-会话消息同步-P4-验收记录.md`](validation/WxAgent-会话消息同步-P4-验收记录.md)。
|
||||
|
||||
## 10. 测试、指标与真机证据
|
||||
|
||||
### 10.1 必须覆盖的逻辑/故障场景
|
||||
|
||||
| 场景 | 期望 |
|
||||
| --- | --- |
|
||||
| 局部空快照、失败、半页结果 | 不删除历史;错误和覆盖范围明确 |
|
||||
| 真实完整空目录 | 正确显示该授权范围为空,不推导历史消息已删除 |
|
||||
| ACK 丢失、重复批次、乱序/跳号 | 不重复入库,不跨缺口确认,相同 ID 异载荷拒绝 |
|
||||
| 入库前/提交后崩溃、重启 | 未提交可重发;已提交可恢复 ACK |
|
||||
| 初始化期间新增、迟到同时间记录、新分片 | 边界不漏,稳定分页,补查可去重 |
|
||||
| 源端不再保留离线期间数据 | 标记缺口,不报告全部追平 |
|
||||
| 账号切换、同名会话、节点迁移、旧源重连 | 不串库、不猜测身份、不允许旧代次写入 |
|
||||
| 撤销授权、旧策略重放、授权到期 | 拒收/拒查,预览/附件/旧结果不得绕过 |
|
||||
| 队列满、磁盘满、SQLite busy、损坏库 | 明确错误与背压,单账号故障隔离,无假 ACK |
|
||||
| 备份恢复、schema 升级失败、平台游标回退 | 可诊断恢复和对账,不静默跳过缺失数据 |
|
||||
| 历史回填重放 | 不重复触发 AI、通知或任何发送动作 |
|
||||
|
||||
### 10.2 初始性能目标(目标,不是现有承诺)
|
||||
|
||||
- 平台单页读取最多 100 条,预热后 API P95 ≤ 300ms;记录冷读表现,不隐藏慢请求。
|
||||
- 参考合成负载:100 个账号库、每库 10 万条小文本消息、20 个并发读取客户端、合计每秒 20 条增量写入;记录 CPU、内存、磁盘、正文大小与索引占用。该负载不是支持上限声明。
|
||||
- 健康网络/来源下:建议 Agent 初始增量间隔 2–5 秒,页面活跃轮询约 2 秒;真机“Agent 首次观察到消息 → 页面可见”P95 目标 ≤ 10 秒,后续按实测调整。
|
||||
- 另行测量“微信源端提交/测试发送 → 首次观察”的延迟,不能只测上报阶段来声称端到端实时。跨机器计时注明时钟偏差;恢复/历史回填不混入正常实时指标。
|
||||
- 必备指标:最后成功扫描/入库时间、待发条数与字节、最老积压年龄、每阶段延迟、每库大小、WAL 大小、连接数、锁等待和错误计数。
|
||||
|
||||
### 10.3 执行与证据
|
||||
|
||||
Linux:Core/Service 相关测试、Go 测试、前端现有测试/构建、solution Release build;构建与会修改相同产物的测试顺序执行,避免文件锁冲突。文档提交本身只需链接及 diff 检查。
|
||||
|
||||
Windows:开发实施后在 `10.1.1.101` 的正常登录会话验证,使用 `C:\Users\Rogee\wx-agent01` 的部署/配置约定,GUI 配置不新增命令行修改入口。每次发布执行 `doctor`、脱敏 `inspect-ui`、对应 smoke;只使用项目允许的文件传输助手和测试联系人/群组,不盲目重试发送,不绕过登录或锁屏。
|
||||
|
||||
本轮已执行“切换页面、短时断连补传、进程重启恢复、授权撤销”的最小真机检查;原始脱敏计数见 [`WxAgent-会话消息同步-P4-live-evidence.json`](validation/WxAgent-会话消息同步-P4-live-evidence.json)。[PENDING](PENDING.md) 中的长时间监听、20 次重启、全版本/DPI、断电和多控制面 HA 仍是后续专项,不在本轮 P0–P4 结论中冒充通过。
|
||||
|
||||
证据保存至 `docs/validation/` 下的专项记录,包含代码版本、Windows/微信版本、样本范围、执行时间、计数、延迟和失败诊断;不提交数据库、密钥、真实正文或运行时控制面 JSON。
|
||||
|
||||
## 11. 迁移与回退
|
||||
|
||||
1. 先加存储和契约,不删除现有 API;新能力未通过验收前不自动激活。
|
||||
2. 对已验证授权账号做影子同步与样本对账;旧任务结果含 UI 局部身份,不默认批量导入新历史库。
|
||||
3. 分账号切换页面查询路径;中心账号路由与数据 schema 版本可审计。
|
||||
4. 回退时先停新同步写入,再切回兼容读取路径,保留已生成账号库和备份;回退不放宽权限或将缺失数据显示成完整。
|
||||
5. 不让旧版本进程读写未知 schema;恢复备份后重新检查当前授权及采集源代次,再补数据,防止复活已撤销访问。
|
||||
|
||||
## 12. 进入实现前需冻结的事项
|
||||
|
||||
以下事项已在本轮记录,后续运维仍需持续复核:
|
||||
|
||||
- P1:跨设备稳定账号证据、Go SQLite 驱动、schema/协议版本、授权有效期及撤销策略已冻结;物理删除/备份过期继续按运维窗口执行。
|
||||
- P2:会话数据源、分片与 ID 稳定性、WAL 可见性、迟到/旧记录修改覆盖范围已在白名单账号上验证;超出授权范围仍显式标记缺口。
|
||||
- P4:保留期、磁盘预算、备份恢复窗口、压力基线和性能结果见 `docs/validation/`;生产大容量、断电和 HA 需要单独的后续环境。
|
||||
|
||||
**最终交付标准:页面可用性与微信现场操作解耦;授权范围明确;新鲜度和缺口可见;初始化、重放、断连和账号切换可验证,而不只是“成功读到一次”。**
|
||||
@@ -6,6 +6,10 @@
|
||||
>
|
||||
> 当前入口约束:Windows Agent 只允许双击 `WxAgent.Tray.exe`,本机配置通过“服务设置...”窗口完成;`WxAgent.Host` 仅保留只读/诊断 CLI,任何命令行配置修改均禁用。本文中“本地 CLI 配置”属于历史计划措辞,不是当前可执行入口。
|
||||
|
||||
## 后续专项计划
|
||||
|
||||
- [会话消息同步与分账号存储开发计划](WxAgent-会话消息同步与分账号存储开发计划.md):已完成“页面读取平台副本、Agent 后台增量同步、小中心目录与每账号 SQLite”的 P0–P4 实现及白名单真机验收;长期 endurance、断电和多控制面 HA 仍按专项记录。继续保持本地白名单、账号隔离、不上传完整微信数据库和不提供跨账号聚合视图的边界。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
在不增加本地 Web UI 和 MCP 的前提下,实现多个 Windows 微信节点的统一远程管理:
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"captured_at": "2026-09-22T09:40:00+08:00",
|
||||
"source_revision": "ab9ff38",
|
||||
"artifacts": {
|
||||
"control_plane_sha256": "f7695475efcf6742a2c6d8e7355029916093ad03c93fa3127c6e0534d8487fb2",
|
||||
"tray_sha256": "f649cb55335e668850769e9c13e4bea5add5839e7a4a08179ede4c8895ee2bf7"
|
||||
},
|
||||
"account": {
|
||||
"account_id_prefix": "a2e8a1ea",
|
||||
"authorized_chat_count": 2,
|
||||
"raw_message_content_included": false,
|
||||
"database_keys_included": false
|
||||
},
|
||||
"offline_replay": {
|
||||
"control_plane_unreachable": true,
|
||||
"agent_process_session": 1,
|
||||
"queue_pending_while_unreachable": 1,
|
||||
"queue_bytes_while_unreachable": 3317,
|
||||
"control_plane_restarted": true,
|
||||
"queue_pending_after_reconnect": 0,
|
||||
"node_status_after_reconnect": "Online",
|
||||
"messages_before_replay": 458,
|
||||
"messages_after_replay": 459,
|
||||
"confirmed_batches_after_replay": 5,
|
||||
"coverage_after_replay": "complete"
|
||||
},
|
||||
"agent_restart_recovery": {
|
||||
"messages_before": 459,
|
||||
"batches_before": 5,
|
||||
"confirmed_sequence_before": 5,
|
||||
"tray_stopped_seconds": 15,
|
||||
"tray_restarted_session": 1,
|
||||
"node_status_after_restart": "Online",
|
||||
"messages_after": 459,
|
||||
"batches_after": 5,
|
||||
"confirmed_sequence_after": 5,
|
||||
"duplicate_batch_observed": false
|
||||
},
|
||||
"control_plane_crash_recovery": {
|
||||
"termination": "SIGKILL",
|
||||
"integrity_before_restart": "ok",
|
||||
"messages_before_restart": 460,
|
||||
"batches_before_restart": 6,
|
||||
"confirmed_sequence_before_restart": 6,
|
||||
"integrity_after_restart": "ok",
|
||||
"node_status_after_restart": "Online",
|
||||
"messages_after_restart": 460,
|
||||
"batches_after_restart": 6,
|
||||
"coverage_after_restart": "complete"
|
||||
},
|
||||
"authorization_revoke": {
|
||||
"revoke_http_status": 200,
|
||||
"conversations_http_status_while_revoked": 403,
|
||||
"messages_http_status_while_revoked": 403,
|
||||
"sync_status_http_status_while_revoked": 200,
|
||||
"authorized_test_send_exit": 0,
|
||||
"pending_batches_after_revoked_send": 1,
|
||||
"agent_rejection_http_status": 403,
|
||||
"agent_rejection_code": "AccountNotAuthorized",
|
||||
"restored_by_agent_reregistration": true,
|
||||
"node_status_after_restore": "Online",
|
||||
"messages_after_restore": 460,
|
||||
"confirmed_batches_after_restore": 6,
|
||||
"coverage_after_restore": "complete",
|
||||
"conversations_http_status_after_restore": 200,
|
||||
"messages_http_status_after_restore": 200
|
||||
},
|
||||
"windows": {
|
||||
"session": 1,
|
||||
"interactive": true,
|
||||
"doctor_exit": 0,
|
||||
"inspect_ui_exit": 0,
|
||||
"smoke_exit": 0,
|
||||
"wechat_version": "4.1.13.65",
|
||||
"windows_build": "10.0.19044.0",
|
||||
"ui_tree_nodes": 166,
|
||||
"scope": "File Transfer Assistant and approved test chats only",
|
||||
"stability_smoke": {
|
||||
"exit": 0,
|
||||
"success": true,
|
||||
"healthy": true,
|
||||
"sample_count": 7,
|
||||
"message_events": 0,
|
||||
"reconnect_events": 0,
|
||||
"working_set_growth_bytes": 1273856,
|
||||
"handle_growth": 12,
|
||||
"thread_growth": 2
|
||||
}
|
||||
},
|
||||
"notes": [
|
||||
"The offline batch was observed in the durable queue before the control plane was restarted and drained after reconnect.",
|
||||
"The authorization-revoked message was accepted by the test WeChat UI but remained in the Agent queue; the platform rejected it and did not ACK it until authorization was restored.",
|
||||
"Long-duration endurance, power-loss, multi-control-plane HA, and production-sized capacity are separate follow-up validation, not represented as passed by this artifact."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
# 会话消息同步与分账号存储:P4 验收记录
|
||||
|
||||
> 验收日期:2026-09-22(+08:00)
|
||||
> 源码基线:`ab9ff38`(实现变更已在本地提交)
|
||||
> 控制面验收二进制:`/tmp/wxagent-control-plane-final5`,SHA-256:`f7695475efcf6742a2c6d8e7355029916093ad03c93fa3127c6e0534d8487fb2`
|
||||
> Tray 验收二进制:`/tmp/wxagent-tray-publish-final8b/WxAgent.Tray.exe`,SHA-256:`f649cb55335e668850769e9c13e4bea5add5839e7a4a08179ede4c8895ee2bf7`
|
||||
|
||||
## 1. 自动化回归
|
||||
|
||||
| 范围 | 结果 |
|
||||
| --- | --- |
|
||||
| Web 单元测试 | 4/4 通过 |
|
||||
| Web Vite 构建 | 通过 |
|
||||
| Go `go test ./...` | 通过 |
|
||||
| `WxAgent.Core.Tests` | 173/173 通过 |
|
||||
| `WxAgent.Service.Tests` | 25/25 通过 |
|
||||
| 完整 .NET Release 构建 | 0 警告、0 错误 |
|
||||
| `git diff --check` | 通过 |
|
||||
|
||||
专项 Go 测试覆盖:
|
||||
|
||||
- 保留期清理:旧消息、无保留消息的会话预览和已确认批次可清理。
|
||||
- 容量预算:批次/账号分片超限返回 `ErrAccountCapacityExceeded`,HTTP 映射为 507。
|
||||
- WAL checkpoint/optimize:维护任务逐账号执行,坏分片错误隔离。
|
||||
- 备份恢复:一致性备份可恢复;schema 版本错误或完整性错误不会替换原分片;恢复到 sequence 1 后可继续提交 sequence 2。
|
||||
- 授权撤销:撤销 scope 后缓存消息查询拒绝,平台查询不会继续暴露历史正文。
|
||||
- 游标/幂等:重复批次、序列冲突、源代次变化和 ACK 状态已有回归覆盖。
|
||||
- 现有分片迁移:重新打开旧分片时补建消息观察时间和会话活动索引。
|
||||
|
||||
## 2. 合成压力
|
||||
|
||||
`TestAccountStoreSyntheticConcurrentReadWrite`:
|
||||
|
||||
- 8 个账号、800 条合成消息、16,000 次并发读取。
|
||||
- 总耗时约 47.8 ms,最慢账号约 44.6 ms。
|
||||
- 账号 scope 隔离和分片查询均通过。
|
||||
|
||||
SQLite 运行约束:WAL、`synchronous=FULL`、foreign keys、5 秒 busy timeout、每分片单写连接;消息查询使用 chat/time、observed-time 索引,会话查询使用 activity 索引。
|
||||
|
||||
## 3. Windows 交互桌面真机
|
||||
|
||||
通过 Windows UI 将微信切换到白名单测试群“消息测试专用群组”,再以交互 Session 1 执行:
|
||||
|
||||
- `doctor=0`
|
||||
- `inspect-ui=0`
|
||||
- `smoke=0`
|
||||
- `session_id=1`, `interactive=True`
|
||||
- WeChat `4.1.13.65`,Windows build `10.0.19044.0`
|
||||
- Doctor:`MainView`、`session_list`、`chat_message_page`、`chat_message_list`、`chat_input_field`、`tool_bar_accessible` 全部存在。
|
||||
- UI 快照 166 个节点,已脱敏;smoke 发送确认目标为 File Transfer Assistant,未启用 Web 发送。
|
||||
|
||||
非交互 SSH Session 0 的失败结果不作为真机验收结果;它只证明 Session 0 必须拒绝 UIA 操作。
|
||||
|
||||
## 4. 真机同步、断线补传、进程重启与授权撤销
|
||||
|
||||
完整、脱敏的事件计数见 [`WxAgent-会话消息同步-P4-live-evidence.json`](WxAgent-会话消息同步-P4-live-evidence.json)。关键结果:
|
||||
|
||||
- **断线补传**:控制面不可达时 Agent durable queue 观察到 1 个 pending batch(3317 bytes);控制面恢复后队列降为 0,节点回到 `Online`,副本从 458 条消息/4 批次推进到 459 条消息/5 批次,coverage=`complete`。
|
||||
- **Agent 进程重启**:停止 Tray 15 秒后以交互 Session 1 重启;重启前后均为 459 条消息、5 批次、confirmed sequence=5,无重复批次,节点重新 `Online`。
|
||||
- **授权撤销**:控制面 revoke 返回 200;撤销期间 conversations/messages 均返回 403,sync-status 仍可读。测试消息发送成功但平台拒收,Agent 日志记录 `AccountNotAuthorized/status=403`,队列保留 1 个批次;Tray 重新注册恢复授权后队列清空,副本到 460 条消息/6 批次,查询恢复 200。
|
||||
- **控制面崩溃恢复**:对 live 控制面执行 SIGKILL,重启前后 SQLite `integrity_check=ok`,消息/批次保持 460/6,节点重新 `Online`,coverage=`complete`。
|
||||
- **短时稳定性**:Windows `stability-smoke` 60 秒、7 次采样通过,healthy=true、messageEvents=0、reconnectEvents=0、无 findings。
|
||||
- **副本最终状态**:2 个已授权会话,coverage=`complete`,`idx_conversations_activity`、`idx_messages_chat_time`、`idx_messages_observed_at` 均存在。
|
||||
|
||||
测试机 `service.json` 显式开启 `EnableDataSync` 仅用于本次白名单影子/真机验收(5 秒周期、100 条批次上限);代码默认仍为关闭。Reporting 白名单仍只包含文件传输助手、Hao 豪、吉祥三宝和消息测试专用群组范围。
|
||||
|
||||
## 5. Web 平台副本验收
|
||||
|
||||
使用 Browser Harness 登录控制面后,页面默认读取 `/v1/data/accounts/{id}` 平台副本:
|
||||
|
||||
- 显示两个会话及平台同步状态。
|
||||
- 打开消息测试专用群组后显示历史消息。
|
||||
- 页面显示 complete/freshness 信息;backlog 无 Agent 队列上报时保持未知,不伪造为 0。
|
||||
- 旧 `/v1/reads/*` 路径仍存在,作为诊断/回退;发送仍独立禁用。
|
||||
|
||||
## 6. 本轮边界与后续运维专项
|
||||
|
||||
本轮 P0–P4 的目标是单控制面、本机 SQLite、一个已验证白名单账号的可恢复同步和 Web 切换;下列项目不属于本阶段部署边界,不能被本记录误读为已承诺的生产能力:
|
||||
|
||||
- 24 小时/7 天长期稳定性和大规模生产账号容量曲线。
|
||||
- 生产环境硬杀进程/断电期间的 WAL 恢复演练。
|
||||
- 多控制面高可用、跨节点故障转移和生产网络分区长时间补传。
|
||||
- 所有微信版本、所有数据库分片布局及 page 1 HMAC 失败样本的真机矩阵。
|
||||
- 生产备份介质上的异机恢复与定期恢复演练。
|
||||
|
||||
这些项目已列为后续运维/发布专项;本轮已完成授权测试账号的影子同步、断线补传、进程重启恢复、授权撤销和平台读取切换。旧 `/v1/reads/*` 仍保留,其他账号仍保持默认关闭,避免把本轮单账号证据扩大成生产容量承诺。
|
||||
Reference in New Issue
Block a user