Files
EveryPublish/docs/system-design.md
T

14 KiB
Raw Blame History

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 出站最安全):

  1. 零入站暴露面:Agent 不监听任何端口,攻击者无处下手。
  2. 设备级身份:安装时生成 Ed25519 密钥对,配对时注册公钥;连接与关键消息均签名,不可伪造。
  3. 单一信任锚:Agent 只信任我方服务器;所有指令经服务器签名下发,杜绝中间人。
  4. 可降级:企业防火墙拦截 WSS 时自动降级 HTTPS 长轮询(同样 TLS+签名),可用性不降级。
  5. 证书固定(可选):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 即够;凭据/挑战类数据才端到端加密。