Files
EveryPublish/docs/system-design.md
T

234 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 配对(一次性)
```mermaid
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 分期策略),其余链路不变。
```mermaid
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 即够;凭据/挑战类数据才端到端加密。