14 KiB
14 KiB
EveryPublish 系统设计(网页端 + 客户端 App)
版本 v1 · 2026-08-20 · 与 docs/multi-platform-publish-plan.md 配套(架构原则见其 §3/§3.1/§17-22)
1. 总体形态
- 网页端(我方统一部署):客户工作台 + 内部后台,同一应用、角色与租户隔离;客户与我们内部共用一套代码。
- 客户端 App(客户部署):Agent 常驻应用(系统托盘/后台服务),承载数据面。
- 连接:WSS 双向长连接为主通道 + HTTPS 长轮询降级(评估见 §4)。
术语澄清:本文「Agent」= 客户端发布执行器(本地常驻程序,系统托盘/后台服务形态),与 AI 大模型 Agent 无关——不跑模型、不调 AI 接口、不需要任何模型配置。用户侧零配置:下载安装包 → 扫码绑定 → 完成。运行时与浏览器驱动全部内置进安装包。对外可称「发布助手」。
2. 网页端功能清单
2.1 客户工作台(按角色)
| 模块 | 功能点 | 可见角色 |
|---|---|---|
| 认证与账户 | 登录(密码+TOTP 2FA)、找回密码、登录设备管理 | 全部 |
| 工作区与成员 | 成员邀请、角色(管理员/审核/运营)、权限 | 管理员 |
| 平台账号台账 | 绑定/解绑、登录态健康度(过期预测)、代理绑定查看、发起扫码 | 管理员 |
| 素材库 | 上传(OSS 预签名直传)、分组/标签、检索、预览、元信息 | 运营/审核 |
| 发布任务 | 创建(选素材/平台/账号/标题话题/定时)、排期日历、列表筛选、实时状态、失败重试、取消 | 运营 |
| 审核流 | 待审队列、通过/驳回+批注(可配置跳过) | 审核/管理员 |
| 通知与挑战 | 挑战处理中心(扫码/验证码/APP确认)、失败告警、登录过期提醒 | 管理员/运营 |
| 审计 | 全操作日志查询 | 管理员 |
| 数据报表(可选) | 发布量/成功率/失败原因分布 | 管理员 |
2.2 内部后台(平台角色,与工作台同部署)
| 模块 | 功能点 |
|---|---|
| 租户管理 | 客户列表、套餐/计费、配额、状态 |
| Agent 管理 | 设备在线监控、版本推送、远程指令(重启/升级)、强制下线/吊销 |
| 渠道适配器 | 平台模块状态、改版巡检、灰度开关 |
| 风控台账 | 风控事件、账号冷却记录 |
| 系统配置 | 通知模板、代理池(托管形态)、审计查询 |
3. 客户端 App 功能清单
| 模块 | 功能点 |
|---|---|
| 安装与配对 | 安装包、配对码绑定工作区、首次引导 |
| 连接管理 | WSS 连接状态、心跳、自动重连、延迟诊断 |
| 凭据保险库 | 本地加密存储(SQLCipher)、账号列表与健康度 |
| 扫码与挑战 | 二维码展示/自动刷新、验证码回填、APP确认轮询 |
| 代理管理 | 代理配置、健康检查、按账号绑定 |
| 任务执行 | 接收/幂等去重/执行/回传;浏览器档案管理;指纹配置 |
| 运维 | 本地日志、自动更新、远程指令(重启/升级/日志上传) |
4. 网页端与 App 的连接方案评估
| 方案 | 延迟 | 双向 | 穿透 | 安全 | 结论 |
|---|---|---|---|---|---|
| REST 轮询 | 秒级 | 否 | 是 | 中 | 仅作降级通道 |
| 服务器回调 Agent(Agent 开端口) | 低 | 是 | 否(需端口映射/UPnP) | 差(暴露面) | 排除 |
| SSE 单向推送 | 低 | 半 | 是 | 中 | 可选补充 |
| MQTT | 低 | 是 | 是(出站) | 高 | 进阶可选(暂不引入) |
| WSS(Agent 出站) | <50ms | 是 | 是(免内网穿透) | 高 | 主通道 |
| gRPC 双向流 | 低 | 是 | 是 | 高 | 暂不需要 |
安全依据(为什么 WSS 出站最安全):
- 零入站暴露面:Agent 不监听任何端口,攻击者无处下手。
- 设备级身份:安装时生成 Ed25519 密钥对,配对时注册公钥;连接与关键消息均签名,不可伪造。
- 单一信任锚:Agent 只信任我方服务器;所有指令经服务器签名下发,杜绝中间人。
- 可降级:企业防火墙拦截 WSS 时自动降级 HTTPS 长轮询(同样 TLS+签名),可用性不降级。
- 证书固定(可选):TLS 1.3 + 证书指纹固定,防内网 DNS 劫持。
5. 鉴权体系(四层)
| 对象 | 方式 | 凭证 | 存储 | 吊销 |
|---|---|---|---|---|
| 网页用户 | 密码(Argon2id)+ TOTP 2FA(可选,默认关闭,管理员可开启) | Access JWT 15min + Refresh Cookie 7d(轮换) | 服务器 | 会话管理/登出全部设备 |
| Agent 设备 | 配对码 + Ed25519 签名 | agentToken(长期可吊销) | 服务器存公钥;App 本地存私钥(DPAPI/SQLCipher) | 控制台吊销设备 |
| 平台官方 API(X/IG/YouTube) | OAuth2 + PKCE | refresh token | 服务器 KMS 信封加密 | OAuth 应用撤销 |
| 国内平台账号 | 官方扫码登录 | cookie/token | 仅 Agent 本地保险库,服务器零凭据 | 远程失效→重新扫码 |
| 挑战输入(验证码等) | WSS 加密通道 | 不适用 | 不落日志、不持久化明文 | 会话结束即弃 |
6. REST API 清单(网页端 → 服务器)
| 模块 | 端点 | 说明 |
|---|---|---|
| 认证 | POST /api/auth/login、/refresh、/logout、/2fa/verify | 登录/刷新/登出/两步验证 |
| 工作区 | GET/PUT /api/workspace、POST /api/members/invite、PATCH /api/members/:id | 工作区与成员 |
| 账号台账 | GET /api/accounts、POST /api/accounts/bind、DELETE /api/accounts/:id、GET /api/accounts/:id/health | 绑定发起即生成挑战 |
| 素材 | GET/POST /api/materials、POST /api/materials/presign、DELETE /api/materials/:id | 直传签名与元数据 |
| 任务 | GET/POST /api/tasks、POST /api/tasks/:id/submit/approve/reject/retry/cancel、GET /api/tasks/:id/events | 任务生命周期 |
| 挑战 | GET /api/challenges、POST /api/challenges/:id/resolve | resolve 载荷:验证码/确认结果 |
| 审计 | GET /api/audit-logs | 全操作日志 |
| 通知 | GET /api/notifications、POST /api/notifications/read | 通知中心 |
| Agent 管理 | POST /api/agents/pairing、GET /api/agents、POST /api/agents/:id/revoke、POST /api/agents/:id/cmd | 配对码/吊销/远程指令 |
| 内部后台 | GET /api/admin/tenants、/agents、/adapters、/risk-events | 平台角色专属,租户域隔离 |
7. WebSocket 消息协议
| 方向 | 消息 | 载荷要点 |
|---|---|---|
| A→S | hello | deviceId + 签名 + 协议版本(连接即鉴权,只此一次) |
| A→S | heartbeat | 10s 间隔;服务器 25s 未收判定离线 |
| S→A | task.push | taskId + 加密载荷(素材URL/标题/平台/账号) |
| A→S | task.ack / task.result | 幂等确认 / 结果+截图URL+失败原因分类 |
| A→S | challenge.push | 类型(qr/confirm/sms/captcha)+ 凭证 |
| S→A | challenge.resolve | 用户输入结果(验证码等,端到端加密) |
| S→A | agent.cmd / config.update | 重启/升级/配置下发(签名) |
| 浏览器↔S | 原生 WebSocket(JSON 协议) | task / challenge / notification 订阅推送(JWT 鉴权) |
8. 关键流程
8.1 网页用户登录(标准)
账号密码 → 签发 Access JWT(15min) + Refresh Cookie(7d 轮换) → 进入工作区。TOTP 2FA 为可选能力,默认关闭,管理员可对工作区开启;异常登录(新设备/异地)触发告警通知。
8.2 Agent 配对(一次性)
sequenceDiagram
participant U as 用户
participant W as 网页端
participant S as 服务器
participant A as App(Agent)
U->>W: 登录(密码+TOTP 2FA)
W->>S: POST /api/auth/login
S-->>W: JWT(15min) + refresh cookie
U->>W: 工作区→设备→添加设备
W->>S: POST /api/agents/pairing
S-->>W: 配对码(6位,5分钟有效)
U->>A: 输入配对码
A->>A: 生成Ed25519密钥对+deviceId
A->>S: WSS auth(deviceId,配对码,公钥,签名)
S->>S: 验证→注册公钥→签发agentToken(可吊销)
S-->>A: 绑定成功
A->>A: 私钥入本地保险库(DPAPI/SQLCipher)
8.3 平台账号绑定(沿用 §22 优化登录)
控制台发起绑定 → 服务器生成挑战 → Agent API 取码(B站等)或温浏览器取码(视频号)→ 只传 token/URL → 控制台重渲染 → 用户扫码 → Agent 短轮询检测 → 换取 cookie 入保险库 → 台账变绿。
8.4 发布任务全链路(沿用 §21 全链路时序)
9. 边界问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 企业防火墙拦截 WSS | 443 端口 + wss;仍失败则自动降级 HTTPS 长轮询(同签名) |
| 服务器重启/网络抖动 | Redis 持久队列(asynq)+ 重投 + Agent 断线重连 + taskId 幂等 ack |
| 重复执行 | taskId 全局唯一,Agent 本地状态表去重 |
| 大文件挤占消息通道 | 素材走 OSS 预签名直传,WSS 只传元数据 + SHA-256;断点续传 |
| 两端时钟不同步(签名 nonce) | 配对时同步服务器时间 + nonce 宽限窗口 |
| 并发挑战冲突 | 账号级互斥锁:一账号同一时刻仅一个挑战 |
| 版本不兼容 | 协议 version 字段 + zod schema 校验,旧版 App 只读降级 |
| 验证码泄露 | 挑战消息端到端加密、日志脱敏、不持久化明文 |
| 排期时区错乱 | 统一 UTC 存储,控制台按本地时区渲染 |
| 托管形态代理失效 | 池健康检查自动摘除 + 同区域切换 + 告警 |
10. 核心数据模型
数据库引擎:MySQL 8.x(GORM);Redis 承载队列与缓存(asynq)。
| 表 | 关键字段 | 说明 |
|---|---|---|
| tenant | id, name, plan, status | 租户(客户/内部) |
| user | id, tenant_id, phone/email, password_hash, totp_secret, role | 平台角色独立域 |
| platform_account | id, tenant_id, platform, status, health, proxy_binding | 台账(无凭据字段) |
| agent_device | id, tenant_id, device_id, public_key, token_hash, status, version | 设备(公钥,无私钥) |
| material | id, tenant_id, oss_key, sha256, meta | 素材 |
| task | id, tenant_id, material_id, targets, schedule_at, status, retry_count | 发布任务 |
| task_event | id, task_id, type, payload, ts | 状态事件流 |
| challenge | id, account_id, type, status, expires_at | 挑战(凭证加密) |
| audit_log | id, tenant_id, actor, action, target, ts | 审计 |
| notification | id, tenant_id, user_id, channel, content, read_at | 通知 |
11. 安全清单
- 全链路 TLS 1.2+(HTTPS/WSS);密码 Argon2id;登录限速;异常登录告警;TOTP 2FA 可选(默认关闭)。
- 租户数据行级隔离(tenant_id 强制注入);内部后台独立角色域。
- 敏感字段 KMS 信封加密;日志脱敏;挑战内容不落日志。
- Agent 指令签名校验;设备可远程吊销。
- 依赖扫描 + 常规安全审计(SAST/依赖 CVE)。
12. 传输体系设计
12.0 分期策略(一期本地,二期接桶)
- 一期(首版):不接入存储桶。素材存服务器本地磁盘;运营 multipart 直传服务器;Agent 用带短时效签名 token 的直链下载(HTTP 直链,不做分片续传)。大文件同样不走 WSS。
- 二期:接 OSS 双桶(国内/海外)、预签名 URL、Range 分片断点续传、本地缓存 LRU(即 §12.1-12.5 全部能力)。
- 工程要求:存储层必须抽象为 StorageDriver 接口(一期 local 实现 / 二期 oss 实现),上传、下载、元数据三处调用全部经接口——二期切换零业务改动。
12.1 三链路分离(控制面轻、数据面直)
| 链路 | 内容 | 通道 | 典型大小 | 续传 | 加密 |
|---|---|---|---|---|---|
| 控制面 | 任务/状态/挑战/心跳 | WSS | <1KB | 幂等重发 | TLS + 签名 |
| 数据面·上传 | 运营浏览器 → 素材桶 | HTTPS 直传(预签名) | 10MB ~ 数GB | 分片续传 | TLS + 私有桶签名 |
| 数据面·下发 | 素材桶 → Agent | HTTPS Range 下载 | 10MB ~ 数GB | Range 续传 | TLS + 短时效签名 |
| 数据面·回传 | 截图 → 素材桶 | HTTPS 直传(预签名) | 100-500KB | 无需 | TLS + 签名 |
铁律:大文件绝不走 WSS(避免阻塞消息通道);WSS 只传元数据与短时效下载 URL。一期由服务器本地盘存素材字节(单机磁盘即够);二期切换 OSS 后服务器只存元数据(oss_key/sha256/大小)。
12.2 全链路传输流程
注:图中「素材桶 OSS」为二期形态;一期将 OSS 替换为服务器本地盘(见 §12.0 分期策略),其余链路不变。
flowchart TB
OP["运营(网页端)"] -->|"① 分片直传(预签名,断点续传)"| OSS["素材桶 OSS"]
OP -->|"② 元数据 API(oss_key/sha256)"| S["服务器"]
S -->|"③ WSS task.push + 短时效下载URL"| A["Agent"]
A -->|"④ Range分片下载+SHA256校验+本地缓存"| OSS
A -->|"⑤ 上传发布"| P["目标平台"]
P -->|"⑥ 回执+截图"| A
A -->|"⑦ 截图直传OSS(预签名)"| OSS
A -->|"⑧ WSS task.result(URL+状态)"| S
S -->|"⑨ WSS 实时状态推送"| OP
12.3 断点续传与完整性
- 浏览器上传:OSS 分片上传(SDK 原生),失败自动重传分片。
- Agent 下载:HTTP Range 分段 + 本地 .part 文件,完成后整体 SHA-256 校验(与 DB 元数据比对),失败重下。
- 预签名 URL 过期:Agent 向服务器换新 URL(协议预留 refresh 消息)。
12.4 区域与成本
- 双桶架构:国内桶(阿里云 OSS/腾讯 COS)+ 海外桶(Cloudflare R2 或 S3),按任务目标平台就近分配——国内平台任务用国内桶、国际平台任务用海外桶,避免跨境传输慢。
- 本地缓存 + LRU:重复任务/重试不重复下载,降低 OSS 流量成本。
- 进阶:大客户可绑定自有存储(数据不出门)。
12.5 安全
- 私有桶 + 预签名 URL(上传 15min、下载 2h 短时效);URL 只经加密通道下发,不落日志。
- 上传白名单:仅视频/图片 MIME + 大小上限。
- 素材本身最终要公开到平台,HTTPS 即够;凭据/挑战类数据才端到端加密。