docs(desktop-client): revise spec v2 with triple-review fixes
Address 6 Critical + 10 Warning findings from Claude + Codex + Gemini cross-review: - C1 Replace Network.responseReceivedExtraInfo path with EventSource / Fetch.takeResponseBodyAsStream routing per transport type; add Electron-Chromium CDP compatibility matrix commitment. - C2 Default patch channel to pure-data DSL (no JS exec); executable fallback gated on utilityProcess + isolated-vm sandbox with frozen globals. Full TUF-style signing chain (patch_seq, hash, expires_at, key_id rotation, revokes, runtime hash re-verify). - C3 Add RabbitMQ task_available fanout + Redis presence registry; SSE only pushes signals, task ownership decided atomically by lease endpoint. - C4 platform_accounts gains platform_uid + account_fingerprint + UNIQUE(platform, platform_uid) enforcing workspace-exclusive binding in DB, not by client discipline. - C5 Task protocol adds attempt_id + lease_token with extend/pause/resume/cancel endpoints; manual mode enters waiting_user status with extended lease; 409 on stale lease_token. - C6 Rewrite scope estimate with 7 detect-only publish adapters and DeepSeek monitor placeholder called out explicitly; reshape as 9-bucket 22-26 week effort; introduce Plan A/B/C split. Warnings addressed: BrowserView->WebContentsView everywhere; allowDowngrade spelling fix; client_token decoupled from client_version with rotate/revoke APIs; safeStorage Linux basic_text guard + separate vault-export for machine migration; keep-alive token bucket + 429 backoff + resume warm-up; packages/ui-shared carve-out (no admin-web cloning); state machine gets failed/aborted/unknown terminal states; sqlc regeneration footprint made explicit; crash recovery split by task.kind; autoUpdater unit tests + pre-release smoke; trace phased (Alpha=logs+shots, Phase 2=Playwright-compat export). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
# Electron 桌面客户端设计(全面替换 WXT 浏览器插件)
|
||||
|
||||
**状态**:Draft · 待实施
|
||||
**作者**:@liangxu + Claude
|
||||
**状态**:Draft v2 · 待实施(v1 经 Claude + Codex + Gemini 三方交叉审查后修订)
|
||||
**作者**:@liangxu + Claude(codex/gemini 交叉审校)
|
||||
**日期**:2026-04-18
|
||||
**范围**:新增 `apps/desktop-client/`、`/api/desktop/*` 服务端路由、相关 DB schema;删除 `apps/browser-extension/` 与 `/api/plugin/*` 路由及相关代码。
|
||||
|
||||
> **v2 主要修订**:修正 CDP 流式抓取方法(C1)、Patch 通道沙箱机制(C2)、SSE 多实例协调(C3)、workspace 独占 DB 落地(C4)、任务租约协议(C5)、老适配器工作量重估(C6);以及 12 条 Warning 逐项落入文档。详见 §13 变更记录。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机
|
||||
@@ -19,6 +21,7 @@
|
||||
- **LLM 抓取不稳定**。监控模块依赖 DOM 选择器与 `webRequest.onBeforeRequest` 在 MV3 下受限,无法可靠拿到 SSE 响应 Body;平台 UI 一变就挂。
|
||||
- **适配器受 MV3 约束**。后台 service worker 频繁休眠,长任务难以保证完成;跨平台复杂登录(QR、滑块、SMS)在扩展内体验差。
|
||||
- **不可扩展到真正的系统级自动化**。系统托盘、开机自启、多账号并发 session 隔离、进程级 CDP 调试,这些 MV3 都做不到。
|
||||
- **现有适配器实现覆盖不全**。仓库实测:发布侧 13 家里 `weixin_gzh / juejin / dongchedi / smzdm / wangyihao / bilibili / zol` 共 **7 家** 仍返回 `unsupportedPublishResult`(detect-only 占位);监控侧 DeepSeek 是 `defaultAdapter` 占位没实现 query。这部分要**从零实现**,不是迁移。
|
||||
|
||||
因此:**全面替换**为一个 Electron 桌面客户端,承担上述全部职责。浏览器扩展停止维护并下线。
|
||||
|
||||
@@ -29,11 +32,11 @@
|
||||
1. **全面替换**,不做"先 monitoring 后 publishing"的分阶段替换。
|
||||
2. **不做向后兼容**。因为是开发版本阶段,服务端协议、DB schema、认证字段按桌面客户端的最佳形态重新设计,不为插件保留旧字段或旧路由。
|
||||
3. **登录走嵌入式 Web View**。用户在 Electron 真实平台登录页里自己完成扫码/密码/短信/滑块;我们只保存 cookie,不保存账号密码。
|
||||
4. **Cookie 本地存储 + OS 级加密**(Electron `safeStorage`,macOS Keychain / Windows DPAPI / Linux libsecret)。服务端**永不存储** Cookie 或任何平台凭据。
|
||||
4. **Cookie 本地存储 + OS 级加密**(Electron `safeStorage`,macOS Keychain / Windows DPAPI / Linux libsecret)。服务端**永不存储** Cookie 或任何平台凭据。Linux 上 `safeStorage` 可能退化为 `basic_text`,必须启动时检测并显式降级告警(见 §4.5)。
|
||||
5. **LLM 监控一律走网页 + CDP 拦截**,不调用任何 LLM 官方 API(成本导向;多账号 = 多配额,线性成本比 token 计费友好)。后端 `llm.api_key` 是后端自用管线,与客户端监控完全解耦。
|
||||
6. **不打包 Playwright**。复用 Electron 自带 Chromium,通过 `webContents.debugger` 直接用 CDP。包体越小越好。
|
||||
7. **支持 5 目标**:Windows x64/arm64、macOS Intel+Apple Silicon、Linux x64/arm64。
|
||||
8. **一个微信号只给一个 workspace**——不支持跨租户共享账号。
|
||||
8. **一个平台账号只绑定一个 workspace**(不支持跨租户共享账号)。该约束必须落到 DB 唯一约束与 API 级冲突校验,不依赖客户端自觉(见 §6.6 `platform_accounts` 的 `platform_uid` 字段 + 唯一索引)。
|
||||
|
||||
---
|
||||
|
||||
@@ -41,12 +44,14 @@
|
||||
|
||||
### 3.1 进程拓扑
|
||||
|
||||
单 Electron 进程树(方案 A)。
|
||||
单 Electron 进程树(方案 A)。所有视图宿主统一用 `WebContentsView`(Electron 已把 `BrowserView` 标记为 deprecated)。
|
||||
|
||||
- **Main 进程**(Node):任务调度器、Session 注册表、加密凭据仓库、CDP debugger 管理、与后端 REST/SSE 通信、Tray & autoUpdater。
|
||||
- **Renderer 进程**(托盘窗口 / 发布预览窗口):Vue UI,复用 `apps/admin-web` 的组件库与 i18n。禁用 `nodeIntegration`,通过 `contextBridge` 暴露窄接口。
|
||||
- **工作 BrowserView**:每个登录账号一个 `persist:<account-id>` session + 一个隐藏 `BrowserView`,挂在离屏 `BrowserWindow` 上。常驻但不可见;需要操作时 `webContents.debugger.attach()`。
|
||||
- 只有当实测 CDP 流式抓取会让 UI 卡顿时,才把 debugger 交互迁到 `utilityProcess`,其他模块保持不动。
|
||||
- **Main 进程**(Node):任务调度器、Session 注册表、加密凭据仓库、CDP debugger 管理、租约状态机、与后端 REST/SSE 通信、Tray & autoUpdater。
|
||||
- **Renderer 进程**(托盘窗口 / 登录窗口 / 发布预览窗口):Vue UI,基于 `packages/ui-shared`(见 §3.3)。禁用 `nodeIntegration`,通过 `contextBridge` 暴露窄接口。
|
||||
- **Utility 进程**:为 Patch 可执行代码沙箱化而预留(见 §5.5)。`utilityProcess` 不开 Node 能力,仅以 IPC 与主进程通信。
|
||||
- **工作 WebContentsView**:每个登录账号一个 `persist:<account-id>` session,挂在离屏 `BaseWindow` 上。
|
||||
- **生命周期**:**两档**。Hot-tier(最近 N 分钟有任务或保活的账号)常驻 WebContentsView;Cold-tier 账号只保留 session partition 在磁盘,WebContentsView lazy-create。阈值默认 30 min,可配置。避免 10 账号场景下 RAM 被固定占用。
|
||||
- 仅在实测 CDP 抓流阻塞 UI 线程时,才把 debugger 交互迁到 `utilityProcess`,其他模块保持不动。
|
||||
|
||||
### 3.2 模块分层
|
||||
|
||||
@@ -56,42 +61,50 @@
|
||||
apps/desktop-client/
|
||||
main/
|
||||
bootstrap.ts # app ready, single-instance-lock, tray, autoUpdater
|
||||
session-registry.ts # account-id → BrowserView + session
|
||||
vault.ts # safeStorage 包 cookie/token,SQLite 持久化
|
||||
scheduler.ts # 本地 cron + 服务端 task lease 合流
|
||||
session-registry.ts # account-id → (WebContentsView|null) + session
|
||||
view-pool.ts # Hot/Cold 两档 WebContentsView 生命周期
|
||||
vault.ts # safeStorage 包 cookie/token + SQLite(本机恢复缓存)
|
||||
vault-export.ts # 用户交互式导出(口令二次加密)用于手工迁机器
|
||||
scheduler.ts # 本地 cron + 服务端 SSE/pull 合流
|
||||
lease-manager.ts # 任务租约:ack→attempt_id+lease_token→extend/cancel/result
|
||||
rate-limiter.ts # 每平台/账号 token bucket,共享 business/keep-alive/probe 预算
|
||||
circuit-breaker.ts # 平台级熔断
|
||||
keep-alive.ts # 探活 / 保活调度
|
||||
crash-recovery.ts # 启动时扫描 in-flight 任务
|
||||
keep-alive.ts # 探活 / 保活 / resume warm-up 错峰
|
||||
crash-recovery.ts # 启动时按 task.kind 分派恢复策略
|
||||
patch-loader/
|
||||
registry.ts # 已加载 patch 版本表
|
||||
signer.ts # manifest + Ed25519 + hash + 回滚序号校验
|
||||
parser-dsl.ts # 受限 DSL 解释器(默认通道)
|
||||
vm-host.ts # utilityProcess 沙箱启动 + 冻结 global(备用通道)
|
||||
tracing/
|
||||
recorder.ts # 启停;订阅 CDP Page/Network/Fetch/Runtime;采样截屏
|
||||
trace-writer.ts # 流式写入 .trace zip
|
||||
recorder.ts # 结构化日志 + 关键截屏(Alpha 范围)
|
||||
trace-writer.ts # 轻量 zip(Alpha 仅 JSON 日志 + 截屏)
|
||||
redactor.ts # Cookie / Authorization / password 字段自动脱敏
|
||||
viewer-assets/ # 内置 trace viewer(Vue 单页,离线)
|
||||
# Playwright 兼容导出作为 Phase 2 能力,见 §5.6
|
||||
transport/
|
||||
api-client.ts # 复用 packages/http-client,注入 client_token
|
||||
sse-client.ts # /api/desktop/events SSE 长连接 + pull 兜底
|
||||
adapters/
|
||||
_shared/
|
||||
debugger-helper.ts
|
||||
dom-helper.ts
|
||||
upload-helper.ts
|
||||
captcha-pause.ts
|
||||
selectors.json # 可热更的选择器表
|
||||
base.ts # PublishAdapter / MonitorAdapter 接口
|
||||
wechat.ts ... smzdm.ts # 13 个发布适配器
|
||||
doubao.ts qwen.ts deepseek.ts # 3 个 LLM 监控适配器
|
||||
index.ts # 注册表
|
||||
sse-client.ts # /api/desktop/events 长连接 + 60s pull 兜底
|
||||
preload/
|
||||
bridge.ts # 暴露给 renderer 的受控 API
|
||||
renderer/ # 复用 apps/admin-web,打 Electron 专属 build
|
||||
bridge.ts # contextBridge 窄 API
|
||||
renderer/ # 极简 Vite 项目,仅消费 packages/ui-shared
|
||||
main.ts
|
||||
routes.ts
|
||||
views/ # 桌面端专属视图(账号管理/任务中心/事件中心/诊断)
|
||||
```
|
||||
|
||||
### 3.3 关键复用
|
||||
### 3.3 关键复用(更务实的口径)
|
||||
|
||||
- `packages/shared-types`:平台枚举、任务类型、DTO。
|
||||
- `packages/http-client`:HTTP 基座。
|
||||
- `apps/admin-web` 的 Vue 组件与路由:删掉 SaaS 专属页面后作为 renderer 的基座。
|
||||
- `apps/browser-extension/src/adapters/*` 的选择器和工作流:批量搬进来,套一层 `runInSession(accountId, fn)` 即可复用。
|
||||
**可复用**:
|
||||
- `packages/shared-types`:平台枚举、任务类型、DTO。需要**扩展**:加入租约、platform_uid、patch manifest 等新类型。
|
||||
- `packages/http-client`:HTTP 基座直接用。
|
||||
- **新建 `packages/ui-shared`**:从 `apps/admin-web` 抽出**通用组件、i18n、样式、utils**。admin-web 与 desktop-client 两边都只消费共享包。Renderer 是一个极简 Vite 项目,不是克隆+删页面。
|
||||
- 老适配器里的**选择器字符串**和**业务流程描述**("打开编辑器 → 填标题 → 粘正文 → 上传封面 → 点发布"这种语义)。
|
||||
|
||||
**不可复用、必须重写或新建**:
|
||||
- 所有适配器代码——老代码硬编码 `chrome.tabs / chrome.storage / chrome.runtime / chrome.webRequest / wxt` 框架依赖,全部换成 Electron `session / WebContentsView / webContents.debugger / Fetch domain`。
|
||||
- **发布适配器中 7 家 detect-only 的平台**(微信公众号、掘金、懂车帝、什么值得买、网易号、B 站、ZOL)**需从零实现 publish**。
|
||||
- **DeepSeek 监控**需从零实现 query。
|
||||
- Trace 基建、Patch 通道、租约管理器、LeaseManager + sandbox-loader、ui-shared 抽取——全部新建。
|
||||
|
||||
---
|
||||
|
||||
@@ -103,11 +116,15 @@ apps/desktop-client/
|
||||
type Account = {
|
||||
id: string // uuid,本地生成
|
||||
platform: PlatformKind // 'wechat' | 'toutiao' | ... | 'doubao' | 'qwen' | 'deepseek'
|
||||
platformUid?: string // 平台侧真实账号 ID / OpenID / UIN;登录探针抓取
|
||||
accountFingerprint?: string // 额外防伪:昵称/手机号尾号/账号主页 URL hash
|
||||
purpose: 'publish' | 'monitor'
|
||||
displayName: string
|
||||
displayName: string // 用户自填,仅作本地展示
|
||||
partitionKey: string // 'persist:acc-<id>',Electron session 隔离
|
||||
proxy?: ProxyConfig // 可选 session-level 代理
|
||||
createdAt: Date
|
||||
lastSeenAt: Date
|
||||
verifiedAt?: Date // 上次成功 probe 的时间
|
||||
health: 'live' | 'expired' | 'captcha' | 'risk'
|
||||
tenantId: string
|
||||
workspaceId: string
|
||||
@@ -120,11 +137,18 @@ type Account = {
|
||||
failureStreak: number
|
||||
}
|
||||
quota: {
|
||||
hourlyBudget: number
|
||||
hourlyBudget: number // 默认 60/小时,含业务+保活+探活合计
|
||||
usedThisHour: number
|
||||
resetAt: Date
|
||||
}
|
||||
}
|
||||
|
||||
type ProxyConfig = {
|
||||
scheme: 'http' | 'socks5'
|
||||
host: string
|
||||
port: number
|
||||
auth?: { user: string; pass: string } // 存入 vault,不明文落盘
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 登录流程
|
||||
@@ -132,13 +156,15 @@ type Account = {
|
||||
1. 用户在 UI 点「新增账号 → 选平台」。
|
||||
2. 主进程 `session-registry.create(platform)` 分配新 partition,打开一个**可见** `BrowserWindow`(登录专用),加载该平台登录页。
|
||||
3. 用户自己用扫码 / 密码 / 短信 / 滑块登录。
|
||||
4. 登录探针(preload 脚本 + URL 规则)检测到已登录态后关闭窗口。
|
||||
5. Electron 自动把 cookies 持久化到 `userData/Partitions/<key>/`;同时主进程读取 cookies 用 `safeStorage` 加密写本地 SQLite 作为元数据 + 加密快照(用于迁机器、备份、健康检查)。
|
||||
6. 账号进入"常驻池",分配隐藏 BrowserView 挂在离屏窗口;按需激活 CDP。
|
||||
4. 登录探针(preload 脚本 + URL 规则 + 成功态 DOM 标记)检测到已登录态后,在窗口顶部叠加"登录成功"遮罩层 1.5 s,然后关窗。
|
||||
5. 探针在关窗前抓取平台侧 `platformUid`(如微信公众号的 `fakeid` / B 站的 `mid` / 知乎的 `hash_id`),写入 `Account.platformUid`。
|
||||
6. Electron 自动把 cookies 持久化到 `userData/Partitions/<key>/`;同时主进程用 `safeStorage` 加密写本地 SQLite 作为**本机恢复缓存**(跨账号解绑、重装应用时 restore 用;**非迁机器备份**)。
|
||||
7. 启动时强制 `safeStorage.isEncryptionAvailable()`;若在 Linux 上 `safeStorage.getSelectedStorageBackend()` 返回 `basic_text`,UI 显著告警、默认关闭加密快照(避免把平台 cookie 明文写盘)。
|
||||
8. 账号进入"常驻池",hot-tier 分配 WebContentsView,cold-tier 仅保留 partition;按需激活 CDP。
|
||||
|
||||
### 4.3 多账号并存
|
||||
|
||||
同一平台多账号完全隔离——partition 不同、cookies 不同、UA 不同、可通过 session 级 proxy 走不同 IP。调度器按账号维度限流(默认同一账号并发 1、同一平台并发 3,可配置)。
|
||||
同一平台多账号完全隔离——partition 不同、cookies 不同、UA 不同、可通过 session 级 proxy 走不同 IP。代理来自 `Account.proxy` 字段,由 `_shared/proxy-helper.ts` 统一调用 `ses.setProxy`(§5.4)。调度器按账号维度限流(默认同一账号并发 1、同一平台并发 3,可配置)。
|
||||
|
||||
### 4.4 Cookie/Session 保活策略
|
||||
|
||||
@@ -159,19 +185,22 @@ type Account = {
|
||||
|
||||
实施细节:
|
||||
|
||||
- **错峰**:同一平台 N 个账号按 `hash(accountId) mod period` 分布时间槽 + ±30s 抖动。
|
||||
- **优先级**:业务任务 > 保活 > 探活。
|
||||
- **错峰**:同一平台 N 个账号按 `hash(accountId) mod period` 分布时间槽 + ±30s 抖动;当 N > 槽数时使用随机顺延。
|
||||
- **统一 token bucket**:每个 `(platform, account)` 维度一只 leaky bucket,桶容量 = `hourlyBudget`。**业务任务、保活、探活同桶扣减**——避免保活把业务流量配额吃掉或反之。
|
||||
- **优先级**:业务任务 > 保活 > 探活;同槽冲突由优先级决定是否放行。
|
||||
- **失败升级**:保活失败 1 次立即重试;连 2 次失败降级为探活一次确认状态;探活失败则 `health: expired`,托盘红点 + OS 通知 → 用户点击弹登录窗(partition 复用,不需要重建账号)。
|
||||
- **静音时段**:默认 0:00–7:00 不保活(可配置关闭)。
|
||||
- **休眠感知**:`powerMonitor.on('suspend'|'resume')`,休眠暂停全部保活,唤醒后补一次探活再恢复。
|
||||
- **429/验证码退避**:任何响应识别为 429/风控页/captcha 时,该账号进入**指数退避**(基础 30s,因子 2,上限 30 min),期间**完全暂停保活**,只保留"按需激活"(业务任务到来时才 pre-check)。
|
||||
- **静音时段**:默认 0:00–7:00(**用户系统本地时区**)不保活,可配置关闭。
|
||||
- **休眠感知**:`powerMonitor.on('suspend'|'resume')`,休眠暂停全部保活;**唤醒后错峰 warm-up**——按 `hash(accountId)` 分片,每 10s 一批探活(每批 ≤3 账号),避免同步流量尖峰。
|
||||
- **配额池**:同一 LLM 多账号时,调度器按"最近使用 + 剩余配额"挑账号。
|
||||
- **风控回避**:每账号"本小时平台请求数"超阈值就停保活。
|
||||
|
||||
### 4.5 凭据粒度
|
||||
### 4.5 凭据粒度与备份
|
||||
|
||||
- **不**保存账号密码。
|
||||
- 只保存 Cookie + 平台侧必要 token(如 Doubao 的 `ms_token`、`fp`)。
|
||||
- 重装客户端 → 从本地加密备份恢复,或重登一次。
|
||||
- **`safeStorage` + SQLite** 是**本机恢复缓存**:userData 被清/重装应用可恢复,不保证可迁机器。
|
||||
- **跨机器迁移**走独立的**用户交互式导出**流程(`vault-export.ts`):本机解密后用**用户输入的口令**二次加密导出为 `.vault` 文件;新机器同样输入口令解密、用新环境的 `safeStorage` 重加密落盘。导出的 `.vault` 文件不会自动上传服务端。
|
||||
- 服务端**永远不**持有任何形式的 cookie 备份(硬约束 4)。
|
||||
|
||||
---
|
||||
|
||||
@@ -183,17 +212,19 @@ type Account = {
|
||||
interface AdapterContext {
|
||||
accountId: string
|
||||
session: Session
|
||||
view: WebContentsView
|
||||
view: WebContentsView // 统一使用 WebContentsView
|
||||
debugger: DebuggerClient
|
||||
rateLimit: RateLimitHandle // 业务调用前 acquire
|
||||
logger: ScopedLogger
|
||||
signal: AbortSignal
|
||||
reportProgress: (stage: string) => void // 供状态机向 lease-manager 报进度
|
||||
}
|
||||
|
||||
interface PublishAdapter {
|
||||
platform: PlatformKind
|
||||
capabilities: { images: boolean; video: boolean; markdown: boolean; /* ... */ }
|
||||
probe(ctx: AdapterContext): Promise<HealthState>
|
||||
keepAlive(ctx: AdapterContext): Promise<void> // 默认 no-op
|
||||
keepAlive(ctx: AdapterContext): Promise<void> // 默认 no-op
|
||||
publish(ctx: AdapterContext, task: PublishTask): Promise<PublishResult>
|
||||
}
|
||||
|
||||
@@ -205,31 +236,60 @@ interface MonitorAdapter {
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 监控适配器标准套路
|
||||
### 5.2 监控适配器:流式 SSE 抓取技术路径(核心修正)
|
||||
|
||||
**不要用** `Network.responseReceivedExtraInfo + getResponseBody`——前者是响应头/Cookie 元信息,后者只能在响应整段收完后取完整 body,**对长连接 SSE 拿不到流式增量**。
|
||||
|
||||
**按传输类型分流**:
|
||||
|
||||
| 页面使用的机制 | 正确的 CDP 抓法 |
|
||||
|---|---|
|
||||
| `new EventSource(url)` | `Network.enable` → `Network.eventSourceMessageReceived` 监听每条 message |
|
||||
| `fetch(url)` + `text/event-stream` + streaming body | `Fetch.enable({patterns})` → `Fetch.requestPaused` → `Fetch.continueResponse` → `Fetch.takeResponseBodyAsStream` + `IO.read` 流式拼接 |
|
||||
| `fetch(url)` + chunked transfer + 自定义协议 | 同上(或 Chromium ≥ 120 的 `Network.streamResourceContent + Network.dataReceived`,**需先确认 Electron 打包的 Chromium 版本支持**)|
|
||||
|
||||
**Chromium 版本兼容矩阵**由 `docs/superpowers/specs/electron-chromium-cdp-matrix.md`(本 spec 落地后新建)维护;每次升 Electron 大版本检查一次上面这些 domain 是否仍可用。
|
||||
|
||||
实施步骤:
|
||||
|
||||
```
|
||||
1. ensureLoggedIn(ctx) → probe / 必要时触发保活
|
||||
2. openChat(ctx) → 在 BrowserView 里导航或复用 tab
|
||||
3. attach CDP Fetch+Network domain
|
||||
4. submit query → 触发网页发出请求
|
||||
5. collect SSE via CDP.Network.responseReceivedExtraInfo + getResponseBody
|
||||
6. parse provider-specific payload → normalized MonitorResult
|
||||
7. detach CDP(finally)
|
||||
1. ensureLoggedIn(ctx) → probe / 必要时触发保活
|
||||
2. openChat(ctx) → 在 WebContentsView 里导航或复用 tab
|
||||
3. detect protocol → 通过 DevTools Protocol 注入一小段探测脚本,返回 EventSource / fetch-stream
|
||||
4. attach CDP Fetch or Network → 按 §5.2 表格选 domain
|
||||
5. submit query → 触发网页发出请求
|
||||
6. collect streaming body → eventSourceMessageReceived 或 IO.read
|
||||
7. parse provider-specific payload → normalized MonitorResult
|
||||
8. detach CDP(finally)
|
||||
```
|
||||
|
||||
**核心价值**:步骤 5 抓拦截后的原始 API 响应 JSON/SSE,不依赖渲染后 DOM 文本。UI 改变不影响,只有 API schema 变更才需要更新 parser。
|
||||
**Provider-specific parser**:每家 LLM 的 payload schema 不同(豆包 `{event: 'data', data: {text: ...}}` 这种结构),放 `adapters/doubao.ts` 等私有文件。Schema 变化通过 Patch 通道下发新 `parser`(§5.5)。
|
||||
|
||||
### 5.3 发布适配器模式
|
||||
### 5.3 发布适配器状态机 + 租约绑定
|
||||
|
||||
每个发布任务或每个账号带 `PublishMode`:
|
||||
|
||||
```ts
|
||||
type PublishMode =
|
||||
| { kind: 'manual'; requireUserReview: true } // 填好表单后暂停,等用户点"发布"
|
||||
| { kind: 'auto' } // 一口气跑到底
|
||||
| { kind: 'manual'; requireUserReview: true }
|
||||
| { kind: 'auto' }
|
||||
|
||||
type PublishState =
|
||||
| 'filling' // adapter 正在往编辑器填内容
|
||||
| 'readyToReview' // manual 模式下等待用户点发布
|
||||
| 'submitting' // 已经点了发布按钮,等平台回应
|
||||
| 'done' // 平台明确返回成功
|
||||
| 'failed' // 明确失败(可重试)
|
||||
| 'aborted' // 用户或系统取消
|
||||
| 'unknown' // 崩溃/超时/无法判断(人工对账,见 §7.5)
|
||||
```
|
||||
|
||||
状态机:`filling → readyToReview → submitting → done`。manual 在 `readyToReview` 挂一个用户 gate,auto 不挂。同一份 adapter 代码两种模式共用。
|
||||
**状态机与租约的绑定**(见 §6 协议):
|
||||
- 进入 `filling / readyToReview / submitting` 时,适配器必须每 60 s 调 `lease-manager.extend(lease_token)` 续租。
|
||||
- 进入 `readyToReview` 时,lease-manager 同步把服务端任务切到 `waiting_user` 状态,服务端 lease 续期 = 30 min(长于默认 10 min),避免等用户审核过程中被抢任务。
|
||||
- 用户取消或 signal abort → `aborted`。
|
||||
- adapter 抛异常 → `failed`,带错误码。
|
||||
- 进程崩溃 → 启动时按 §7.5 分派为 `unknown`。
|
||||
|
||||
文件上传:`protocol.handle('file')` + CDP `Input.dispatchDragEvent` 或直接喂 `<input type=file>`,不经服务端中转。
|
||||
|
||||
@@ -237,51 +297,63 @@ type PublishMode =
|
||||
|
||||
`adapters/_shared/`:
|
||||
|
||||
- `debugger-helper.ts`:封装 CDP attach/detach、SSE stream 收集、超时。
|
||||
- `dom-helper.ts`:语义化选择器("找到登录头像")、重试、容错。
|
||||
- `debugger-helper.ts`:封装 CDP attach/detach、Fetch/Network domain 选择、SSE 流式拼接、超时。
|
||||
- `dom-helper.ts`:语义化选择器、重试、容错。
|
||||
- `upload-helper.ts`:文件上传统一入口。
|
||||
- `captcha-pause.ts`:遇滑块/点击验证时把 BrowserView 转前台让用户过,通过后继续。
|
||||
- `captcha-pause.ts`:遇滑块/点击验证时把 WebContentsView 转前台让用户过,通过后续跑。
|
||||
- **`proxy-helper.ts`**(新):统一走 `Session.setProxy(Account.proxy)`,支持 socks5/http + 凭据存 vault。
|
||||
|
||||
### 5.5 双轨更新:局部热更 + 全量
|
||||
### 5.5 双轨更新:局部热更 + 全量(安全收紧)
|
||||
|
||||
| 通道 | 内容 | 节奏 | 形式 |
|
||||
|---|---|---|---|
|
||||
| **Patch 通道** | 单个适配器的 `selectors.json`、`parsers.ts`(纯声明 / 纯函数)| 分钟级按需 | 服务端签名补丁包,存 `userData/adapter-patches/<platform>/<version>/`,**下次该适配器的任务开始时动态 import 加载**(不重启客户端)|
|
||||
| **Patch 通道(默认·纯数据 DSL)** | 适配器的 `selectors.json` + `parser.dsl`(受限的**声明式解析 DSL**,非 JS)| 分钟级按需 | 服务端签名 manifest + 补丁包;客户端 DSL 解释器运行,**无任意 JS 执行** |
|
||||
| **Patch 通道(备用·隔离执行)** | 个别场景确需写 JS parser 时的"逃生舱" | 仅在评审后发布 | 加载到 `utilityProcess`(无 Node 能力)+ `isolated-vm` 或 `vm.createContext` + 冻结 global + 禁用 `eval/Function/WebAssembly/dynamic import` + 仅允许消息传递返回结构化结果 |
|
||||
| **Release 通道** | 二进制、主进程、UI、适配器宿主框架 | 周/双周 | electron-updater 差分包 |
|
||||
|
||||
Patch 通道硬约束:
|
||||
**默认走纯数据 DSL 通道**——安全、可审、无 RCE 面。仅当平台响应结构复杂到 DSL 难以表达时,才考虑升级到备用通道,且每个 parser 必须经过安全评审。
|
||||
|
||||
- 只允许替换**声明式数据**和**纯函数模块**。静态检查拒绝 `fs/child_process/Buffer/require/import.meta.url` 等导入。
|
||||
- 服务端签名 + 客户端 Ed25519 验签。
|
||||
- 每个 patch 带 `{platform, version, minClientVersion}`;客户端版本不够忽略。
|
||||
- UI 每个适配器显示两行版本号:`client 1.2.0 · doubao patch #47`。
|
||||
**签名链(TUF 思路简化版)**:
|
||||
|
||||
### 5.6 Trace 基础设施(Playwright Trace Viewer 等级)
|
||||
每个 patch 打包为 `manifest.json + patch.data`,manifest 至少包含:
|
||||
|
||||
记录内容:
|
||||
```json
|
||||
{
|
||||
"platform": "doubao",
|
||||
"patch_seq": 47, // 单调递增,防回滚
|
||||
"hash": "sha256:<of patch.data>",
|
||||
"expires_at": "2026-05-18T00:00:00Z",
|
||||
"min_client_version": "1.2.0",
|
||||
"max_client_version": null, // 可选
|
||||
"key_id": "root-2026-01", // 支持根密钥轮换
|
||||
"revokes": [], // 撤销之前的 patch_seq 列表
|
||||
"sig": "<Ed25519 over manifest+hash>"
|
||||
}
|
||||
```
|
||||
|
||||
| 类别 | 内容 |
|
||||
|---|---|
|
||||
| **CDP 事件流** | Network/DOM/Page/Fetch/Runtime 事件时间线 |
|
||||
| **关键截屏** | 2 fps JPG 或手动截屏 |
|
||||
| **DOM 快照** | 关键节点 outerHTML |
|
||||
| **Network** | 请求+响应,包括 SSE 原始 chunks |
|
||||
| **日志** | console + 结构化 |
|
||||
客户端约束:
|
||||
|
||||
触发策略:
|
||||
- 内置 root key(可通过 Release 通道更新),支持 `key_id` 轮换。
|
||||
- 下载时校验签名;**每次加载前再次校验 hash + 过期时间 + 未被 `revokes` 列表覆盖**。
|
||||
- `patch_seq` 必须严格 > 已加载值,否则拒绝(防降级重放)。
|
||||
- 服务端下发一个 `revoke <platform> <patch_seq>` 指令 → 客户端停用对应 patch、降级为前一版本,直到收到下一批 manifest。
|
||||
- UI 每个适配器显示:`client 1.2.0 · doubao patch #47 (via DSL)`。
|
||||
|
||||
- **平时**:只结构化日志,不录 trace。
|
||||
- **Rolling buffer**:每个适配器保留最近 30 秒内存缓冲;任务失败自动 flush 成 trace,成功则丢弃。
|
||||
- **强制录制**:UI 开关或启动参数 `--trace=always`。
|
||||
- **日志等级**:debug/info/warn/error,按适配器独立调(`DEBUG_ADAPTERS=doubao,qwen`)。
|
||||
### 5.6 Trace 基础设施(Alpha 范围 + Phase 2 兼容 Playwright)
|
||||
|
||||
存储:
|
||||
**Alpha 范围(MVP)**:
|
||||
|
||||
- 位置:`userData/traces/<accountId>/<taskId>.trace.zip`。
|
||||
- 配额:默认保留 **50 份** 或 **500 MB**(先到先清)。
|
||||
- 脱敏:Cookie / Set-Cookie / Authorization / x-xsrf-token / password / 手机号 / email 自动 `<redacted>`。
|
||||
- **默认不上传服务端**;用户手动点"发送诊断包"时才上传。
|
||||
- 文件格式兼容 Playwright Trace Viewer(`npx playwright show-trace` 可直接打开)。
|
||||
- 结构化日志(pino JSON)滚动写入 `userData/logs/`。
|
||||
- 任务失败时自动截屏 3–5 张 + 存 Network 响应摘要。
|
||||
- 本地内置极简 viewer(Vue 单页,离线),能读 `.trace.json` 文件。
|
||||
- 脱敏:Cookie / Set-Cookie / Authorization / x-xsrf-token / password / 手机号 / email → `<redacted>`。
|
||||
- 存储:`userData/traces/<accountId>/<taskId>.trace.zip`,配额默认 **50 份** 或 **500 MB**(先到先清)。
|
||||
- **默认不上传服务端**;用户手动"发送诊断包"才上传。
|
||||
|
||||
**Phase 2(Beta 之后)**:
|
||||
|
||||
- Playwright Trace Viewer 格式兼容导出(为方便外部开发者用 `npx playwright show-trace` 打开)。因格式私有、跟随 Playwright 版本演进,仅作为**导出**选项。
|
||||
- CDP 事件流完整记录 + 2fps 截屏 + DOM 快照(内存 rolling buffer 30s,失败时 flush)。
|
||||
|
||||
---
|
||||
|
||||
@@ -291,75 +363,200 @@ Patch 通道硬约束:
|
||||
|
||||
老 `plugin_installations` / `/api/plugin/*` / `installation_token` → 新 `desktop_clients` / `/api/desktop/*` / `client_token`。因为是开发版本、无生产流量,DB 迁移直接 `drop` 旧表、`create` 新表,不做导入或双写。
|
||||
|
||||
**sqlc 协同**:重命名会让 `server/internal/tenant/repository/generated/` 里所有相关 struct 和 query 方法名重生成——Service 层也要改。实施计划必须**显式列出**:
|
||||
|
||||
- `server/internal/tenant/repository/queries/*.sql` 里要改的查询文件清单
|
||||
- 所有持有 `PluginInstallation` / `InstallationToken` 类型的 `server/internal/tenant/app/*.go` service 文件
|
||||
- `server/internal/tenant/transport/*.go` handler
|
||||
- 对应 repo 层单元测试
|
||||
|
||||
### 6.2 认证
|
||||
|
||||
| Token | 代表 | 发放 | 用途 |
|
||||
|---|---|---|---|
|
||||
| **User JWT** | 人类用户 | `/api/auth/login` | 用户在 Electron 内的交互(新增账号、创建监控计划、看报表) |
|
||||
| **Client Token** | 一台 Electron 安装 | 登录后 `/api/desktop/clients/register` 换取 | 后台任务回调、任务拉取、心跳 |
|
||||
| **Client Token** | 一台 Electron 安装(与 client_version 解耦)| 登录后 `/api/desktop/clients/register` 换取 | 后台任务回调、任务拉取、心跳 |
|
||||
|
||||
Client Token 绑定 `tenant_id + workspace_id + client_id + os + cpu_arch + client_version`,SHA-256 后入库。重装/迁机器 = 重新 register。
|
||||
**Client Token 绑定字段**:`client_id + tenant_id + workspace_id`(稳定维度)。**`client_version / os / cpu_arch` 仅作为 heartbeat metadata 上报,不入 token 绑定**——autoUpdater 升版后 token 仍有效。
|
||||
|
||||
### 6.3 任务分发:SSE + 60s pull 兜底
|
||||
Token TTL = 30 天,支持滚动刷新:`POST /api/desktop/clients/rotate` 用旧 token 换新 token(剩余有效期 < 7 天时自动调用)。
|
||||
撤销:`POST /api/desktop/clients/revoke` 由 Web 端用户主动撤销(如丢失设备)或管理员强制;被撤销的 client 任何接口都返 401 + 引导用户重新登录换新 client。
|
||||
|
||||
- 客户端启动后对 `/api/desktop/events` 建立 SSE 长连接,server 推 `{type:'task', payload}`。
|
||||
- 网络抖/代理不稳时退化为**每 60 秒** pull `/api/desktop/tasks/lease`。
|
||||
- 两渠道幂等;任务带 `task_id + deduplication_key`。
|
||||
- 为什么不用 WebSocket:SSE 单向推已足够,穿透 Nginx/Cloudflare 更稳,代码更简单。
|
||||
- 为什么不纯 pull:用户在 Web 上点"立即跑一次"需要秒级响应。
|
||||
### 6.3 任务分发:SSE + 60s pull 兜底 · 多实例协调
|
||||
|
||||
**SSE 仅作为通知信道,不直接承载任务归属。**
|
||||
|
||||
多实例部署下:
|
||||
|
||||
```
|
||||
Web UI / Scheduler creates task
|
||||
↓
|
||||
DB insert desktop_tasks(queued)
|
||||
↓
|
||||
publish task_available event
|
||||
↓
|
||||
RabbitMQ fanout → every server instance
|
||||
↓
|
||||
each instance looks up its local SSE hub:
|
||||
"do I hold any client that can handle this task's platform+workspace?"
|
||||
↓
|
||||
if yes: push {type: 'task_available', task_id} via SSE
|
||||
↓
|
||||
client POST /api/desktop/tasks/lease → server atomically claims task
|
||||
↓
|
||||
server returns {task, attempt_id, lease_token, lease_expires_at}
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- **SSE 只推 `task_available` 信号**,携带 `task_id` 而非任务正文,避免跨实例推送导致 payload 在错误客户端落地。
|
||||
- **任务归属由 `POST /tasks/lease` 原子性决定**(DB `UPDATE ... WHERE status='queued' RETURNING` 或 Postgres advisory lock)。多个 client 同时收到通知也只会有一个 lease 成功。
|
||||
- **60 s pull** 作为 SSE 失联兜底;和 SSE 都打同一个 lease endpoint,天然幂等。
|
||||
- **presence registry**:每个 server 实例维护本地 `client_id → SSE connection`,并把 `(client_id, instance_id)` 写到 Redis(TTL 30 s,SSE 心跳续期)。Scheduler 根据 presence 决定是否要广播。
|
||||
|
||||
### 6.4 路由表
|
||||
|
||||
```
|
||||
# 配对
|
||||
POST /api/auth/login
|
||||
POST /api/desktop/clients/register # body: {device_name, os, cpu_arch, client_version}
|
||||
POST /api/desktop/clients/heartbeat # client_token, 上报活跃度
|
||||
POST /api/desktop/clients/register # 换 client_token
|
||||
POST /api/desktop/clients/rotate # 滚动刷新 client_token
|
||||
POST /api/desktop/clients/revoke # 主动撤销
|
||||
POST /api/desktop/clients/heartbeat # 心跳 + 上报 client_version/os/arch
|
||||
POST /api/desktop/clients/metrics # 轻量非敏感聚合指标
|
||||
|
||||
# 事件 & 任务
|
||||
# 事件 & 任务租约
|
||||
GET /api/desktop/events # SSE, client_token
|
||||
GET /api/desktop/tasks/lease?kind=publish|monitor # pull 兜底
|
||||
POST /api/desktop/tasks/{id}/ack
|
||||
POST /api/desktop/tasks/{id}/result
|
||||
POST /api/desktop/tasks/lease # {task_id?} 原子领取;kind=publish|monitor 过滤
|
||||
POST /api/desktop/tasks/{id}/extend # body: {lease_token},续租
|
||||
POST /api/desktop/tasks/{id}/pause # body: {lease_token, reason: 'waiting_user'|'captcha'}
|
||||
POST /api/desktop/tasks/{id}/resume # body: {lease_token}
|
||||
POST /api/desktop/tasks/{id}/cancel # body: {lease_token}
|
||||
POST /api/desktop/tasks/{id}/result # body: {lease_token, status, payload}
|
||||
POST /api/desktop/tasks/{id}/trace # 用户主动上传诊断包
|
||||
|
||||
# 账号元数据(不传 cookie)
|
||||
POST /api/desktop/accounts
|
||||
PATCH /api/desktop/accounts/{id}
|
||||
POST /api/desktop/accounts # upsert:以 (workspace_id, platform, platform_uid) 为键
|
||||
PATCH /api/desktop/accounts/{id} # 改 displayName / health / quota
|
||||
DELETE /api/desktop/accounts/{id}
|
||||
|
||||
# Patch 通道
|
||||
GET /api/desktop/patches/manifest # 返回当前适用的 manifest 列表 + revoke 指令
|
||||
|
||||
# 管理 UI(Web / Electron UI 共用)
|
||||
GET /api/tenant/monitoring-plans
|
||||
POST /api/tenant/publish-jobs
|
||||
# ...
|
||||
```
|
||||
|
||||
**幂等契约**:所有写操作(lease/extend/pause/resume/cancel/result)**必须携带 `lease_token`**;服务端校验 `lease_token` 与当前 `active_attempt_id` 绑定,否则返 409 Conflict。客户端收到 409 = 租约已失效,**停止动作 + 提示人工对账**。
|
||||
|
||||
### 6.5 服务端边界
|
||||
|
||||
服务端只存**任务意图**("账号 A 去豆包问 Q")与**结果**("答案文本 + 品牌是否出现")。**永不**存 Cookie、**永不**直连平台帮客户端跑。客户端失联 = 任务排队;长时间拿不到 = 超时失败。硬边界,防止 Cookie 从客户端外泄。
|
||||
|
||||
### 6.6 DB Schema(关键表)
|
||||
|
||||
- `desktop_clients`: `id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, created_at, last_seen_at, revoked_at`
|
||||
- `platform_accounts`: `id, tenant_id, workspace_id, client_id(FK), platform, display_name, health, created_at, last_seen_at`(**无 cookie / token 列**)
|
||||
- `desktop_tasks`: `id, kind, payload(jsonb), status, client_id, lease_expires_at, created_at, result(jsonb), error(jsonb), dedup_key`
|
||||
- `desktop_task_traces`: `task_id(FK), size, uploaded_at, object_key`
|
||||
```sql
|
||||
-- 客户端身份
|
||||
CREATE TABLE desktop_clients (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
workspace_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
token_hash BYTEA NOT NULL,
|
||||
device_name TEXT,
|
||||
os TEXT,
|
||||
cpu_arch TEXT,
|
||||
client_version TEXT, -- 仅作 metadata,不入 token 绑定
|
||||
channel TEXT, -- alpha/beta/stable
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
last_seen_at TIMESTAMPTZ,
|
||||
last_rotated_at TIMESTAMPTZ,
|
||||
revoked_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
### 6.7 任务生命周期
|
||||
-- 平台账号(workspace 独占约束落地)
|
||||
CREATE TABLE platform_accounts (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
workspace_id UUID NOT NULL,
|
||||
client_id UUID NOT NULL REFERENCES desktop_clients(id),
|
||||
platform TEXT NOT NULL,
|
||||
platform_uid TEXT NOT NULL, -- 平台侧真实 ID(必填)
|
||||
account_fingerprint TEXT,
|
||||
display_name TEXT NOT NULL,
|
||||
health TEXT NOT NULL,
|
||||
verified_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
last_seen_at TIMESTAMPTZ,
|
||||
UNIQUE (platform, platform_uid) -- 硬约束 8 的 DB 落地
|
||||
);
|
||||
|
||||
-- 任务 + 租约
|
||||
CREATE TABLE desktop_tasks (
|
||||
id UUID PRIMARY KEY,
|
||||
kind TEXT NOT NULL, -- 'publish' | 'monitor'
|
||||
payload JSONB NOT NULL,
|
||||
status TEXT NOT NULL, -- queued | in_progress | waiting_user | succeeded | failed | unknown | aborted
|
||||
dedup_key TEXT,
|
||||
-- 租约
|
||||
active_attempt_id UUID, -- null 表示无租约持有者
|
||||
lease_token_hash BYTEA,
|
||||
lease_client_id UUID REFERENCES desktop_clients(id),
|
||||
lease_expires_at TIMESTAMPTZ,
|
||||
attempts INT NOT NULL DEFAULT 0,
|
||||
-- 结果
|
||||
result JSONB,
|
||||
error JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
-- 历次 attempt 的审计(调试用,可选保留 30 天)
|
||||
CREATE TABLE desktop_task_attempts (
|
||||
id UUID PRIMARY KEY,
|
||||
task_id UUID NOT NULL REFERENCES desktop_tasks(id),
|
||||
client_id UUID NOT NULL REFERENCES desktop_clients(id),
|
||||
started_at TIMESTAMPTZ NOT NULL,
|
||||
ended_at TIMESTAMPTZ,
|
||||
final_status TEXT,
|
||||
error JSONB
|
||||
);
|
||||
|
||||
CREATE TABLE desktop_task_traces (
|
||||
task_id UUID NOT NULL REFERENCES desktop_tasks(id),
|
||||
attempt_id UUID,
|
||||
size BIGINT,
|
||||
uploaded_at TIMESTAMPTZ,
|
||||
object_key TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### 6.7 任务生命周期(含租约细节)
|
||||
|
||||
```
|
||||
Web UI 创建 MonitoringPlan("brand=X on doubao,qwen daily 9am")
|
||||
↓
|
||||
Scheduler (server cron) 展开成 N 条 desktop_tasks (queued)
|
||||
Scheduler (server cron) 展开成 N 条 desktop_tasks (status=queued)
|
||||
↓
|
||||
SSE push → 任意在线的、有对应账号的 desktop_client
|
||||
SSE push 'task_available' → 任意实例 → 对应 SSE 连接 → client
|
||||
↓
|
||||
Client ack → lease_expires_at=+10min → 执行 → result
|
||||
Client POST /tasks/lease → DB atomic claim →
|
||||
返回 {task, attempt_id, lease_token, lease_expires_at=+10min}
|
||||
↓
|
||||
POST /result → server 存结果 → Web UI 可视化
|
||||
Client 执行(filling→submitting),每 60s POST /extend
|
||||
↓
|
||||
如果 manual 等审核 → POST /pause(reason=waiting_user) → lease_expires_at=+30min
|
||||
审核通过 → POST /resume
|
||||
↓
|
||||
POST /result(lease_token, status=succeeded|failed) → server 校验 lease_token 后存结果
|
||||
↓
|
||||
Web UI 可视化
|
||||
```
|
||||
|
||||
**超时处理**:`lease_expires_at` 到期后 scheduler 把 task 放回 `queued`,`active_attempt_id` 置空;若老 client 恢复后再 POST `/result(lease_token=过期值)`,返 409 → client 本地记为 `unknown` 提示人工。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理与可观测性
|
||||
@@ -375,25 +572,32 @@ POST /result → server 存结果 → Web UI 可视化
|
||||
|
||||
### 7.2 结构化日志
|
||||
|
||||
`pino` JSON,字段约定:`ts, level, module, accountId, taskId, stage, event, durationMs, bytes`。本地 rolling file(10 MB × 20 份)。**不默认上传**。
|
||||
`pino` JSON,字段约定:`ts, level, module, accountId, taskId, attemptId, leaseToken, stage, event, durationMs, bytes`。本地 rolling file(10 MB × 20 份)。**不默认上传**。脱敏字段:Cookie / Set-Cookie / Authorization / x-xsrf-token / password / 手机号 / email。
|
||||
|
||||
### 7.3 指标上报
|
||||
|
||||
5 min 一次上报 `/api/desktop/clients/metrics`,只含非敏感聚合:CPU/内存/事件循环延迟、每平台每账号的任务成功率 + 平均耗时、SSE 连接状态、pull 回退次数。**不含**查询内容、回答内容、Cookie。
|
||||
5 min 一次上报 `/api/desktop/clients/metrics`,只含非敏感聚合:CPU/内存/事件循环延迟、每平台每账号的任务成功率 + 平均耗时、SSE 连接状态、pull 回退次数、token bucket 拒绝率。**不含**查询内容、回答内容、Cookie。
|
||||
|
||||
### 7.4 用户可见反馈
|
||||
|
||||
- 托盘图标三色(绿/黄/红)表示账号健康。
|
||||
- OS 通知**只**在需要用户动作时弹(登录失效、捕获到验证码)。
|
||||
- OS 通知**只**在需要用户动作时弹(登录失效、捕获到验证码、租约失效导致 UNKNOWN 需人工对账)。
|
||||
- UI "事件中心":列最近 50 次失败,点进去直接"重新运行"或"启用录屏再跑"。
|
||||
- "发送诊断包"按钮:最近 1 小时日志 + trace 打包脱敏上传,返回 `diagnostic_id`。
|
||||
|
||||
### 7.5 崩溃恢复(幂等)
|
||||
### 7.5 崩溃恢复(按 task.kind 分治)
|
||||
|
||||
启动时扫描 `desktop_tasks` 里 `lease 属于我但 result 为空` 的任务:
|
||||
启动时扫描 `desktop_tasks` 里 `lease_client_id = self AND status IN (in_progress, waiting_user)` 的任务:
|
||||
|
||||
- **未进入 submit 阶段**:重新 ack 再跑。
|
||||
- **已进入 submit 阶段**(已确认):**放弃任务,标 UNKNOWN,由人判断**。UI 提示"上次崩溃时尚不确定是否发布成功,请去平台确认后手动标注",避免重复发布。
|
||||
**Monitor 任务**(幂等只读,可安全重跑):
|
||||
|
||||
- 任何阶段崩溃 → 丢弃本地状态 → 放回队列 → 下次被分派重跑。日志里记录 `previous_attempt_aborted_by_crash` 用于对账。
|
||||
- 不标 UNKNOWN,不骚扰用户。
|
||||
|
||||
**Publish 任务**(不可逆副作用,保守处理):
|
||||
|
||||
- **未进入 `submitting` 阶段**(即还在 `filling` 或 `readyToReview`):重新 ack → 把 state 重置到 `filling` 从头跑,因为还没点过"发布"按钮。
|
||||
- **已进入 `submitting`**:**放弃任务,标 `unknown`**。UI 提示"上次崩溃时尚不确定是否发布成功,请去平台确认后手动标注 succeeded 或重新创建任务"。避免重复发布。
|
||||
|
||||
---
|
||||
|
||||
@@ -417,15 +621,21 @@ POST /result → server 存结果 → Web UI 可视化
|
||||
|
||||
产物大小目标:**单平台 ≤ 120 MB**。
|
||||
|
||||
**native deps 多架构注意**:`better-sqlite3` / `keytar` 等在 universal Mac build 里需通过 `electron-builder` 的 `buildDependenciesFromSource` 或 pre-built multi-arch binaries 处理,CI 会增加一步 `@electron/rebuild` 针对每个 target。
|
||||
|
||||
### 8.3 CI
|
||||
|
||||
GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 universal;`windows-latest` 出 x64 与 arm64;`ubuntu-latest` 出 x64;`ubuntu-22.04-arm` 出 arm64(或 cross-build)。发布 tag `v1.2.3` → 打包 → 上传对象存储 + 生成 `latest-*.yml` → `autoUpdater.checkForUpdates()` 看到。
|
||||
GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 universal;`windows-latest` 出 x64;Windows arm64 用 cross-compile;Linux x64 `ubuntu-latest`;Linux arm64 优先用 `ubuntu-22.04-arm` 原生 runner,不可用时退化到 QEMU(2026-04 GitHub arm64 runner 仍在限量可用)。
|
||||
|
||||
发布 tag `v1.2.3` → 打包 → 上传对象存储 + 生成 `latest-*.yml` → `autoUpdater.checkForUpdates()` 看到。
|
||||
|
||||
### 8.4 更新 UX
|
||||
|
||||
- 默认"自动下载 + 手动安装"。下好后 Tray 角标提示"新版本可用,点击重启安装"。不打断正在跑的任务。
|
||||
- 强制更新:服务端下发 `minClientVersion`;低于该版本拒绝启动,引导升级。
|
||||
- 降级:服务端可下发"建议版本号",客户端比建议版本新时允许一键回滚(electron-updater `downgrade: true`)。
|
||||
- 强制更新:服务端下发 `minClientVersion`;低于该版本拒绝启动,引导升级(配合 30 分钟宽限期 + 本地可见的手动下载链接兜底)。
|
||||
- 降级:使用 `electron-updater` 的 `allowDowngrade: true`(**注意正确拼写**,旧 spec 写的 `downgrade` 属性不存在)。服务端可下发"建议版本号",客户端比建议版本新时允许一键回滚。
|
||||
- **N-1 回退**:对象存储长期保留上一版安装包 + manifest,UI 提供"手动下载上一版本"链接,作为 autoUpdater 失败的人工兜底。
|
||||
- pre-release 通道每次打包都跑一次**updater smoke 自动化**(见 §9.7)。
|
||||
|
||||
### 8.5 应用标识
|
||||
|
||||
@@ -438,7 +648,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
- `contextIsolation: true`、`nodeIntegration: false`、`sandbox: true` for renderer。
|
||||
- CSP 严格模式;renderer 不允许加载远程 JS。
|
||||
- `webContents.setWindowOpenHandler` 拦截所有 window.open → 系统浏览器。
|
||||
- 禁用 `<webview>` tag,第三方平台一律用 `BrowserView`。
|
||||
- 禁用 `<webview>` tag,第三方平台一律用 `WebContentsView`。
|
||||
|
||||
---
|
||||
|
||||
@@ -460,8 +670,8 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
|
||||
### 9.2 单元
|
||||
|
||||
- 对象:适配器的 **SSE parser**(纯函数)与 **发布状态机**。
|
||||
- Fixtures:`tests/fixtures/sse/doubao-2026-04.txt`(脱敏后真实抓的 SSE)。
|
||||
- 对象:适配器的 **SSE parser**(纯函数)、**发布状态机 + 租约交互逻辑**、**lease-manager 本地状态机**、**autoUpdater 核心逻辑**(版本比较、签名验签、`allowDowngrade` 决策、`minClientVersion` 校验)、**patch-loader 签名与序号校验**。
|
||||
- Fixtures:`tests/fixtures/sse/doubao-2026-04.txt`、`tests/fixtures/manifest/valid-*.json` + `invalid-*.json`。
|
||||
- 平台 payload 变动 → 补 fixture → 测试跟着更新。
|
||||
|
||||
### 9.3 集成
|
||||
@@ -470,7 +680,7 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
|
||||
### 9.4 集成 · 协议
|
||||
|
||||
客户端 + 真实 server + fake 平台:验证**任务分发 / 幂等 / 熔断 / 断线重连**。
|
||||
客户端 + 真实 server + fake 平台:验证**任务分发 / 租约 (lease/extend/pause/resume/cancel) / 多实例广播 / 幂等 / 熔断 / 断线重连**。
|
||||
|
||||
### 9.5 人工冒烟 Playbook
|
||||
|
||||
@@ -485,16 +695,18 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
|
||||
工具 `pnpm dev:replay <trace.zip>`:把真实 trace 里的 network responses 重放到 fake 平台,跑 adapter。历史错过的输入永远不再破解析器。
|
||||
|
||||
### 9.7 不做的
|
||||
### 9.7 autoUpdater Smoke(pre-release 必跑)
|
||||
|
||||
- 不对 electron-builder 打包产物在 CI 跑 E2E(矩阵太贵)。
|
||||
- 不对 autoUpdater 真实升级做 CI(单独写 `tests/updater-smoke.md` 手动脚本)。
|
||||
- 不测真平台滑块 captcha——它是概率事件,只测 gate 机制。
|
||||
不是"完整升级 E2E",但必须覆盖:
|
||||
|
||||
- 旧版本打包 + 新版本打包 + 从旧版本启动 → 拉 manifest → 下载 → 安装 → 启动新版本(headless 跑通即 pass)。
|
||||
- 签名不一致、manifest 过期、`minClientVersion` 不满足 → 各一条负面用例。
|
||||
- `allowDowngrade: true` 与 `false` 各一条正向用例。
|
||||
|
||||
### 9.8 门禁
|
||||
|
||||
- PR:单元 + 集成(fake)全过。
|
||||
- Release tag:+ 冒烟 Playbook 签字。
|
||||
- Release tag:+ 冒烟 Playbook 签字 + updater smoke 全过。
|
||||
- 每周:trace 诊断报告 review。
|
||||
|
||||
---
|
||||
@@ -505,8 +717,8 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
|
||||
| 阶段 | 受众 | 签名 | 退出门槛 |
|
||||
|---|---|---|---|
|
||||
| **Alpha(内部)** | 开发 + QA(≤5 人)| Mac 公证 / Win 无签名 / Linux 裸 AppImage | 13+3 适配器 fake + 真实账号成功率 ≥ 90% |
|
||||
| **Beta(邀请制)** | 公司内部 + 合作客户(≤30 人)| 同上 | 连续 7 天无 P0;保活命中率 > 95%;patch 通道应急修复跑通 ≥ 1 次 |
|
||||
| **Alpha(内部)** | 开发 + QA(≤5 人)| Mac 公证 / Win 无签名 / Linux 裸 AppImage | 13+3 适配器 fake + 真实账号成功率 ≥ 90%;lease 协议在混沌测试下无脏写 |
|
||||
| **Beta(邀请制)** | 公司内部 + 合作客户(≤30 人)| 同上 | 连续 7 天无 P0;保活命中率 > 95%;patch 通道应急修复跑通 ≥ 1 次;autoUpdater smoke 每 release 全过 |
|
||||
| **GA 候选** | 放开注册 | **Win 签名必须到位** | 安装成功率 > 99%;crash rate < 0.1%/session |
|
||||
|
||||
客户端和服务端同加 `channel`(alpha/beta/stable),更新按 channel 拉不同 `latest-*.yml`。"关于"里可切。
|
||||
@@ -522,41 +734,129 @@ GitHub Actions,runner 矩阵:`macos-14 (arm64)` + `macos-13 (x64)` 合并 un
|
||||
|
||||
如果未来发现有内部测试账号遗留在老表里:手动导出账号元数据后 drop 老表,不写自动迁移。
|
||||
|
||||
### 10.3 三层回滚
|
||||
### 10.3 工作量重估(4 块)
|
||||
|
||||
粗估(单人工程周):
|
||||
|
||||
| 块 | 范围 | 估时 |
|
||||
|---|---|---|
|
||||
| **A. Desktop runtime 基座** | Main 进程、session-registry、view-pool、vault、lease-manager、rate-limiter、circuit-breaker、crash-recovery、tracing(Alpha 版)、transport (SSE + pull + RabbitMQ presence) | 3–4 周 |
|
||||
| **B. LLM 监控适配器** | Doubao + Qwen + **DeepSeek(从零)** | 2–3 周 |
|
||||
| **C. 已有真实发布能力的平台迁移** | 约 6 家(头条 / 百家 / 搜狐 / 知乎 / 企鹅 / 简书)从 chrome.* 迁到 Electron 抽象 | 3 周 |
|
||||
| **D. Detect-only 平台从零实现 publish** | 7 家(微信公众号 / 掘金 / 懂车帝 / 什么值得买 / 网易 / B 站 / ZOL)| 5–7 周 |
|
||||
| **E. 服务端协议 + DB + sqlc 重生 + Service 层重构** | `/api/desktop/*`、租约、presence、patch manifest | 2 周 |
|
||||
| **F. 打包 5 arch + Mac 公证 + autoUpdater + updater smoke** | — | 1.5 周 |
|
||||
| **G. Patch 通道(DSL 解释器 + sandbox 备用 + 签名链)** | — | 2 周 |
|
||||
| **H. packages/ui-shared 抽取 + desktop renderer** | — | 1.5 周 |
|
||||
| **I. 测试基建(fake server + fake 平台 + replay)** | — | 1.5 周 |
|
||||
|
||||
合计约 **22–26 工程周**。这份 spec 因此**应拆 3 个 plan**(见 §12)。
|
||||
|
||||
### 10.4 三层回滚
|
||||
|
||||
| 情景 | 动作 |
|
||||
|---|---|
|
||||
| 某 patch 导致单平台适配器坏 | 服务端把该平台 patch 版本号回滚到上一稳定版;客户端下次任务自动用旧 patch(秒级) |
|
||||
| 某 release 中等问题 | 下发"建议版本 ≤ X";`electron-updater` 帮用户降级(分钟级) |
|
||||
| 某 release 严重安全问题 | 下发 `minClientVersion = Y`;低于 Y 拒绝启动,UI 引导升级 |
|
||||
| 某 patch 导致单平台适配器坏 | 服务端下发 revoke `patch_seq` + 上一版本可用 manifest;客户端下次任务自动降回前一版 patch(秒级) |
|
||||
| 某 release 中等问题 | 下发"建议版本 ≤ X";`electron-updater` 用 `allowDowngrade: true` 降级(分钟级);+ 手动 N-1 下载链接 |
|
||||
| 某 release 严重安全问题 | 下发 `minClientVersion = Y`;低于 Y 的客户端 30 分钟宽限后拒绝启动,UI 引导升级 |
|
||||
|
||||
所有回滚**只在服务端配置**,不动客户端代码。
|
||||
|
||||
### 10.4 运营指标
|
||||
### 10.5 运营指标
|
||||
|
||||
- 每平台适配器每日成功率(target:发布 > 95%、监控 > 90%)
|
||||
- 每次 crash 的 top 堆栈
|
||||
- SSE 长连接平均保持时长(target > 30 min)
|
||||
- patch 推送后"多久 80% 客户端加载新 patch"(target < 30 min)
|
||||
- Patch 推送后"多久 80% 客户端加载新 patch"(target < 30 min)
|
||||
- 租约冲突率(409 / 百次 lease;target < 0.5%)
|
||||
|
||||
---
|
||||
|
||||
## 11. 已知风险
|
||||
|
||||
1. **Win 无签名期间**(Alpha/Beta),用户首次安装会遇 SmartScreen 警告,转化率会掉。已接受。
|
||||
2. **豆包/千问网页结构变**,最坏情况紧急手搓 patch,SLA 按小时算。
|
||||
3. **用户跨 OS 切换**(Mac → Win 再回来)不迁移登录态(local-only cookies),需全部重登。
|
||||
1. **Win 无签名期间**(Alpha/Beta),用户首次安装会遇 SmartScreen 警告,且有机率被 Defender / 360 / 国内安全软件主动隔离。Alpha 期间通过白名单与用户手动放行缓解。
|
||||
2. **豆包/千问网页结构变**,最坏情况紧急手搓 patch,SLA 按小时算。DSL 能力有限时退化到备用隔离执行通道。
|
||||
3. **用户跨 OS 切换**(Mac → Win 再回来)不迁移登录态(local-only cookies),需全部重登。或使用 `vault-export` 手工带密口令迁移。
|
||||
4. **账号被平台风控**无法自动恢复;UI 引导用户换号。
|
||||
5. **开机自启 + 托盘常驻**可能被企业 EDR 误报恶意软件;需准备白名单说明材料。
|
||||
6. **Electron 基础 RAM 占用**~300 MB + 每 BrowserView ~50 MB。10 账号场景约 800 MB,这是 Chromium 本质开销,无法显著优化。
|
||||
6. **Electron 基础 RAM 占用**~300 MB + 每 hot-tier WebContentsView ~50 MB。默认 Hot/Cold 分档后,10 账号活跃场景约 600–800 MB。
|
||||
7. **Electron CVE 维护节奏**:Electron 约每 4–6 周发安全版本,需对应 release 通道的补丁节奏。滞后发版 > 2 周的 Electron 主线安全问题需主动升级。
|
||||
8. **平台 ToS 风险**:部分媒体平台(尤其微信公众号)明文禁止自动化操作。Alpha/Beta 阶段限定给明确授权账号;商业化前评估法务暴露面。
|
||||
9. **CDP domain 可用性跟随 Chromium 版本**(如 `Network.streamResourceContent` ≥ 120)。升 Electron 大版本前必须跑一遍 `electron-chromium-cdp-matrix.md` 回归。
|
||||
|
||||
---
|
||||
|
||||
## 12. 术语表
|
||||
## 12. Plan 拆分建议
|
||||
|
||||
- **Patch 通道**:仅下发适配器的声明式数据和纯函数,分钟级生效,不重启。
|
||||
本 spec 包含约 22–26 周的工作量,应按以下 3 个独立 plan 执行,每个 plan 都能独立 ship:
|
||||
|
||||
### Plan A · 骨架穿透(5–6 周,Alpha 上限)
|
||||
|
||||
- 块 A(Desktop runtime 基座)
|
||||
- 块 E(服务端协议 + DB)
|
||||
- 块 H(ui-shared + renderer)
|
||||
- **1 个** 监控适配器(Doubao)+ **1 个** 发布适配器(头条号,它已有 publish 能力)
|
||||
- 块 I(测试基建)
|
||||
- 块 F 但**仅 Mac 公证**
|
||||
|
||||
**退出目标**:端到端能跑一条 Doubao 监控任务 + 一条头条发布任务,Mac 公证版可安装,lease 协议在 fake 多实例下无脏写。
|
||||
|
||||
### Plan B · 广度铺开(10–12 周,Beta 上限)
|
||||
|
||||
- 块 B(Qwen + DeepSeek 从零)
|
||||
- 块 C(其余 5 家已有真实发布能力的平台迁移)
|
||||
- 块 D(7 家 detect-only 从零实现 publish)
|
||||
- 块 F 完整:Win/Linux 全架构 + 签名(Win 签名到位)+ autoUpdater 完整
|
||||
- 块 I 补齐:多适配器回归 + 冒烟 Playbook
|
||||
|
||||
**退出目标**:13+3 适配器全量可用,autoUpdater smoke 每 release 全过,5 arch × 3 OS 全量打包签名。
|
||||
|
||||
### Plan C · 运维闭环(4–5 周,GA 上限)
|
||||
|
||||
- 块 G(Patch 通道 DSL + 沙箱 + 签名链)
|
||||
- Phase 2 Trace 基建(CDP 全事件流 + Playwright 兼容导出)
|
||||
- Metrics dashboard + 报警规则
|
||||
- 回滚剧本与演练
|
||||
|
||||
**退出目标**:patch 热更经过混沌演练,trace 能被外部 Playwright tooling 打开,运营指标全套接入报警。
|
||||
|
||||
---
|
||||
|
||||
## 13. 变更记录
|
||||
|
||||
- **v2(2026-04-18)**:Claude + Codex + Gemini 三方交叉审查后的修订版。主要修订:
|
||||
- **C1** 修正 CDP 流式 SSE 抓取方法(§5.2),改为按 EventSource / fetch-stream 分流;维护 Electron Chromium CDP 版本矩阵。
|
||||
- **C2** Patch 通道默认改为**纯数据 DSL**(无 JS 执行),可执行代码作为备用通道并限定在 `utilityProcess + isolated-vm` 沙箱;引入 TUF 风格 manifest + patch_seq + 根密钥轮换 + 撤销机制(§5.5)。
|
||||
- **C3** 补齐 SSE 多实例协调方案:RabbitMQ 广播 + Redis presence registry,SSE 仅推通知、任务归属由 lease endpoint 原子决定(§6.3)。
|
||||
- **C4** `platform_accounts` 新增 `platform_uid` + 唯一索引 `(platform, platform_uid)`,落实硬约束 8(§6.6)。
|
||||
- **C5** 任务协议引入 `attempt_id + lease_token` + extend/pause/resume/cancel;manual 模式进入 `waiting_user` 状态并延长租约(§5.3/§6.4/§6.6/§6.7)。
|
||||
- **C6** §3.3 重写工作量与可复用性口径,显式列出 7 家 detect-only 发布适配器 + DeepSeek 监控占位;§10.3 拆 9 个工作块重估总时;§12 新增 3-plan 拆分建议。
|
||||
- **W1** `BrowserView` 全局替换为 `WebContentsView`。
|
||||
- **W2** autoUpdater 配置名修正 `downgrade` → `allowDowngrade`;加 N-1 手动下载链接 + pre-release updater smoke(§8.4/§9.7)。
|
||||
- **W3** Client Token 解绑 `client_version/os/cpu_arch`,新增 `/rotate` 和 `/revoke`(§6.2/§6.4)。
|
||||
- **W4** Patch 签名链补全 manifest 字段、根密钥轮换、运行前 hash 二次校验、revoke 指令(§5.5)。
|
||||
- **W5** `safeStorage` 启动时检测可用性;Linux `basic_text` 降级告警;跨机迁移改为独立的 `vault-export` 用户交互式流程(§4.5)。
|
||||
- **W6** 崩溃恢复按 `task.kind` 分治:monitor 幂等重跑,publish 按 `submitting` 边界决定人工对账(§7.5)。
|
||||
- **W7** 保活引入 per-(platform,account) token bucket(业务+保活+探活同桶);429/captcha 指数退避;resume 错峰 warm-up(§4.4)。
|
||||
- **W8** `apps/admin-web` 不再被 renderer 直接复用;抽 `packages/ui-shared` 作为共享包;renderer 是极简 Vite 项目(§3.3)。
|
||||
- **W9** 发布状态机加 `failed / aborted / unknown` 终止态(§5.3)。
|
||||
- **W10** §6.1 显式列出 sqlc 重生成与 Service/Handler 受影响面。
|
||||
- **W11** Trace 拆 Alpha(结构化日志 + 失败截屏)vs Phase 2(CDP 全事件流 + Playwright 兼容导出)(§5.6)。
|
||||
- **W12** §9.2 + §9.7 新增 autoUpdater 核心逻辑单元测试 + updater smoke 自动化覆盖。
|
||||
- **Info 合并**:`view-pool` Hot/Cold 分档、`proxy-helper` 新建、登录 1.5s 遮罩层、时区明确为本地、配额默认值、`vault-export` 备份流程、universal Mac 原生依赖、arm64 runner 兜底、平台 ToS / Electron CVE / Win Defender 三条新风险(§3.1/§4.2/§4.4/§4.5/§5.4/§8.2/§8.3/§11)。
|
||||
|
||||
---
|
||||
|
||||
## 14. 术语表
|
||||
|
||||
- **Patch 通道**:仅下发适配器的声明式数据与受限 DSL(默认)或隔离沙箱执行的纯函数(备用),分钟级生效,不重启。
|
||||
- **Release 通道**:完整二进制 + 差分升级,通过 electron-updater。
|
||||
- **探活(probe)**:加载主页 DOM 判断登录态,低代价。
|
||||
- **保活(keep-alive / heartbeat)**:主动访问平台私有 API 触发 token 刷新。
|
||||
- **Client Token**:一台 Electron 安装的后台身份,与 User JWT 并列。
|
||||
- **Client Token**:一台 Electron 安装的后台身份,与 User JWT 并列;解耦 client_version,支持 rotate/revoke。
|
||||
- **Partition**:Electron `session.fromPartition('persist:<id>')`,每个账号一个隔离的 cookie/storage 容器。
|
||||
- **Lease**:任务租约。领取任务时服务端返回 `attempt_id + lease_token + lease_expires_at`;后续所有写操作必须带 `lease_token` 验证。
|
||||
- **Waiting-user 状态**:manual 发布模式下,adapter 暂停等用户审核时,服务端将任务置为此状态并延长租约;在此状态下不会被重分配。
|
||||
- **Platform UID**:平台侧真实账号 ID(如微信 fakeid、B 站 mid、知乎 hash_id);`(platform, platform_uid)` 在 DB 层唯一,落实硬约束 8。
|
||||
- **DSL(Patch 默认通道)**:一套受限的声明式解析语言,只能做字符串/JSON 解析与模式匹配,无 JS 执行能力。
|
||||
- **Presence registry**:多实例服务端共享的 `client_id → (instance_id, conn_id)` 注册表,用 Redis TTL 30 s 维护;用于跨实例路由 SSE 通知。
|
||||
|
||||
Reference in New Issue
Block a user