commit bf25d8beac3a1eafc8063c997acd810af48b718b Author: Qiufeng Date: Thu Aug 20 20:38:21 2026 +0800 docs: 项目文档、设计图、工具、协议模块、部署说明 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8b3be9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# macOS +.DS_Store + +# 依赖(所有层级) +node_modules/ + +# 顶层 dist(仅保留部署说明与编排文件,二进制/zip 不入库) +/dist/* +!/dist/deploy/ +/dist/deploy/* +!/dist/deploy/deploy-README.md +!/dist/deploy/docker-compose.yml +!/dist/deploy/.env.example + +# 前端构建产物 +apps/web/dist/ + +# C# / .NET 构建产物 +bin/ +obj/ + +# 二进制与归档(任意层级) +*.exe +*.blockmap +*.zip +builder-debug.yml +client/dist/ # agent-core 编译产物(win/darwin) +client/desktop/icons/ # 生成图标(gen-icon.js/gen-ico.js 可重生成) + +# 本地运行数据与密钥(绝不入库) +server/data/ +server/.env +client/core/data/ +client/desktop/release/ +client/desktop/win-unpacked/ +*.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5ccc20b --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: run build test tidy infra-up infra-down health + +run: ## 本地运行服务 + cd server && go run . + +build: ## 编译二进制 + cd server && go build -o ../dist/everypublish-server . + +test: ## 单测 + cd server && go test ./... + +tidy: ## 整理依赖 + cd server && go mod tidy + +infra-up: ## 启动 MySQL/Redis(Docker) + docker compose -f server/docker-compose.yml up -d + +infra-down: ## 停止 MySQL/Redis + docker compose -f server/docker-compose.yml down + +health: ## 健康检查 + curl -s http://127.0.0.1:8090/health diff --git a/README.md b/README.md new file mode 100644 index 0000000..0218f4e --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# EveryPublish 项目总文档(唯一入口) + +**一句话定位**:企业多平台官方账号的发布中台——网页控制台协作、审核、排期,客户端单点持号、自动发布。 + +> 当前状态:开发进行中(阶段一网页端 D1)。进度跟踪见 docs/progress.md。 + +## 文档地图(全部文档一览) + +| # | 文档 | 用途 | 主要读者 | 状态 | +|---|---|---|---|---| +| 1 | [docs/prd.md](docs/prd.md) | 产品需求:做什么、给谁用、体验与验收要点 | 全员 | v1.0 待评审 | +| 2 | [docs/tech-stack.md](docs/tech-stack.md) | 技术栈定版(唯一权威来源) | 开发 | 定版 | +| 3 | [docs/system-design.md](docs/system-design.md) | 系统设计:功能清单/API/鉴权/传输/数据模型 | 开发 | v1 | +| 4 | [docs/logic-diagrams.md](docs/logic-diagrams.md) | 11 张逻辑图全集(架构/时序/状态机/排期) | 全员 | 完成 | +| 5 | [docs/delivery-plan.md](docs/delivery-plan.md) | 日计划 D1-D13 + 每步验收标准 + 风险 | 开发/管理 | 完成 | +| 6 | [docs/page-wireframes.md](docs/page-wireframes.md) | 全部 22 个页面的 ASCII 线框图 | 前端 | 完成 | +| 7 | [docs/frontend-guide.md](docs/frontend-guide.md) | starter 组件清单/场景映射/过渡效果/套用规范 | 前端 | 完成 | +| 8 | [docs/dev-schedule.md](docs/dev-schedule.md) | AI 加速排期(参考,已被 #5 取代) | 管理 | 归档 | +| 9 | [docs/multi-platform-publish-plan.md](docs/multi-platform-publish-plan.md) | 方案总纲:调研/开源聚合/IP风控/协议考证 | 全员(背景) | 完成 | +| 10 | [docs/progress.md](docs/progress.md) | 开发进度跟踪:逐日验收状态实时更新 | 全员 | 进行中 | +| 12 | [docs/web-test-report.md](docs/web-test-report.md) | 网页端测试报告(检查点①) | 全员 | 已验收 ✅ | +| 13 | [docs/client-test-checklist.md](docs/client-test-checklist.md) | Windows 客户端测试清单(检查点②) | 开发/测试 | 待验收 | +| 11 | [docs/api.md](docs/api.md) | 服务端接口文档(认证/工作区/成员/账号/素材/任务/挑战/通知/审计) | 开发 | v1 | + +## 阅读路径 + +- **产品评审**:1 → 4 → 5 +- **开发开工**:2 → 3 → 5 → 7 → 6 +- **新人上手**:1 → 9 → 4 → 3 +- **向客户/上级汇报**:1(定位与范围)+ 5(排期) + +## 关键决策速览 + +| 项 | 决策 | +|---|---| +| 技术栈 | React+TDesign(页面)/ Go(网页端服务层+客户端核心)/ WinUI 3(客户端壳)/ MySQL 8 + Redis(数据) | +| MVP 范围 | 网页端全部 + Windows 客户端 + 抖音/快手/小红书/B站;素材本地盘直链 | +| 二期 | 视频号、官方API通道(X/IG/YouTube/TikTok)、OSS、托管Agent | +| 连接方式 | WSS 主通道(Agent 出站,Ed25519 设备签名)+ HTTPS 降级;大文件直链不走 WSS | +| 鉴权 | 用户 JWT+refresh;Agent 配对码+设备密钥;平台账号 cookie 仅存客户端本地 | +| 职责边界 | 网站=分配任务+收状态(零平台凭据);客户端=持号+登录鉴权+发布执行(凭据仅本机) | +| 红线 | 矩阵养号/搬运去重绕过/刷量/私信轰炸一律不做 | + +## 目录结构 + +- apps/web —— 前端(TDesign starter,MIT) +- server/ —— Go 服务器(REST + WSS 网关 + 队列;端口 8090) +- shared/ —— 协议 Go module(server 与 agent-core 共用) +- client/ —— Windows 客户端(ui WinUI3 + core agent-core,D8 起) +- docs/ —— 全部文档(见上表) +- diagrams/png —— 11 张逻辑图 PNG(2400px 宽,白底);diagrams/mmd —— mermaid 源文件 +- tools/ —— 联调诊断(diag-connection.mjs) + +## 开发进行中(阶段一 D1-D7) + +- 排期:docs/delivery-plan.md(含检查点①网页端测试 / 检查点②客户端测试,均需用户确认) +- 甘特图:diagrams/png/11-开发排期甘特.png +- 本地启动:`make infra-up`(MySQL)→ `make run`(Go 后端 :8090)→ `cd apps/web && pnpm dev`(前端 :3003) +- 部署态(单进程):`cd apps/web && pnpm build` 后直接 `make run`,Go 服务在 :8090 同时托管前端 + API + WSS(无需 vite) diff --git a/diagrams/mmd/01-总体架构-双通道).mmd b/diagrams/mmd/01-总体架构-双通道).mmd new file mode 100644 index 0000000..23e4df7 --- /dev/null +++ b/diagrams/mmd/01-总体架构-双通道).mmd @@ -0,0 +1,27 @@ +flowchart TB + subgraph WEB["① 控制端 Web 多租户SaaS"] + U["运营 / 审核 / 管理员"] + W["任务编排 · 素材库 · 账号台账 · 审计"] + U --- W + end + subgraph CORE["② 调度中心 服务器"] + Q["任务队列 Redis(asynq) · 状态机 · 排期器"] + API["通道A 官方API执行器 OAuth"] + AGW["通道B Agent网关 WSS注册/心跳/路由"] + end + subgraph AGENT["③ 执行端 Agent 客户侧安装"] + WS["WSS长连接 主动出网回连"] + VAULT["凭据保险库 cookie本地加密"] + ROUTER["代理路由器 一账号一IP"] + PA["浏览器执行器 go-rod CDP 独立档案"] + QR["扫码登录面板"] + end + WEB -->|"任务下发"| Q + Q -->|"通道A 官方API"| API + Q -->|"通道B 自动化"| AGW + AGW --> WS --> ROUTER --> PA + VAULT --- PA + QR --- VAULT + API --> INT["国际 X/IG/YouTube/TikTok"] + PA --> DOM["国内 抖音/快手/小红书/视频号/B站"] + PA --> INT \ No newline at end of file diff --git a/diagrams/mmd/02-控制面-数据面分离.mmd b/diagrams/mmd/02-控制面-数据面分离.mmd new file mode 100644 index 0000000..ef88a12 --- /dev/null +++ b/diagrams/mmd/02-控制面-数据面分离.mmd @@ -0,0 +1,14 @@ +flowchart TB + subgraph CP["控制面 我方服务器 无平台流量"] + WEB["控制台/API"] --- Q["队列/排期/状态机"] + Q --- GW["Agent网关 WSS"] + end + subgraph DP["数据面 客户Agent 全部平台流量"] + WS["WSS客户端"] --- EX["任务执行器"] + EX --- V["凭据保险库"] + EX --- PR["代理路由"] + end + CP -->|"任务元数据 WSS <1KB"| DP + DP -->|"结果/挑战 WSS"| CP + DP -->|"上传/发布 平台流量"| P["目标平台"] + CP -.->|"绝不触达"| P \ No newline at end of file diff --git a/diagrams/mmd/03-MVP-部署拓扑-内网联调).mmd b/diagrams/mmd/03-MVP-部署拓扑-内网联调).mmd new file mode 100644 index 0000000..b1c3596 --- /dev/null +++ b/diagrams/mmd/03-MVP-部署拓扑-内网联调).mmd @@ -0,0 +1,11 @@ +flowchart LR + subgraph M["Mac 开发机 内网测试环境"] + WEB["网页端 dev"] --> S["服务器 API + WSS网关"] + end + subgraph W["Windows 客户机"] + APP["客户端App Agent
WinUI壳+Go执行核心"] + end + W -->|"WSS ws://局域网IP:端口"| S + S --> DB[("MySQL + Redis")] + S --> FS[("素材 服务器本地盘")] + APP -->|"直链下载素材 带签名token"| FS \ No newline at end of file diff --git a/diagrams/mmd/04-IP-路由逻辑.mmd b/diagrams/mmd/04-IP-路由逻辑.mmd new file mode 100644 index 0000000..08e9948 --- /dev/null +++ b/diagrams/mmd/04-IP-路由逻辑.mmd @@ -0,0 +1,8 @@ +flowchart LR + ACC["账号台账"] --> REG{"账号归属地?"} + REG -->|国内平台| POOL1["国内住宅IP池
芝麻/快代理/922S5等"] + REG -->|国外平台| POOL2["海外住宅IP池
BrightData/IPRoyal等"] + POOL1 --> BIND["一账号绑定一IP
长期固定不轮换"] + POOL2 --> BIND + BIND --> CTX["浏览器Context级注入
非全局TUN 防串账号"] + CTX --> EXEC["执行发布"] \ No newline at end of file diff --git a/diagrams/mmd/05-重试与降级状态机.mmd b/diagrams/mmd/05-重试与降级状态机.mmd new file mode 100644 index 0000000..a16c2c4 --- /dev/null +++ b/diagrams/mmd/05-重试与降级状态机.mmd @@ -0,0 +1,17 @@ +stateDiagram-v2 + [*] --> 队列中 + 队列中 --> 执行中: Agent认领 + 执行中 --> 已发布: 平台回执成功 + 执行中 --> 网络重试: 网络/超时错误 + 网络重试 --> 执行中: 退避1m/5m/15m/1h + 网络重试 --> 失败终态: 超上限N次 + 执行中 --> 挑战中: 需扫码/验证码 + 挑战中 --> 执行中: 用户完成挑战 + 挑战中 --> 挂起: 挑战超时 + 挂起 --> 执行中: 用户稍后处理 + 执行中 --> 账号冷却: 风控拦截 + 账号冷却 --> 执行中: 冷却结束+人工确认 + 账号冷却 --> 失败终态: 人工放弃 + 失败终态 --> 队列中: 人工一键重发 + 已发布 --> [*] + 失败终态 --> [*] \ No newline at end of file diff --git a/diagrams/mmd/06-全链路时序-含异常分支).mmd b/diagrams/mmd/06-全链路时序-含异常分支).mmd new file mode 100644 index 0000000..183a497 --- /dev/null +++ b/diagrams/mmd/06-全链路时序-含异常分支).mmd @@ -0,0 +1,38 @@ +sequenceDiagram + participant OP as 运营 + participant S as 服务器 + participant A as Agent + participant PL as 平台 + participant IT as 账号负责人 + Note over A,S: 单条WSS长连接复用(实测RTT 0.07ms) + OP->>S: 提交任务(素材/平台/排期) + S->>S: 校验→审核→入队→排期触发 + S->>A: task.push(taskId,加密载荷) + A-->>S: ack(taskId) 幂等确认 + A->>A: 选账号→注入代理→协议级登录态校验(不启浏览器) + alt 登录态正常 + A->>PL: 冷启动浏览器→上传素材→提交发布 + PL-->>A: 发布回执+截图 + A->>S: task.result(success) + S-->>OP: 实时状态+链接 + else 平台触发二次验证 + PL-->>A: 挑战(扫码/短信/APP确认/滑块) + A->>S: challenge.push(类型,凭证) + S-->>IT: 控制台+企微提醒 + IT->>S: 扫码或输入验证码 + S->>A: challenge.resolve(结果) + A->>PL: 继续发布流程 + else 网络类失败 + A->>A: 指数退避 1m/5m/15m/1h + A->>PL: 自动重试上传 + else 登录态失效 + A->>S: challenge.push(qr) + S-->>IT: 推送扫码提醒 + IT->>S: 扫码成功 + S->>A: challenge.resolve + A->>PL: 恢复执行 + else 风控拦截 + A->>S: task.result(fail,原因) + S->>S: 账号冷却+人工队列 + S-->>OP: 告警通知 + end \ No newline at end of file diff --git a/diagrams/mmd/07-二次验证挑战兜底流程.mmd b/diagrams/mmd/07-二次验证挑战兜底流程.mmd new file mode 100644 index 0000000..e806e23 --- /dev/null +++ b/diagrams/mmd/07-二次验证挑战兜底流程.mmd @@ -0,0 +1,13 @@ +flowchart TB + TR["平台触发二次验证"] --> DET{"Agent检测挑战类型"} + DET -->|二维码登录| QR["取码→推控制台+企微
码过期自动刷新"] + DET -->|APP确认| CF["推送手机确认提醒
Agent轮询登录态"] + DET -->|短信/邮箱| SMS["控制台弹输入框
用户输入→回传回填"] + DET -->|滑块/点选| CAP["默认截图推人工
低风控平台本地尝试"] + QR --> OK{"完成?"} + CF --> OK + SMS --> OK + CAP --> OK + OK -->|是| CONT["Agent继续发布流程"] + OK -->|否/超时| PEND["任务挂起→人工队列
一键重发/放弃"] + PEND --> CONT \ No newline at end of file diff --git a/diagrams/mmd/08-登录链路优化时序.mmd b/diagrams/mmd/08-登录链路优化时序.mmd new file mode 100644 index 0000000..fb8a737 --- /dev/null +++ b/diagrams/mmd/08-登录链路优化时序.mmd @@ -0,0 +1,22 @@ +sequenceDiagram + participant U as 用户(手机) + participant C as 控制台/企微 + participant S as 服务器 + participant A as Agent + participant P as 平台 + Note over A,P: 首选API直取(无浏览器, B站已验证) + A->>P: HTTP取码 0.1-0.5s 得token+图片URL + A->>S: challenge.push(token) 实测<50ms + S->>C: WS推码 并行推企微/托盘 + C->>C: 本地重渲染二维码(非截图) + par 用户扫码 3-10s + U->>U: 手机扫码确认 + and Agent短轮询检测 + loop 300-500ms + A->>P: 轮询扫码状态(或网络钩子) + end + end + A->>P: API换取cookie 0.2-0.5s + A->>A: 存入凭据保险库 + A->>S: challenge.resolve(已登录) + S-->>C: 状态变绿 \ No newline at end of file diff --git a/diagrams/mmd/09-Agent-配对时序.mmd b/diagrams/mmd/09-Agent-配对时序.mmd new file mode 100644 index 0000000..79cc8dc --- /dev/null +++ b/diagrams/mmd/09-Agent-配对时序.mmd @@ -0,0 +1,17 @@ +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) \ No newline at end of file diff --git a/diagrams/mmd/10-素材传输链路-一期本地盘-二期OSS).mmd b/diagrams/mmd/10-素材传输链路-一期本地盘-二期OSS).mmd new file mode 100644 index 0000000..0761058 --- /dev/null +++ b/diagrams/mmd/10-素材传输链路-一期本地盘-二期OSS).mmd @@ -0,0 +1,9 @@ +flowchart TB + OP["运营(网页端)"] -->|"① multipart直传(一期服务器本地盘)"| FS["素材存储 一期本地盘/二期OSS"] + OP -->|"② 元数据 API(sha256)"| S["服务器"] + S -->|"③ WSS task.push + 短时效下载URL"| A["Agent"] + A -->|"④ 直链下载(带签名token)"| FS + A -->|"⑤ 上传发布"| P["目标平台"] + P -->|"⑥ 回执+截图"| A + A -->|"⑦ WSS task.result(URL+状态)"| S + S -->|"⑧ WSS 实时状态推送"| OP \ No newline at end of file diff --git a/diagrams/mmd/11-开发排期甘特.mmd b/diagrams/mmd/11-开发排期甘特.mmd new file mode 100644 index 0000000..803e8dd --- /dev/null +++ b/diagrams/mmd/11-开发排期甘特.mmd @@ -0,0 +1,25 @@ +gantt + title EveryPublish 交付排期(1人+AI · 3阶段2检查点) + dateFormat YYYY-MM-DD + axisFormat %m-%d + section 阶段一 网页端(D1-D7) + D1 工程化与骨架 :A1, 2026-08-20, 1d + D2 认证与工作区API :A2, after A1, 1d + D3 业务API(账号/素材/任务) :A3, after A2, 1d + D4 WSS网关+假执行器 :A4, after A3, 1d + D5 页面1(登录/仪表盘/成员/设置) :A5, after A4, 1d + D6 页面2(素材/账号/任务/日历) :A6, after A5, 1d + D7 页面3(审核/挑战/审计/admin) :A7, after A6, 1d + 检查点1 网页端手动测试(暂停) :milestone, M1, after A7, 1d + section 阶段二 Windows客户端(D8-D11) + D8 agent-core(Go) :B1, after M1, 1d + D9 WinUI3壳+托盘+watchdog :B2, after B1, 1d + D10 B站真实发布(无浏览器) :B3, after B2, 1d + D11 抖音/快手/小红书(CDP) :B4, after B3, 1d + 检查点2 交付用户测试(暂停) :milestone, M2, after B4, 1d + section 阶段三 联调与交付(D12-D15) + D12 联调+异常注入 :C1, after M2, 1d + D13 打包+内部试用 :C2, after C1, 1d + D14-15 缓冲(平台逆向) :C3, after C2, 2d + section 二期(预留) + 视频号+官方API通道 :F1, after C3, 21d diff --git a/diagrams/mmd/puppeteer-config.json b/diagrams/mmd/puppeteer-config.json new file mode 100644 index 0000000..7d85458 --- /dev/null +++ b/diagrams/mmd/puppeteer-config.json @@ -0,0 +1 @@ +{"args":["--no-sandbox","--disable-gpu"]} \ No newline at end of file diff --git a/diagrams/png/01-总体架构.png b/diagrams/png/01-总体架构.png new file mode 100644 index 0000000..5590485 Binary files /dev/null and b/diagrams/png/01-总体架构.png differ diff --git a/diagrams/png/02-控制面-数据面分离.png b/diagrams/png/02-控制面-数据面分离.png new file mode 100644 index 0000000..7f0982c Binary files /dev/null and b/diagrams/png/02-控制面-数据面分离.png differ diff --git a/diagrams/png/03-MVP-部署拓扑.png b/diagrams/png/03-MVP-部署拓扑.png new file mode 100644 index 0000000..61c241d Binary files /dev/null and b/diagrams/png/03-MVP-部署拓扑.png differ diff --git a/diagrams/png/04-IP-路由逻辑.png b/diagrams/png/04-IP-路由逻辑.png new file mode 100644 index 0000000..3aded03 Binary files /dev/null and b/diagrams/png/04-IP-路由逻辑.png differ diff --git a/diagrams/png/05-重试与降级状态机.png b/diagrams/png/05-重试与降级状态机.png new file mode 100644 index 0000000..13caaae Binary files /dev/null and b/diagrams/png/05-重试与降级状态机.png differ diff --git a/diagrams/png/06-全链路时序.png b/diagrams/png/06-全链路时序.png new file mode 100644 index 0000000..7fc9648 Binary files /dev/null and b/diagrams/png/06-全链路时序.png differ diff --git a/diagrams/png/07-二次验证挑战兜底流程.png b/diagrams/png/07-二次验证挑战兜底流程.png new file mode 100644 index 0000000..4f3b3dd Binary files /dev/null and b/diagrams/png/07-二次验证挑战兜底流程.png differ diff --git a/diagrams/png/08-登录链路优化时序.png b/diagrams/png/08-登录链路优化时序.png new file mode 100644 index 0000000..4a4715e Binary files /dev/null and b/diagrams/png/08-登录链路优化时序.png differ diff --git a/diagrams/png/09-Agent-配对时序.png b/diagrams/png/09-Agent-配对时序.png new file mode 100644 index 0000000..dfd707e Binary files /dev/null and b/diagrams/png/09-Agent-配对时序.png differ diff --git a/diagrams/png/10-素材传输链路.png b/diagrams/png/10-素材传输链路.png new file mode 100644 index 0000000..528daa1 Binary files /dev/null and b/diagrams/png/10-素材传输链路.png differ diff --git a/diagrams/png/11-开发排期甘特.png b/diagrams/png/11-开发排期甘特.png new file mode 100644 index 0000000..26d19e0 Binary files /dev/null and b/diagrams/png/11-开发排期甘特.png differ diff --git a/dist/deploy/.env.example b/dist/deploy/.env.example new file mode 100644 index 0000000..2e302a3 --- /dev/null +++ b/dist/deploy/.env.example @@ -0,0 +1,13 @@ +# EveryPublish 服务器配置样例(部署包专用,已按部署包目录结构调整) +SERVER_ADDR=:8090 +MYSQL_DSN=root:everypublish@tcp(127.0.0.1:3306)/everypublish?charset=utf8mb4&parseTime=True&loc=Local +REDIS_ADDR=127.0.0.1:6379 +REDIS_PASSWORD= +JWT_SECRET=please-change-me-in-production +# 客户端(agent-core)下载素材用,必须填客户端可达的内网地址: +BASE_URL=http://<服务器内网IP>:8090 +# 浏览器侧链接(邀请链接等),同样填内网地址: +PUBLIC_BASE_URL=http://<服务器内网IP>:8090 +STORAGE_DIR=./data/materials +# 部署包自带 web/ 前端目录,按部署包路径即可: +STATIC_DIR=./web diff --git a/dist/deploy/deploy-README.md b/dist/deploy/deploy-README.md new file mode 100644 index 0000000..3d03d53 --- /dev/null +++ b/dist/deploy/deploy-README.md @@ -0,0 +1,47 @@ +# EveryPublish 服务器部署说明(内网版) + +## 目录内容 + +| 文件 | 说明 | +|---|---| +| everypublish-server-linux-amd64 | Linux x64 服务器程序(单文件,含前端+API+WSS) | +| everypublish-server-windows-amd64.exe | Windows x64 服务器程序 | +| web/ | 网页控制台静态文件(程序自动托管,无需 Nginx) | +| docker-compose.yml | MySQL + Redis 一键编排 | +| .env.example | 配置样例 | + +## 部署步骤(Linux 示例) + +1. 依赖 MySQL 8 + Redis 7(有现成实例可跳过本步): + `docker compose up -d`(或自建 MySQL 库 everypublish + Redis) +2. 准备配置(可选,默认即可用): + `cp .env.example .env`,改 MYSQL_DSN / REDIS_ADDR / JWT_SECRET +3. 启动: + `./everypublish-server-linux-amd64`(默认监听 0.0.0.0:8090,自动建表) +4. 验证:浏览器访问 `http://<服务器内网IP>:8090` → 出现 EveryPublish 登录页即成功。 +5. 防火墙放行 8090 端口(仅内网使用可不放行公网)。 + +## 使用(连接方式) + +1. 浏览器打开 `http://<服务器内网IP>:8090` → **注册账号**(开放注册)→ 登录。 +2. Windows 客户端(安装包)「设置」页填服务器地址 `http://<服务器内网IP>:8090` → 保存。 +3. 网页控制台「设置 → Agent 设备」生成**配对码** → 客户端「配对」页输入 → 完成绑定。 +4. 网页端:账号管理 → 添加平台账号并绑定(客户端扫码/验证)→ 素材库上传 → 新建发布 → 审核通过 → 客户端自动执行。 + +## 常用配置(.env) + +``` +SERVER_ADDR=:8090 # 监听端口 +MYSQL_DSN=root:密码@tcp(127.0.0.1:3306)/everypublish?charset=utf8mb4&parseTime=True&loc=Local +REDIS_ADDR=127.0.0.1:6379 +JWT_SECRET=请改成随机长字符串 +BASE_URL=http://<服务器内网IP>:8090 # 素材直链(客户端下载用,必须客户端可达) +PUBLIC_BASE_URL=http://<服务器内网IP>:8090 # 浏览器侧链接(邀请链接等) +STORAGE_DIR=./data/materials +STATIC_DIR=./web # 前端目录(部署包自带) +``` + +## 测试账号 + +- 部署后网页端**开放注册**,直接注册即可作为测试账号(注册即建工作空间)。 +- 如需平台管理员(可见「平台管理→开发文档」等):把某账号 users.role 改为 admin(MySQL:`UPDATE users SET role="admin" WHERE email="你的邮箱";`)。 diff --git a/dist/deploy/docker-compose.yml b/dist/deploy/docker-compose.yml new file mode 100644 index 0000000..8991093 --- /dev/null +++ b/dist/deploy/docker-compose.yml @@ -0,0 +1,35 @@ +services: + mysql: + image: mysql:8.4 + container_name: everypublish-mysql + environment: + MYSQL_ROOT_PASSWORD: everypublish + MYSQL_DATABASE: everypublish + TZ: Asia/Shanghai + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-peverypublish"] + interval: 5s + timeout: 3s + retries: 30 + + redis: + image: redis:7-alpine + container_name: everypublish-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 30 + +volumes: + mysql_data: + redis_data: diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..0265997 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,161 @@ +# EveryPublish 服务端接口文档(v1) + +> 基地址:`/api/v1` · 统一响应:`{"code":0,"message":"ok","data":{...}}`,code!=0 为业务错误。 +> 认证:`Authorization: Bearer `(15min,前端自动用 refresh 轮换)。 + +## 业务错误码 + +| code | 含义 | +|---|---| +| 1001 | 参数不合法 | +| 1002 | 未登录/令牌无效 | +| 1003 | 无权限 | +| 1004 | 资源不存在 | +| 2001 | 邮箱已注册 | +| 2002 | 邮箱或密码错误 | +| 2003 | 刷新令牌无效 | +| 2004 | 已是成员 | +| 2005 | 邀请链接无效或过期 | +| 1006 | 需要两步验证(2FA,默认关) | +| 3001-3005 | 业务冲突(账号不可删/已绑定/挑战已结束/链接过期/任务状态不允许) | + +## 认证 /auth + +| 方法 | 路径 | 说明 | +|---|---|---| +| POST | /auth/register | 注册 {email,password,nickname} → 返回令牌对+用户+默认工作空间 | +| POST | /auth/login | 登录 {email,password} → 令牌对+用户 | +| POST | /auth/refresh | {refreshToken} → 轮换新令牌对(旧 refresh 立即吊销) | +| POST | /auth/logout | 登出(吊销 refresh) | +| GET | /auth/me | 当前用户+工作空间+角色 | + +## 工作空间 /workspaces(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| POST | /workspaces | 创建 {name}(创建者为 owner) | +| GET | /workspaces | 我所在的工作空间列表 | +| PUT | /workspaces/:id | 改名(owner/admin) | + +## 成员 /members(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /members | 成员列表(含 email/nickname) | +| POST | /members/invite | 邀请 {email,role} → {token,link}(owner/admin;role: admin/operator/reviewer/viewer) | +| POST | /members/join | 接受邀请 {token}(须与登录邮箱一致) | +| PUT | /members/:id/role | 改角色 {role}(不可改 owner) | +| DELETE | /members/:id | 移除(不可移除 owner) | + +## 账号台账 /accounts(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /accounts?platform=&status=&page=&size= | 台账列表(状态:unbound/binding/active/suspended/expired) | +| POST | /accounts | 新增 {platform(douyin/kuaishou/xiaohongshu/bilibili),accountName,avatarUrl?,ipProfile?} | +| PUT | /accounts/:id | 改资料(名称/头像/IP 画像) | +| DELETE | /accounts/:id | 删除(仅 unbound) | +| POST | /accounts/:id/bind | 发起绑定 → 创建挑战记录(status→binding)→ {challengeId} | + +## 素材 /materials(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /materials?kind=&group=&page=&size= | 素材列表 | +| POST | /materials | multipart 上传:file 字段 + 可选 group/tags;流式 sha256,同工作区去重(dedup:true 返回已有素材) | +| GET | /materials/:id/url | 生成签名直链(10 分钟、一次性)→ {url,expiresAt} | +| DELETE | /materials/:id | 删除素材及文件 | +| GET | /files/:token | 凭 token 下载文件(无鉴权,token 即凭证;一次性) | + +## 任务 /tasks(登录;状态机) + +```mermaid +flowchart LR + D[draft] -->|submit| P[pending_review] + P -->|approve| Q[queued] -->|dispatch| DP[dispatched] -->|start| R[running] + R -->|success| S[success] + R -->|fail| F[failed] -->|retry| Q + R -->|suspend| SU[suspended] -->|resume| Q + P -->|reject| RJ[rejected] -->|resubmit| P + D -->|cancel| C[cancelled] + Q -->|cancel| C + DP -->|cancel| C + R -->|cancel| C + SU -->|cancel| C +``` + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /tasks?status=&schedule=&page=&size= | 任务列表 | +| POST | /tasks | 新建草稿 {title,content?,tags?,accountIds[],materialIds[],scheduleAt?(unix ms),priority?(1-10)} | +| GET | /tasks/:id | 详情 | +| PUT | /tasks/:id | 编辑(仅 draft/rejected) | +| DELETE | /tasks/:id | 删除(仅 draft) | +| POST | /tasks/:id/submit | 提交审核 | +| POST | /tasks/:id/approve | 审核通过 → queued(通知创建人) | +| POST | /tasks/:id/reject | 驳回 {note}(通知创建人) | +| POST | /tasks/:id/resubmit | 驳回后重提 | +| POST | /tasks/:id/retry | 失败重试 → queued | +| POST | /tasks/:id/cancel | 取消 | + +## 挑战 /challenges(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /challenges?status=&accountId= | 挑战列表(过期自动置 expired) | +| POST | /challenges/:id/solve | 人工完成 {value?}(验证码/APP 确认;扫码由 Agent 完成) | +| POST | /challenges/:id/resend | 一键重发(expired/suspended → active,重置 30min) | +| POST | /challenges/:id/suspend | 挂起 | + +## 通知 /notifications(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /notifications?kind= | 我的通知 + unread 计数 | +| POST | /notifications/:id/read | 标记已读 | +| POST | /notifications/read-all | 全部已读 | + +## 审计 /audit-logs(登录) + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /audit-logs?userId=&action=&page=&size= | 审计日志(中间件自动埋点:登录/登出/增删改/审核/挑战等) | + +## 设备配对 /agent + +| 方法 | 路径 | 鉴权 | 说明 | +|---|---|---|---| +| POST | /agent/pair-code | Bearer | 生成配对码(6 位,5 分钟一次性) | +| POST | /agent/pair | 无(凭码) | 配对 {code,deviceName,os,version,publicKey(Ed25519 base64)} → {deviceId} | +| GET | /agent/devices | Bearer | 设备列表(在线状态) | + +## WSS 双通道 + +### Agent 通道 `/ws/agent`(Ed25519 设备签名,无 JWT) + +| 方向 | 消息 | 说明 | +|---|---|---| +| C→S | `hello` | {deviceId,nonce,ts,sig(base64, 签名原文 nonce+`\|`+ts),version} | +| S→C | `hello.ack` | {serverNonce,sessionId,serverTs,sig} | +| C→S | `heartbeat`(10-30s) / S→C `heartbeat.ack` | 保活 | +| S→C | `task.push` | 任务下发 {taskId,platform,accountId,title,content,tags,materialUrls(一次性签名直链),priority} | +| C→S | `task.ack` | {taskId,accept,reason?}(accept→running) | +| C→S | `task.result` | {taskId,status(success/failed),publishedUrl,receipts,error,finishedAt} | +| S→C | `challenge.new` | 绑定扫码挑战 {challengeId,accountId,platform,kind,qrToken,qrUrl,prompt,expiresAt} | +| C→S | `challenge.ack` / `challenge.solve` | 应答 / 完成(solve 后账号→active) | + +### 浏览器通道 `/ws/browser?token=`(只读订阅) + +| 事件 | 说明 | +|---|---| +| `task.status` | {taskId,status,errorMessage,publishedUrls}(下发/执行/成功/失败实时推送) | +| `agent.status` | {deviceId,online} | +| `challenge.status` | {challengeId,status,accountId} | + +> 实测:任务下发延迟(approve→task.push)≈ 28.7ms(本机,硬指标 <300ms)。 + +## 健康检查 + +| 方法 | 路径 | 说明 | +|---|---|---| +| GET | /health | 服务健康(db/redis 状态) | diff --git a/docs/client-install-guide.md b/docs/client-install-guide.md new file mode 100644 index 0000000..a4e050b --- /dev/null +++ b/docs/client-install-guide.md @@ -0,0 +1,32 @@ +# EveryPublish Windows 客户端 · 安装与使用说明 + +> 产物:`client/desktop/release/EveryPublish Setup 0.2.0.exe`(NSIS 安装向导,双击安装,可选安装目录、创建桌面快捷方式)。 + +## 一、安装后它能做什么 + +- **常驻后台执行**:安装后即注册开机自启(可在设置页关),关闭窗口最小化到托盘不退出。 +- **接收并执行发布任务**:网页控制台审核通过的任务自动下发到本机,自动登录平台(B站无浏览器投稿;抖音/快手/小红书驱动浏览器)并发布,结果与截图回执回传控制台。 +- **完成账号扫码绑定**:网页端发起绑定后,客户端弹挑战,展示二维码/接收验证码。 +- **凭据本地加密保存**:平台账号 cookie 只存在本机(AES-GCM 保险库),服务器零凭据。 + +## 二、需要填写什么 + +| 位置 | 填什么 | 何时填 | +|---|---|---| +| 设置页 → 服务器地址 | `http://<服务器内网IP>:8090`(不是 localhost!) | 首次启动 | +| 配对页 → 配对码 | 网页控制台「设置→Agent 设备」生成的 6 位码 | 首次绑定 | +| 配对页 → 设备名 | 任意(选填) | 首次绑定 | +| 挑战页 → 验证码 | 平台发送的短信/图形验证码 | 绑定账号时 | +| 设置页 → 模拟执行开关 | 无真实账号联调时打开 | 可选 | + +## 三、连接方式(内网场景) + +1. 服务器部署在内网某台机器(Linux/Windows),监听 8090(部署说明见 dist/deploy/deploy-README.md)。 +2. 客户端与服务器**同一内网可达**:客户端填服务器内网 IP。 +3. 客户端经 WSS(Agent 出站长连接,断线自动重连)与服务器通信——**无需服务器主动连客户端**,防火墙只需放行服务器 8090。 +4. 测试建议:设置页先开「模拟执行」,走通 配对→绑定→任务→发布成功 后再接真实平台。 + +## 四、卸载/数据 + +- 卸载:控制面板卸载 EveryPublish。 +- 数据目录(凭据/配置/截图):`%APPDATA%\EveryPublish`,卸载后如需彻底清除请手动删除。 diff --git a/docs/client-test-checklist.md b/docs/client-test-checklist.md new file mode 100644 index 0000000..405fffc --- /dev/null +++ b/docs/client-test-checklist.md @@ -0,0 +1,51 @@ +# EveryPublish Windows 客户端测试清单(检查点②) + +> 版本 0.2.0 · 交付形态:`dist/EveryPublish-Client-0.2.0-win-x64.zip`(WinUI 壳 + agent-core.exe 同目录)。 +> 前置:网页端已部署(http://<服务器>:8090 单进程),客户端与服务器网络可达。 + +## 一、构建/获取安装包 + +- 方式 A(推荐):Windows 机器上执行 `client/ui/Packaging/build-installer.ps1`(自动生成图标 → Go 交叉编译 agent-core.exe → dotnet publish → 打包 zip)。 +- 方式 B:直接用 `client/dist/agent-core.exe` + 手工编译 WinUI 壳(见 client/ui/README.md)。 + +## 二、第一轮:安装与假任务闭环(无需真实账号) + +1. 解压 zip → 双击 `EveryPublish.Client.exe`:窗口出现「未配对」,右下角托盘出现图标。 +2. 设置页:**打开「模拟执行」开关** → 保存并重启核心(此模式下所有平台走假执行器,不碰真实平台)。 +3. 网页端(管理员/owner 登录)→「设置 → Agent 设备」→ 生成配对码。 +4. 客户端「配对」页输入配对码 → 配对成功 → 「状态」页显示**在线**(服务器/设备号)。 +5. 网页端:账号管理 → 添加账号(任选平台)→ 指定刚配对的设备 → 绑定 → 客户端「挑战」页出现挑战 → 点选提交「我已确认」→ 网页端账号变**已绑定**。 +6. 网页端:素材库上传一个视频 → 新建发布(选素材+账号)→ 任务列表「提交审核」→ 审核中心「通过」→ 任务自动下发 → **变绿「发布成功」**(假执行器秒回)。 +7. 系统行为:关闭窗口 → 最小化到托盘(进程仍在);托盘右键「退出」才退出;设置页开「开机自启」→ 注销重登自动运行;任务管理器手动结束 agent-core.exe → 5 秒内看门狗自动拉起(3 次失败弹窗提示)。 + +## 三、第二轮:B站真实发布(无浏览器投稿) + +1. 设置页**关闭「模拟执行」**并保存(重启核心)。 +2. 网页端添加 B站账号 → 指定设备 → 绑定 → 客户端「挑战」页**显示 B站登录二维码**(API 生成)→ 手机 B站 App 扫码确认 → 网页端账号变已绑定(cookie 加密存入本机保险库,服务器零凭据)。 +3. 新建发布(选该 B站账号)→ 审核通过 → 客户端自动执行:preupload → 分片上传 → 投稿 → 网页端任务变成功,发布链接为 `https://www.bilibili.com/video/BV...`。 +4. 到 B站「创作中心 → 内容管理」核对稿件已上线;客户端数据目录 `receipts/` 有截图回执。 + +## 四、第三轮:抖音/快手/小红书真实发布(CDP) + +1. 分别添加对应平台账号 → 绑定 → 客户端「挑战」页显示该平台登录页二维码(CDP 截图)→ 手机 App 扫码。 +2. 新建发布 → 审核通过 → 客户端拉起浏览器(每账号独立档案,首次会下载 Chromium 或使用系统 Chrome)→ 自动上传/填表/发布 → 任务成功 + 截图回执。 +3. **选择器容错**:平台改版可能导致找不到元素——失败时查看客户端日志(数据目录下)与 `receipts/` 失败截图,把截图发我调整选择器(架构上只需改配置,无需改流程)。 + +## 五、验收标准(与九项最终验收第 6-7 项对应) + +| # | 项 | 通过条件 | +|---|---|---| +| 1 | 安装 | 解压即用,双击启动 | +| 2 | 配对 | 配对码绑定成功,状态页在线 | +| 3 | 假任务闭环 | 第一轮步骤 5-6 全通过 | +| 4 | B站真实发布 | 稿件真实上线 | +| 5 | 抖音/快手/小红书真实发布 | 至少 1 个平台真实上线(可先交付可跑子集) | +| 6 | 凭据安全 | 平台凭据仅存本机 `%APPDATA%\EveryPublish`(加密 vault),服务器零凭据 | +| 7 | 常驻行为 | 托盘/自启/watchdog 均正常 | + +## 六、常见问题 + +- **核心进程未就绪**:安装目录缺少 agent-core.exe,或看门狗 3 次失败弹窗——检查打包脚本是否复制了 core。 +- **配对码无效**:5 分钟过期,网页端重新生成。 +- **任务一直排队**:账号绑定的设备离线(IP 隔离设计),确认客户端状态页在线。 +- **日志位置**:数据目录(设置页可打开):`%APPDATA%\EveryPublish`(core 日志经看门狗输出,可用「打开数据目录」查看 config.json/local.json;进程日志在 Windows 事件查看器或直接命令行运行 `agent-core.exe -dir <目录>` 观察)。 diff --git a/docs/delivery-plan.md b/docs/delivery-plan.md new file mode 100644 index 0000000..aabffb8 --- /dev/null +++ b/docs/delivery-plan.md @@ -0,0 +1,153 @@ +# EveryPublish 交付文档(技术栈定版 · 页面构成 · 日计划) + +> 2026-08-20 · 开发顺序:网站端(含API) → 客户端(Windows) → 联调交付(含两个用户确认检查点)。基础仓库已拉取:apps/web(Tencent/tdesign-react-starter,MIT)。技术栈定版见 docs/tech-stack.md(MySQL + 前后端 Go 统一)。 + +## 1. 技术栈定版 + +| 层 | 选型 | 依据 | +|---|---|---| +| 网页端 | React 18 + TDesign React(Tencent starter,MIT) | 已拉取预览:Vite + TS + Router6 + Redux Toolkit + axios;自带后台页面与 mock,无需装饰 | +| 网页端后端(服务器) | **Go**(gin + coder/websocket + asynq + GORM) | 见 Rust/Go 对比;与 agent-core 同语言共享协议模块;页面层仍为 React+TDesign | +| 客户端 UI | **WinUI 3(C#/.NET 8)原生** | 系统原生控件,无需自写 UI;QR 用 QRCoder 本地渲染 token | +| 客户端核心 | **Go**(WSS 客户端 + go-rod CDP 自动化 + 凭据保险库) | 单二进制、无运行时、可二期抽 Windows 服务/跑 VPS | +| 数据库/队列 | **MySQL 8.x(GORM)+ Redis(asynq)** | 任务队列/排期靠 Redis;MySQL 存业务数据 | +| 素材存储 | 一期服务器本地盘(签名直链);二期 OSS | StorageDriver 抽象预留 | + +### 1.1 Rust 还是 Go:结论选 Go + +| 维度 | Go | Rust | +|---|---|---| +| 本项目规模 | 性能绰绰有余(WS 网关 goroutine-per-conn,千级连接轻松) | 性能过剩,无收益 | +| 开发与 AI 协作 | AI 生成质量高、迭代快、编译秒级 | borrow checker 调试成本高、编译慢 | +| 单二进制部署 | 静态编译、零依赖(Windows 端尤其省心) | 更小,但构建链复杂 | +| 浏览器自动化 | go-rod(CDP)成熟可用 | 生态弱 | +| Windows 常驻/服务 | kardianos/service 简单 | 复杂度高 | +| 生态(队列/WS/DB) | asynq / coder-websocket / GORM(MySQL) 齐全 | 可选少 | + +**性能保障**:①WSS 网关 goroutine-per-conn,单机万级并发无压力;②GORM(MySQL 连接池)+ Redis 承载队列;③大文件绝不走 WSS(签名直链),网关只传 <1KB 消息;④服务无状态可水平扩展;⑤验收硬指标:任务下发 <300ms(tools/diag-connection.mjs 实测基线)。 + +## 2. 样式方案 + +- **网页端**:TDesign starter 默认主题(企业后台风格,深浅色可切换),全部页面用 TDesign 组件,不写自定义样式;示例页清理后按菜单骨架填充。 +- **客户端**:WinUI 3 Fluent 原生风格(与 Windows 11 一致),托盘用 H.NotifyIcon.WinUI(或 Win32 interop),扫码/挑战用对话框页。 + +## 3. 网页端页面构成 + +### 3.1 用户端(客户工作台) + +| 菜单 | 页面 | 关键功能 | 角色 | +|---|---|---|---| +| 工作台 | 仪表盘 | 今日发布统计、待处理挑战数、Agent 在线状态、最近任务 | 全部 | +| 发布管理 | 任务列表 / 新建发布 / 排期日历 / 任务详情 | 状态筛选、批量重试/取消;选素材→平台账号→标题话题→定时/立即→提交审核;月/周视图;事件时间线+回执截图+失败原因 | 运营/审核/管理员 | +| 素材库 | 素材列表 / 上传 / 预览 | 拖拽上传(multipart+进度)、分组标签、sha256、删除 | 运营/审核 | +| 账号管理 | 账号台账 / 绑定向导 / 挑战弹窗 | 平台/绑定状态/健康度/代理/最后活跃;发起扫码→QR 弹窗;重新扫码/解绑 | 管理员 | +| 审核中心 | 待审列表 | 分平台预览、通过/驳回+批注(可配置跳过) | 审核/管理员 | +| 通知与挑战 | 挑战中心 / 通知列表 | QR 从 token 本地渲染、验证码输入、APP确认指引、超时挂起/一键重发 | 相关角色 | +| 成员与权限 | 成员列表 / 邀请 | 邀请链接、角色分配 | 管理员 | +| 设置 | 工作区资料 / 安全 / 通知渠道 / Agent 设备 | 2FA 开关(默认关)、企微/钉钉/邮件、设备列表与吊销 | 管理员 | +| 审计日志 | 日志列表 | 谁在何时做了什么、筛选 | 管理员 | + +### 3.2 平台端(admin 角色,同部署加一个角色即可) + +| 菜单 | 页面 | 功能 | +|---|---|---| +| 租户管理 | 客户列表/详情 | 状态、配额(简单)、停用 | +| Agent 总览 | 设备列表 | 在线/版本/强制吊销/远程指令 | +| 渠道状态(后置) | 适配器列表 | 平台模块状态、灰度 | +| 风控台账(后置) | 事件列表 | 风控/冷却记录 | + +## 4. 调用方式设计 + +```mermaid +flowchart LR + subgraph WEB["网页端 React+TDesign"] + U["用户端页面 / 平台端admin"] + end + subgraph SRV["Go 服务器"] + API["REST /api/*"] + WS["WSS 网关"] + end + subgraph CLIENT["Windows 客户端"] + UI["WinUI 3 壳 C#"] -->|"localhost HTTP+随机token"| CORE["agent-core Go"] + end + U -->|"REST(axios) + 实时WS(JWT)"| API + U -->|"实时状态推送"| WS + CORE -->|"WSS Ed25519设备签名"| WS + CORE -->|"素材直链下载(签名token)"| API + CORE -->|"CDP 驱动系统Chrome/Edge"| CH["浏览器"] +``` + +- ① 网页端 → 服务器:REST(axios,JWT 15min + refresh 轮换)+ 原生 WebSocket(实时状态推送)。 +- ② 客户端 WinUI 壳 → agent-core:localhost HTTP/WS + 随机 token(仅回环,配对时写入本地配置)。 +- ③ agent-core → 服务器:WSS + Ed25519 设备签名(配对码注册公钥)。 +- ④ 素材:multipart 上传 + 签名直链下载(大文件不走 WSS)。 + +## 5. 后台常驻方案(Windows) + +- **开机自启**:安装时写注册表 HKCU Run。 +- **最小化到托盘**:H.NotifyIcon.WinUI / Win32 Shell_NotifyIcon。 +- **进程模型**:WinUI 主进程(UI)spawn agent-core 子进程(随机端口+token);主进程做 watchdog——心跳检测、崩溃自拉起、3 次失败弹窗提示。 +- **断线自愈**:core 内 WSS 指数退避重连(1s/5s/15s/60s)。 +- **二期**:core 抽离为 Windows 服务(kardianos/service),支持无 UI 部署与客户 VPS 形态。 + +## 6. 开发日计划(1 人 + AI,顺序开发,D1-D13 + 2 天缓冲) + +### 阶段一 网站端(D1-D7) + +| 日 | 任务 | 验收标准 | +|---|---|---| +| D1 | 预览与工程化:starter 跑通 → 依赖升级评估(Vite2→5/TS4.8→5) → 清理示例页与 mock → 按页面清单搭菜单/路由空壳;Go 后端初始化(health/配置/DB/Redis) | dev 无报错、菜单空壳可点、/health 200、DB 连上 | +| D2 | 认证与工作区 API:Argon2 登录/refresh 轮换/登出(2FA 代码留位默认关)、成员/邀请/角色、审计埋点 | 单测过;前端登录页真实登录 | +| D3 | 业务 API:账号台账(bind→challenge)、素材(multipart+签名直链+sha256)、任务 CRUD+状态机+排期、审核动作、通知 | 单测+接口文档;curl 全接口可跑 | +| D4 | WSS 网关(hello 验签/心跳/断线、task.push/ack/result、challenge 双向) + 假执行器脚本 + 前端实时推送 | localhost 假执行器闭环:任务→下发→执行→回传→页面变绿,<300ms | +| D5 | 页面①:登录、仪表盘、成员、设置 | 页面与真实 API 联通 | +| D6 | 页面②:素材库、账号台账+绑定挑战弹窗、任务列表+新建+排期日历 | 全流程可点通(与假执行器) | +| D7 | 页面③:审核中心、挑战中心(QR 本地渲染+验证码)、通知、审计 + admin(租户列表/Agent 总览) | 角色权限正确、admin 入口可用 | + +> **检查点①(暂停)**:D7 完成后暂停目标 → 执行网页端手动测试(§7 第 1-5 项 + 全页面冒烟)→ 输出测试报告 → 等待用户确认通过后再进入阶段二。 + +### 阶段二 客户端(D8-D11) + +| 日 | 任务 | 验收标准 | +|---|---|---| +| D8 | agent-core(Go):WSS 客户端(重连/幂等)、凭据保险库(加密)、执行器框架、代理配置、挑战处理 + 假平台执行器 | core 单测过;与服务器假执行闭环 | +| D9 | WinUI 3 壳:托盘、配对引导、扫码/挑战窗口、状态页;localhost 调 core;开机自启+watchdog;打包脚本 | Windows 实机:安装→配对→假任务→回传 | +| D10 | 真实平台:B站(投稿协议,无浏览器)先行 | B站真实发布成功 | +| D11 | 抖音/快手/小红书(go-rod CDP:系统 Chrome、独立档案、指纹、代理绑定;参考 social-auto-upload) | 三平台真实发布+截图回执(可先交付可跑子集) | + +> **检查点②(暂停)**:D11 完成后暂停目标 → 打包并交付用户测试(附测试清单)→ 等待用户确认测试通过后再进入阶段三联调。 + +### 阶段三 联调与交付(D12-D13) + +| 日 | 任务 | 验收标准 | +|---|---|---| +| D12 | 内网联调+异常注入:断线重连/重复任务/登录过期挑战/失败退避;延迟基线 | 验收清单全过 | +| D13 | 打包(安装器+自动更新)、内部试用、修复、部署手册 | 新机安装走通、交付物齐 | +| D14-15 | 缓冲(主要给平台逆向) | — | + +## 7. 最终验收清单 + +1. 登录/刷新/登出/角色权限正常(2FA 默认关)。 +2. 任务全生命周期:草稿→审核→排期→队列→执行→成功/失败→重试。 +3. 任务下发 <300ms;断线重连 <10s 恢复;同任务不重复执行(幂等)。 +4. 挑战全流程:扫码/验证码/APP确认/超时挂起/一键重发。 +5. 素材:上传、签名直链下载、sha256 校验。 +6. 四平台真实发布成功 + 回执截图入库。 +7. Windows 新机:安装→配对→绑定账号→真实发布。 +8. 凭据不出本机;审计日志完整。 +9. 安装包 + 自动更新可用。 + +## 8. 交付物清单 + +- 代码:web/(网页端)、server/(Go 服务器)、client/(WinUI+agent-core)。 +- 文档:docs/prd.md(需求)、docs/tech-stack.md(技术栈)、docs/system-design.md(设计)、docs/logic-diagrams.md(逻辑图)、docs/page-wireframes.md(页面线框图)、docs/frontend-guide.md(前端指南)、本交付文档(排期)、部署手册、验收报告。 +- 工具:tools/diag-connection.mjs 联调诊断。 + +## 9. 风险与应对 + +| 风险 | 应对 | +|---|---| +| starter 依赖过旧(Vite2/TS4.8) | D1 升级评估,必要则升 Vite5+TS5,锁定 LTS Node | +| 平台逆向不可预测 | D14-15 缓冲;B站先行;卡住降级为可跑子集 | +| WinUI 托盘无官方控件 | H.NotifyIcon.WinUI 或 Win32 interop(D9 前验证) | +| 系统 Chrome 版本兼容 | go-rod 用 CDP,绑定系统 Edge 兜底 | diff --git a/docs/dev-schedule.md b/docs/dev-schedule.md new file mode 100644 index 0000000..eae2a36 --- /dev/null +++ b/docs/dev-schedule.md @@ -0,0 +1,101 @@ +# EveryPublish 开发排期 v2(AI 加速 · 日级) + +> 2026-08-20 更新:技术栈定版后的顺序开发计划(网站端→客户端→联调)见 docs/delivery-plan.md,以新文档为准。 + +> 2026-08-20 · 取代 logic-diagrams.md 图11 的传统 10 周排期。前提:1 人 + AI 编码代理并发推进。 + +## 1. 排期总览 + +**内核 6 个工作日 + 2 天风险缓冲**:D1 协议定版+假执行器闭环 → D2 网页端全部功能 → D3 客户端 Windows → D4 四平台真实接入 → D5 内网联调加固 → D6 打包试用。 + +```mermaid +gantt + title MVP 闭环 AI加速排期(1人+AI并发) + dateFormat YYYY-MM-DD + axisFormat %m-%d + section 协议与骨架 + 协议定版+单测 :D1a, 2026-08-21, 1d + 工程骨架+假执行器闭环 :D1b, 2026-08-21, 1d + section 网页端 + 网页端全部功能+WSS网关 :D2, 2026-08-22, 1d + section 客户端 + Agent框架+Electron+打包 :D3, 2026-08-23, 1d + section 平台接入(高风险) + 四平台真实发布 :D4, 2026-08-24, 2d + section 联调收尾 + 内网联调+异常加固 :D5, 2026-08-26, 1d + 打包试用+修复 :D6, 2026-08-27, 1d + section 缓冲 + D4风险缓冲 :buf, 2026-08-28, 2d +``` + +## 2. 加速逻辑(为什么能从 10 周压到 6 天内核) + +| 传统排期的串行等待 | AI 加速后的处理 | +|---|---| +| 协议讨论来回 | 本文档 §7 消息协议已定版,D1 上午直接产出 TS 协议包+单测 | +| 两端互相等接口 | 协议包是唯一耦合点:D1 定死后,网页端/客户端/测试三线完全并行 | +| 脚手架与样板代码 | AI 代理直接生成(monorepo、页面 CRUD、Electron 壳、队列 worker) | +| 平台适配逐个人工摸索 | 每平台一个 AI 子代理,参考 social-auto-upload(MIT 1.4万星)并行逆向 | +| 联调来回排期 | 假执行器先行:D1 起每天都是可联调状态 | + +## 3. 日计划 + +### D1 协议定版 + 工程骨架(验收:本机假执行器闭环) + +| 时段 | 任务 | 并行方式 | +|---|---|---| +| 上午 | packages/protocol:zod schema(hello/heartbeat/task.push/ack/result/challenge.push/resolve/agent.cmd)+ 幂等工具 + 单测 | 人+AI 共同定版 | +| 下午 | monorepo(pnpm+Turborepo)+ Drizzle 建表 + Redis/BullMQ + 假执行器脚本 + 网页最小页(任务列表/状态) | 协议定版后三线并行:web 骨架 / agent 骨架 / mock+测试 | + +**验收**:localhost 闭环——创建任务 → WSS 下发 → 假执行 → 回传 → 页面变绿;延迟 <300ms。 + +### D2 网页端全部功能(验收:全页面可用) + +- auth(无 2FA UI)/ 工作区成员角色 / 账号台账 / 素材(本地盘 multipart + 签名直链)/ 任务+排期日历 / 审核流 / 挑战中心(token 本地渲染二维码 + 验证码输入)/ 审计 / 通知。 +- WSS 网关:hello Ed25519 验签、心跳 10s/25s 超时、挑战消息双向。 +**验收**:与假执行器全流程;二维码能从 token 本地渲染并可被手机识别。 + +### D3 客户端 Windows(验收:安装→配对→假任务→回传) + +- agent-core:WSS 客户端(断线重连/幂等 ack)+ 凭据保险库(加密)+ 执行器框架 + 挑战处理器 + 代理配置。 +- Electron 壳:托盘、配对引导、扫码/挑战窗口、连接状态、日志。 +- electron-builder Windows 打包(CI,不用本机交叉编译)。 +**验收**:Windows 实机安装 → 配对 → 接假任务 → 回传结果。 + +### D4 四平台真实接入(验收:真实发布+截图回执)★最高风险 + +- 顺序:B站(投稿协议,无浏览器)先行 → 抖音/快手/小红书(Playwright,参考 social-auto-upload)并行。 +- 登录:B站 API 取码;三平台浏览器取码 + 只传 token。指纹:真实 Chrome + 每账号独立档案。 +**验收**:四平台真实发布成功,回执截图入库。 + +### D5 内网联调 + 异常加固(验收:清单全过) + +- Mac 服务器 + Windows 客户端局域网全链路;异常注入:断网重连 / 重复任务 / 登录过期挑战 / 失败退避;延迟基线 <300ms。 + +### D6 打包试用 + 修复 + +- Windows 安装包 + 自动更新 + 内部试用 + 修复。 + +## 4. AI 并发工作法 + +1. **依赖序**:协议(D1 上午)→ 三线并行(D1 下午起):A=网页端功能、B=Agent 核心+壳、C=平台 uploader(每平台一个)、D=测试/联调脚本。 +2. **人只做五件事**:协议定版、代码审查、跑验收、平台逆向卡点介入、外部资源准备(账号/代理/扫码手机)。 +3. **子代理 prompt 三件套**:协议包路径 + 对应文档章节 + 验收标准(文档已齐,直接引用)。 +4. **每日节奏**:早定目标 → AI 并行 → 晚验收;验收不过的条目次日优先。 +5. **单测+验收清单兜底 AI 代码质量**:不写测试的产出不收。 + +## 5. 风险与缓冲 + +| 风险 | 影响 | 缓冲策略 | +|---|---|---| +| D4 平台逆向不可预测 | 最可能拖期 | 预留 2 天缓冲;先 B站(确定性最高)后三平台;卡住即降级为「少一个平台」交付 | +| Windows 打包/签名坑 | D3 可能溢出 | 第一天就起 CI;不用本机交叉编译 | +| 平台风控触发 | 账号受限 | 独立档案+真实扫码+人级频率,不硬刚 | +| AI 生成代码质量 | 返工 | 单测+验收清单;关键路径(鉴权/幂等)人工复审 | + +## 6. 结论 + +- **1 人 + AI 并发:内核 6 个工作日,含缓冲 8 个日历日**;「一天跑通 MVP 闭环」= D1 假执行器闭环,完全现实(协议与验收标准已齐)。 +- **真实平台闭环的关键在 D4**:逆向工作无法被 AI 完全消除,现实预期 2 天 + 缓冲,先交付 B站/抖音/小红书/快手中的可跑子集。 +- 视频号、官方 API 通道、OSS 仍为二期(接口已预留)。 \ No newline at end of file diff --git a/docs/frontend-guide.md b/docs/frontend-guide.md new file mode 100644 index 0000000..cbf41e5 --- /dev/null +++ b/docs/frontend-guide.md @@ -0,0 +1,72 @@ +# 前端开发指南(TDesign starter 深度说明) + +> 2026-08-20 · 基于 apps/web 实际代码(starter 0.3.1)· 配套 docs/page-wireframes.md 使用。 + +## 1. 模板事实清单(实测) + +| 项 | 事实 | +|---|---| +| 技术 | React 18.2 + Vite 2.9 + TS 4.8 + React Router 6 + Redux Toolkit + axios | +| 组件库 | tdesign-react 1.15 + tdesign-icons-react 0.6(MIT,可商用) | +| 目录 | src/pages(页面) / src/router(路由=菜单) / src/layouts(布局) / src/services(接口) / src/modules(redux) / src/configs(host.ts 环境) / mock(演示数据) | +| 模板页 | List/Base(表格列表) List/Card(卡片) Form/Base(表单) Form/Step(分步向导) Detail/Base(详情) Result(结果) User(成员) Dashboard(仪表盘) Login | +| 环境 | npm run dev(真实API) / dev:mock(mock数据); host.ts 分 mock/development/test/release/site | +| 过渡 | 无路由切换动画;仅零散 hover transition 与组件自带动效(见 §4) | + +## 2. 关键机制(对接方式) + +- **路由即菜单**:src/router/modules/ 每个 .ts 一个顶级菜单;meta{title, Icon, hidden, single};children 即子菜单;isFullPage=true 为全屏页(登录页)。 +- **布局**:src/layouts/components/ AppLayout(整体) + Header + Menu + Footer + Page;新页面只需加路由+页面文件。 +- **接口**:src/services 为 axios 封装示例;把 src/configs/host.ts 的 development.API 指向 Go 服务器(如 http://localhost:8080)。 +- **状态**:Redux Toolkit(modules/user 存登录态);实时状态用原生 WebSocket(/ws,JWT 鉴权),事件 task.*/challenge.*/notification.*。 +- **mock 移除**:删除 mock 目录与 vite-plugin-mock 使用,全部走真实 API。 + +## 3. 组件清单与场景映射 + +| TDesign 组件 | 适用场景 | 本项目页面 | +|---|---|---| +| Table + Tag + Pagination + Popconfirm | 数据列表/状态/二次确认 | 任务列表/素材/账号台账/成员/审计/租户/Agent总览 | +| Form + Input + Textarea + Select + Checkbox + Radio + Switch | 表单 | 登录/新建发布/设置/成员邀请 | +| Steps | 分步向导 | 新建发布四步 | +| DatePicker / DateRangePicker | 时间选择 | 定时发布/筛选 | +| Calendar | 月历 | 排期日历(若 1.15 无此组件则用卡片网格自绘,需 D6 前验证) | +| Upload + Progress + ImageViewer | 上传/预览 | 素材库 | +| Dialog | 模态弹窗 | 绑定扫码/验证码输入/邀请 | +| Drawer | 侧滑详情 | 任务详情/审核预览 | +| Timeline | 时间线 | 任务事件流 | +| Badge + Tag | 状态角标 | 绑定状态/任务状态/通知 | +| Message + Notification + Loading + Skeleton | 全局反馈 | 全站 | +| Tabs | 分栏 | 审核分平台预览/设置子页 | +| 二维码 | TDesign 无此组件 | 唯一新增依赖:qrcode.react(token 本地渲染) | + +## 4. 过渡效果说明(结论:本期不做页面动画) + +- starter 本身**没有**路由切换动画(实测仅 hover 背景/颜色 transition 与 Tree 组件动效)。 +- TDesign 组件自带动效,直接使用:Drawer 滑入、Dialog 渐入、Message/Notification 渐入渐出、Tabs 下划线滑动、Loading 旋转。 +- 约定:页面级不做过渡动画(保持响应快、实现简单);若后续需要统一 fade,用 TDesign 主题变量 --td-anim-duration-base 统一加,本期不排。 + +## 5. 直接套用规范(每个页面怎么做) + +| 我们的页面 | 复制哪个模板 | 改动点 | +|---|---|---| +| 登录页 | pages/Login | 去第三方登录/注册,接 /api/auth/login | +| 仪表盘 | pages/Dashboard/Base | 替换统计卡片与最近任务表格 | +| 任务列表/账号台账/成员/审计 | pages/List/Base | 改 columns、操作列、接 API | +| 素材库 | pages/List/Card | 卡片项=素材缩略图+操作 | +| 新建发布 | pages/Form/Step | 四步:素材→平台账号→内容→定时提交 | +| 任务详情 | pages/Detail/Base → Drawer 化 | Timeline 事件流 + 回执截图 | +| 审核中心 | pages/List/Base + Drawer | 预览用 Tabs 分平台 | +| 设置 | pages/Form/Base + Tabs | 资料/安全/通知渠道/Agent设备 | +| admin 两页 | pages/List/Base | /admin 路由域 + 角色守卫 | + +## 6. 菜单与路由改造清单(D1 完成) + +- router/modules 改为:dashboard(工作台)、publish(发布管理: 任务列表/新建发布/排期日历)、material(素材库)、account(账号管理)、review(审核中心)、challenge(通知与挑战)、member(成员与权限)、setting(设置)、audit(审计日志)、admin(平台管理域)。 +- 任务详情为 hidden 路由(不进菜单)。 +- 权限:utils 增 auth.ts(角色→菜单过滤 + 路由守卫),角色:admin/reviewer/operator/platform_admin。 + +## 7. 前后端联调约定 + +- REST:/api/*,Header Authorization: Bearer ;401 自动 refresh 重试一次。 +- WS:原生 WebSocket,连接 /ws?token=;心跳由服务端协议统一(10s/25s)。 +- 分页约定:{ list, total, page, pageSize };错误码约定:业务码 + message,前端 Message 提示。 diff --git a/docs/logic-diagrams.md b/docs/logic-diagrams.md new file mode 100644 index 0000000..fe9e112 --- /dev/null +++ b/docs/logic-diagrams.md @@ -0,0 +1,299 @@ +# EveryPublish 逻辑图全集(最小闭环版) + +> 2026-08-20 · 汇总自 docs/multi-platform-publish-plan.md 与 docs/system-design.md,MVP 范围已标注。 + +## 图索引 + +| # | 图 | 回答的问题 | 来源 | PNG | +|---|---|---|---|---| +| 1 | 总体架构(双通道) | 系统由哪些部分组成、如何协作 | plan §3 | [PNG](../diagrams/png/01-总体架构.png) | +| 2 | 控制面/数据面分离 | 为什么我方服务器不会被封 | plan §3.1 | [PNG](../diagrams/png/02-控制面-数据面分离.png) | +| 3 | MVP 部署拓扑(内网联调) | 一期怎么跑起来(Mac+Windows) | 新增 | [PNG](../diagrams/png/03-MVP-部署拓扑.png) | +| 4 | IP 路由逻辑 | 账号与 IP 如何绑定 | plan §8 | [PNG](../diagrams/png/04-IP-路由逻辑.png) | +| 5 | 重试与降级状态机 | 失败/挑战/风控如何流转 | plan §21.2 | [PNG](../diagrams/png/05-重试与降级状态机.png) | +| 6 | 全链路时序(含异常分支) | 用户→端→平台→用户完整路径 | plan §21.1 | [PNG](../diagrams/png/06-全链路时序.png) | +| 7 | 二次验证挑战兜底流程 | 扫码/验证码/APP确认怎么处理 | plan §21.3 | [PNG](../diagrams/png/07-二次验证挑战兜底流程.png) | +| 8 | 登录链路优化时序 | 登录为何能从 10s 级压到 2s 级 | plan §22 | [PNG](../diagrams/png/08-登录链路优化时序.png) | +| 9 | Agent 配对时序 | 客户端如何安全接入 | system-design §8.2 | [PNG](../diagrams/png/09-Agent-配对时序.png) | +| 10 | 素材传输链路 | 文件怎么上传/下发/回传 | system-design §12 | [PNG](../diagrams/png/10-素材传输链路.png) | +| 11 | 开发排期甘特 | 何时完成最小闭环 | 新增 | [PNG](../diagrams/png/11-开发排期甘特.png) | + +## 图1 总体架构(双通道) + +**MVP 范围**:一期只做通道B(Agent 自动化)+ 国内四平台(抖音/快手/小红书/B站);通道A(官方API)与视频号为二期。 + +```mermaid +flowchart TB + subgraph WEB["① 控制端 Web 多租户SaaS"] + U["运营 / 审核 / 管理员"] + W["任务编排 · 素材库 · 账号台账 · 审计"] + U --- W + end + subgraph CORE["② 调度中心 服务器"] + Q["任务队列 Redis(asynq) · 状态机 · 排期器"] + API["通道A 官方API执行器 OAuth"] + AGW["通道B Agent网关 WSS注册/心跳/路由"] + end + subgraph AGENT["③ 执行端 Agent 客户侧安装"] + WS["WSS长连接 主动出网回连"] + VAULT["凭据保险库 cookie本地加密"] + ROUTER["代理路由器 一账号一IP"] + PA["浏览器执行器 go-rod CDP 独立档案"] + QR["扫码登录面板"] + end + WEB -->|"任务下发"| Q + Q -->|"通道A 官方API"| API + Q -->|"通道B 自动化"| AGW + AGW --> WS --> ROUTER --> PA + VAULT --- PA + QR --- VAULT + API --> INT["国际 X/IG/YouTube/TikTok"] + PA --> DOM["国内 抖音/快手/小红书/视频号/B站"] + PA --> INT +``` + +## 图2 控制面/数据面分离 + +我方服务器只流转任务元数据(<1KB),绝不触达平台;全部平台流量从客户 Agent 发出。 + +```mermaid +flowchart TB + subgraph CP["控制面 我方服务器 无平台流量"] + WEB["控制台/API"] --- Q["队列/排期/状态机"] + Q --- GW["Agent网关 WSS"] + end + subgraph DP["数据面 客户Agent 全部平台流量"] + WS["WSS客户端"] --- EX["任务执行器"] + EX --- V["凭据保险库"] + EX --- PR["代理路由"] + end + CP -->|"任务元数据 WSS <1KB"| DP + DP -->|"结果/挑战 WSS"| CP + DP -->|"上传/发布 平台流量"| P["目标平台"] + CP -.->|"绝不触达"| P +``` + +## 图3 MVP 部署拓扑(内网联调) + +Mac 跑网页端+服务器,Windows 装客户端,同局域网联调。注意服务器绑定 0.0.0.0、客户端用局域网 IP、防火墙放行、关 AP 隔离。 + +```mermaid +flowchart LR + subgraph M["Mac 开发机 内网测试环境"] + WEB["网页端 dev"] --> S["服务器 API + WSS网关"] + end + subgraph W["Windows 客户机"] + APP["客户端App Agent
WinUI壳+Go执行核心"] + end + W -->|"WSS ws://局域网IP:端口"| S + S --> DB[("MySQL + Redis")] + S --> FS[("素材 服务器本地盘")] + APP -->|"直链下载素材 带签名token"| FS +``` + +## 图4 IP 路由逻辑 + +一账号一固定 IP,国内账号=国内住宅 IP,国外账号=国外住宅 IP,浏览器 Context 级注入。 + +```mermaid +flowchart LR + ACC["账号台账"] --> REG{"账号归属地?"} + REG -->|国内平台| POOL1["国内住宅IP池
芝麻/快代理/922S5等"] + REG -->|国外平台| POOL2["海外住宅IP池
BrightData/IPRoyal等"] + POOL1 --> BIND["一账号绑定一IP
长期固定不轮换"] + POOL2 --> BIND + BIND --> CTX["浏览器Context级注入
非全局TUN 防串账号"] + CTX --> EXEC["执行发布"] +``` + +## 图5 重试与降级状态机 + +失败按原因分流:网络→指数退避重试;登录态→挑战(人工扫码);风控→账号冷却+人工队列。 + +```mermaid +stateDiagram-v2 + [*] --> 队列中 + 队列中 --> 执行中: Agent认领 + 执行中 --> 已发布: 平台回执成功 + 执行中 --> 网络重试: 网络/超时错误 + 网络重试 --> 执行中: 退避1m/5m/15m/1h + 网络重试 --> 失败终态: 超上限N次 + 执行中 --> 挑战中: 需扫码/验证码 + 挑战中 --> 执行中: 用户完成挑战 + 挑战中 --> 挂起: 挑战超时 + 挂起 --> 执行中: 用户稍后处理 + 执行中 --> 账号冷却: 风控拦截 + 账号冷却 --> 执行中: 冷却结束+人工确认 + 账号冷却 --> 失败终态: 人工放弃 + 失败终态 --> 队列中: 人工一键重发 + 已发布 --> [*] + 失败终态 --> [*] +``` + +## 图6 全链路时序(含异常分支) + +```mermaid +sequenceDiagram + participant OP as 运营 + participant S as 服务器 + participant A as Agent + participant PL as 平台 + participant IT as 账号负责人 + Note over A,S: 单条WSS长连接复用(实测RTT 0.07ms) + OP->>S: 提交任务(素材/平台/排期) + S->>S: 校验→审核→入队→排期触发 + S->>A: task.push(taskId,加密载荷) + A-->>S: ack(taskId) 幂等确认 + A->>A: 选账号→注入代理→协议级登录态校验(不启浏览器) + alt 登录态正常 + A->>PL: 冷启动浏览器→上传素材→提交发布 + PL-->>A: 发布回执+截图 + A->>S: task.result(success) + S-->>OP: 实时状态+链接 + else 平台触发二次验证 + PL-->>A: 挑战(扫码/短信/APP确认/滑块) + A->>S: challenge.push(类型,凭证) + S-->>IT: 控制台+企微提醒 + IT->>S: 扫码或输入验证码 + S->>A: challenge.resolve(结果) + A->>PL: 继续发布流程 + else 网络类失败 + A->>A: 指数退避 1m/5m/15m/1h + A->>PL: 自动重试上传 + else 登录态失效 + A->>S: challenge.push(qr) + S-->>IT: 推送扫码提醒 + IT->>S: 扫码成功 + S->>A: challenge.resolve + A->>PL: 恢复执行 + else 风控拦截 + A->>S: task.result(fail,原因) + S->>S: 账号冷却+人工队列 + S-->>OP: 告警通知 + end +``` + +## 图7 二次验证挑战兜底流程 + +验证码类挑战人工兜底为主、Agent 硬解为辅;超时挂起→人工队列→一键重发。 + +```mermaid +flowchart TB + TR["平台触发二次验证"] --> DET{"Agent检测挑战类型"} + DET -->|二维码登录| QR["取码→推控制台+企微
码过期自动刷新"] + DET -->|APP确认| CF["推送手机确认提醒
Agent轮询登录态"] + DET -->|短信/邮箱| SMS["控制台弹输入框
用户输入→回传回填"] + DET -->|滑块/点选| CAP["默认截图推人工
低风控平台本地尝试"] + QR --> OK{"完成?"} + CF --> OK + SMS --> OK + CAP --> OK + OK -->|是| CONT["Agent继续发布流程"] + OK -->|否/超时| PEND["任务挂起→人工队列
一键重发/放弃"] + PEND --> CONT +``` + +## 图8 登录链路优化时序 + +API 取码免浏览器、只传 token 不传截图、短轮询检测、扫码与检测并行。 + +```mermaid +sequenceDiagram + participant U as 用户(手机) + participant C as 控制台/企微 + participant S as 服务器 + participant A as Agent + participant P as 平台 + Note over A,P: 首选API直取(无浏览器, B站已验证) + A->>P: HTTP取码 0.1-0.5s 得token+图片URL + A->>S: challenge.push(token) 实测<50ms + S->>C: WS推码 并行推企微/托盘 + C->>C: 本地重渲染二维码(非截图) + par 用户扫码 3-10s + U->>U: 手机扫码确认 + and Agent短轮询检测 + loop 300-500ms + A->>P: 轮询扫码状态(或网络钩子) + end + end + A->>P: API换取cookie 0.2-0.5s + A->>A: 存入凭据保险库 + A->>S: challenge.resolve(已登录) + S-->>C: 状态变绿 +``` + +## 图9 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) +``` + +## 图10 素材传输链路(一期本地盘 / 二期OSS) + +大文件绝不走 WSS;一期存服务器本地盘、直链带签名 token;二期切 OSS 预签名(StorageDriver 抽象保证零改动)。 + +```mermaid +flowchart TB + OP["运营(网页端)"] -->|"① multipart直传(一期服务器本地盘)"| FS["素材存储 一期本地盘/二期OSS"] + OP -->|"② 元数据 API(sha256)"| S["服务器"] + S -->|"③ WSS task.push + 短时效下载URL"| A["Agent"] + A -->|"④ 直链下载(带签名token)"| FS + A -->|"⑤ 上传发布"| P["目标平台"] + P -->|"⑥ 回执+截图"| A + A -->|"⑦ WSS task.result(URL+状态)"| S + S -->|"⑧ WSS 实时状态推送"| OP +``` + +## 图11 开发排期甘特(2 人,至最小闭环) + +> 2026-08-20 更新:AI 加速版日级排期见 docs/dev-schedule.md,以新文档为准。 + +```mermaid +gantt + title EveryPublish 最小闭环排期(2人) + dateFormat YYYY-MM-DD + axisFormat %m-%d + section 奠基 + 协议包+工程骨架 :M0, 2026-08-24, 7d + section 网页端 + 服务器最小闭环 :M1, 2026-08-31, 14d + 网页端全部功能 :M2, 2026-08-31, 28d + section 客户端Windows + Agent框架+WinUI壳 :M3, 2026-08-31, 21d + 真实平台接入4平台 :M4, 2026-09-21, 21d + section 联调与收尾 + 内网联调+异常加固 :M5, 2026-10-12, 14d + 打包+内部试用 :M6, 2026-10-26, 7d + section 二期 + 视频号+官方API通道 :M7, after M6, 21d +``` + +## 最小闭环验收清单 + +| 里程碑 | 验收标准 | 周次 | +|---|---|---| +| M0 协议+骨架 | 协议包单测通过,两端共享类型 | W1 | +| M1 服务器最小闭环 | 假执行器:任务→下发→执行→回传→显示全通 | W2-3 | +| M2 网页端全部功能 | 账号/素材/任务/排期/审核/挑战/审计/通知全页面可用 | W2-6 | +| M3 Windows 客户端框架 | 安装→配对→接假任务→回传结果 | W2-4 | +| M4 真实平台接入 | 抖音/快手/小红书/B站真实发布成功+截图回执 | W5-7 | +| M5 内网联调+加固 | 断线/重试/挑战/延迟<300ms 验收全过 | W8-9 | +| M6 打包+试用 | Windows 安装包+自动更新+内部试用 | W10 | + +**人力**:2 人(网页/服务端 1 + 客户端 1)10 周闭环;单人 16-18 周。闭环后持续成本:平台适配每周 0.5-1 天。 \ No newline at end of file diff --git a/docs/multi-platform-publish-plan.md b/docs/multi-platform-publish-plan.md new file mode 100644 index 0000000..d1a8866 --- /dev/null +++ b/docs/multi-platform-publish-plan.md @@ -0,0 +1,600 @@ +# EveryPublish 多平台聚合发布方案(调研与架构设计) + +> 调研日期:2026-08-20 | 数据来源:GitHub API 实测(星标/许可/活跃度)+ 官方文档抽查 + 工程实践估计。 +> 说明:本会话 web_search 不可用,个别官方接口细节(X 定价、国内开放平台审核范围)以官方最新文档为准,文中已标注置信度。 + +## 0. TL;DR 结论速览 + +1. **你的方向是对的**:「网站控制端 + 客户侧安装的执行端」是覆盖国内平台且支持多人协作的唯一高效解。行业成熟商业产品(易媒助手、融媒宝、蚁小二等)均采用同构方案,开源侧 `social-auto-upload` 的 Web-UI 衍生版也是该形态。 +2. **但不要一刀切,用双通道混合架构**:国际平台(X/IG/YouTube/TikTok)走**官方 API**,服务器直连、无需 Agent、完全合规;国内平台(抖音/快手/小红书/视频号/B站)走 **Agent 浏览器自动化**。合规面最大、Agent 复杂度最小。 +3. **唯一硬骨头是视频号**:微信官方无内容发布 API,只有「视频号助手」网页自动化一条路,会话短(小时级)、风控极高且牵连微信账号——建议单独迭代、默认关闭、客户主动开通并书面确认。 +4. **IP 风控的核心是一账号一固定 IP**:国内账号绑国内住宅 IP,国外账号绑国外住宅 IP,在 Agent 的浏览器 Context 级注入,永不跨区、永不轮换。 +5. **现成可复用**:`dreammis/social-auto-upload`(MIT,14.4k★,覆盖抖音/快手/小红书/视频号/微博/B站/TikTok/YouTube)是国内通道的最佳地基;`inovector/mixpost`(MIT)可整体消化国际侧;`postiz-app`(AGPL)只宜自托管参考。 +6. **最快验证路径**:第 1 周自托管 Mixpost 跑通国际平台并开始验证客户价值,同时并行开发 Agent 通道。 + +## 1. 需求与约束拆解 + +| 客户诉求 | 技术含义 | +|---|---| +| 越简单越好 | 客户三步上手:装 Agent → 扫码绑定 → 网站排期,零配置 | +| 发布越快 | WSS 任务推送到执行 <1s;跨平台并行;失败自动重试 | +| 账号越稳 | 登录态单点持有 + 固定设备指纹 + 固定 IP + 人级频率 | +| 覆盖全部发布功能 | 图文/视频/定时/多平台/多账号,统一任务模型 | +| 不违反官方规则 | 官方 API 优先;自动化部分明确告知灰色性质并分级开通 | +| 多运营协作一个官方账号 | 控制台角色权限 + 审核流;账号凭据不落运营手中 | +| IP 风控 | 账号-IP 绑定路由,按归属地分流(见 §8) | + +## 2. 三种实现形态对比 + +| 维度 | Web控制端+桌面Agent(推荐) | 浏览器插件 | 纯云端官方API SaaS | +|---|---|---|---| +| 多用户协作单账号 | **优**:凭据单点持有,角色审批,全程审计 | 差:多人登录同一账号易触发平台多设备风控 | 优:但仅覆盖有 API 的平台 | +| 账号凭据安全 | 优:客户本地加密,服务器零凭据 | 差:凭据散落各浏览器 | 优:OAuth,无密码 | +| IP 控制 | 优:Agent 按账号绑定代理 | 优:用户真实 IP(最自然) | 中:API 对 IP 不敏感 | +| 7×24 无人值守 | 优 | 差:需客户电脑开机 + 浏览器常驻 | 优 | +| 平台覆盖率 | **最广**:自动化兜底全部平台 | 中:受插件权限限制 | 仅覆盖有官方 API 的平台(抖音/快手/小红书/视频号内容发布均无公开 API) | +| 合规度 | 灰+白混合(分级开通) | 灰 | 白 | +| 开发成本 | 中高(可大幅复用开源) | 低 | 低(现成开源) | + +**结论**:主架构 = Web 控制端 + Agent;国际平台叠加官方 API 通道;浏览器插件仅作扫码辅助或单人轻量版。 + +## 3. 总体架构(逻辑图) + +```mermaid +flowchart TB + subgraph WEB["① 控制端 Web(多租户 SaaS)"] + U["运营 / 审核 / 管理员"] + W["任务编排 · 素材库OSS · 账号台账 · 审计"] + U --- W + end + subgraph CORE["② 调度中心(服务器)"] + Q["任务队列 Redis · 状态机 · 排期器"] + API["通道A 官方API执行器(OAuth直连)"] + AGW["通道B Agent网关(WSS注册/心跳/路由)"] + end + subgraph AGENT["③ 执行端 Agent(客户侧安装)"] + WS["WSS长连接 · 主动出网回连"] + VAULT["凭据保险库(cookie本地加密)"] + ROUTER["代理路由器(一账号一IP)"] + PA["Playwright 执行器(每账号独立档案)"] + QR["扫码登录面板(本地页面/托盘)"] + end + WEB -->|"任务下发"| Q + Q -->|"通道A 官方API"| API + Q -->|"通道B 自动化"| AGW + AGW --> WS --> ROUTER --> PA + VAULT --- PA + QR --- VAULT + API --> INT["国际:X / IG / YouTube / TikTok"] + PA --> DOM["国内:抖音/快手/小红书/视频号/B站/微博"] + PA --> INT +``` + +**要点**: +- 控制端:角色(管理员/审核/运营)、素材库(OSS+CDN)、账号台账、审计日志、通知(企微/钉钉/邮件)。 +- Agent:轻量常驻进程(早期草案为 Tauri/Electron 壳;最终定版:WinUI 3 壳 + Go 核心,见 docs/tech-stack.md)。四大组件:WSS 客户端(主动出网,免内网穿透)、凭据保险库(SQLCipher,服务器零凭据)、代理路由器、Playwright 执行器。 +- 素材流:控制端预签名直传 OSS → Agent 按任务拉取 → 发布后回调。 +- 响应速度:WSS 下发 <1s;总耗时 ≈ 平台上传+审核,架构不构成瓶颈。 +- **控制面/数据面分离**:我方服务器只处理任务元数据,全部平台流量从客户侧 Agent 发出,我方 IP 不接触平台(详见 §3.1)。 + +## 3.1 控制面 / 数据面分离(关键澄清:我方服务器不会被封) + +你提出的这一点是方案成立的前提,明确写入设计原则: + +- **控制面(我方 SaaS 服务器)**:只流转任务元数据——队列、排期、审核、审计、素材 OSS 中转、通知。**从不直接访问任何平台**,平台看不到我方任何基础设施,因而不存在「我方服务器 IP 被封」的可能。 +- **数据面(客户侧 Agent)**:登录态、代理路由、浏览器自动化、上传与发布,全部从客户自己的机器发出,使用客户自己的 IP(企业宽带/住宅)或客户绑定的住宅代理。**客户与客户之间天然隔离**,杜绝「大量用户共用同一 IP」的矩阵风控问题。 +- **素材中转**:素材存我方 OSS,但上传动作由客户 Agent 发起,平台只看到 Agent 的 IP,我方 OSS 不暴露给平台。 +- **唯一例外——官方 API 通道**:X/IG/YouTube/TikTok 走客户自己的应用凭据 + OAuth,平台识别的是客户的应用与账号,IP 不敏感,可安全地放在我方服务器执行;如客户仍希望「数据不出我方」,也可整体下沉到 Agent。 + +**Agent 部署三形态**: + +| 形态 | 7×24 | IP 真实度 | 成本 | 适用 | +|---|---|---|---|---| +| 客户办公电脑(默认) | 需常开 | 企业真实 IP,最优 | 零 | 默认形态,符合「越简单越好」 | +| 客户自有 VPS + 住宅代理 | ✅ | 住宅代理,较好 | 代理月费 | 需要深夜定时发布/无人值守的客户 | +| 我方托管 Agent(增值) | ✅ | 独享住宅代理 + 独享设备档案 | 高(我方承担风险与运维) | 大客户定制,绝不与其他客户共享 IP | + +多人协作逻辑不变:控制面照常支撑多运营/审核/排期,数据面由客户 Agent 单点执行——一个官方账号始终只在一台机器、一个 IP、一个设备档案上登录。 + +## 4. 任务状态机(逻辑图) + +```mermaid +stateDiagram-v2 + [*] --> 草稿 + 草稿 --> 待审核: 运营提交 + 待审核 --> 已排期: 审核通过 + 待审核 --> 草稿: 驳回 + 已排期 --> 队列中: 到点入队 + 队列中 --> 执行中: Agent认领 + 执行中 --> 已发布: 平台回执成功 + 执行中 --> 失败重试: 可重试错误 + 失败重试 --> 队列中: 指数退避重入 + 执行中 --> 失败终态: 超过重试上限 + 失败终态 --> [*] + 已发布 --> [*] +``` + +## 5. 任务执行时序(逻辑图) + +```mermaid +sequenceDiagram + participant OP as 运营(Web) + participant S as 服务器 + participant A as Agent(客户机器) + participant P as 目标平台 + A->>S: WSS注册(设备+账号清单) + OP->>S: 提交任务(素材/标题/平台/时间) + S->>S: 校验 → 审核 → 排期 + S->>A: 推送任务(加密载荷) + A->>S: ACK 认领(幂等) + A->>A: 选账号 → 注入代理 → 校验登录态 + A->>P: 上传素材 + 提交发布 + P-->>A: 发布回执 + A->>S: 回传结果 + 截图留证 + S-->>OP: 实时状态 + 失败通知 +``` + +## 6. 通信与安全设计 + +- **WSS 长连接**:Agent → Server 出站连接(天然穿透 NAT/防火墙),心跳 30s,断线指数退避重连;任务带 ID + ACK 幂等,防重复执行。 +- **加密**:任务载荷 AES-256-GCM,密钥经 KMS 信封加密;素材下载 URL 短时效签名。 +- **凭据**:cookie/token 仅存 Agent 本地(SQLCipher),服务器只存「账号是否在线、最后活跃」等元信息。 +- **审计**:谁在何时提交/审核/执行了哪个账号的什么内容,全链路留痕。 + +## 7. 平台接入矩阵 + +| 平台 | 官方发布API | 推荐通道 | 登录方式 | 会话稳定性 | 难度 | 合规 | 风控敏感度 | +|---|---|---|---|---|---|---|---| +| 抖音 | 开放平台有(企业审核) | 自动化(默认)/API(客户有资质时) | 扫码 | 中(约1-4周) | ★★★ | 灰/白 | 高 | +| 快手 | 开放平台有(需审核) | 自动化 | 扫码 | 中 | ★★★ | 灰/白 | 中高 | +| 小红书 | 专业号API(以电商为主) | 自动化 | 扫码 | 中短 | ★★★ | 灰/白 | 高 | +| 视频号 | **无**(仅视频号助手) | 自动化(唯一路径) | 微信扫码 | **短(小时级)** | ★★★★★ | 灰 | **极高(牵连微信)** | +| B站 | 投稿协议成熟(biliup) | 协议库直连(非浏览器) | 扫码/cookie | 长(月级) | ★★ | 半白 | 低 | +| 微博 | 开放平台(需审核) | API / 自动化 | 扫码 | 中 | ★★ | 白 | 低 | +| X | v2官方API(付费) | 服务器直连 API | OAuth | 长 | ★★ | 白 | 低 | +| Instagram | Graph API(仅商业号) | 服务器直连 API | OAuth | 长 | ★★ | 白 | 低 | +| YouTube | Data API v3 | 服务器直连 API | OAuth | 长 | ★★ | 白 | 低 | +| TikTok | Content Posting API(需审核) | 服务器直连 API | OAuth | 长 | ★★★ | 白 | 低 | +| WhatsApp | Cloud API(模板消息) | Cloud API(通知)/私协议(群发,灰) | 扫码/凭证 | 中 | ★★★ | 白/灰 | 中高 | + +> 注:自动化目标站点——抖音 `creator.douyin.com`、快手 `cp.kuaishou.com`、小红书 `creator.xiaohongshu.com`、视频号 `channels.weixin.qq.com`、B站 `member.bilibili.com`。 +> WhatsApp 特殊性:无信息流「发布」概念;Cloud API 走模板消息(适合通知/营销,需审核模板),状态/群发属私协议灰区。 +> 置信度说明:抖音/快手/小红书开放平台能力存在但审核门槛与覆盖范围需以官方文档核实(中置信);视频号无公开 API 为高置信(官方仅有视频号助手)。 + +## 8. IP 风控方案(逻辑图 + 策略) + +```mermaid +flowchart LR + ACC["账号台账"] --> REG{"账号归属地?"} + REG -->|国内平台| POOL1["国内住宅IP池
芝麻/快代理/922S5等"] + REG -->|国外平台| POOL2["海外住宅IP池
BrightData/IPRoyal等"] + POOL1 --> BIND["一账号绑定一IP
长期固定不轮换"] + POOL2 --> BIND + BIND --> CTX["浏览器Context级注入
(非全局TUN,防串账号)"] + CTX --> EXEC["执行发布"] +``` + +**策略**: +- 一账号一 IP,长期固定;**严禁轮换**(轮换=异地登录信号,触发风控)。 +- 住宅 IP 优先,IDC 机房 IP 风险最高;新 IP 先「养」再发。 +- 官方 API 通道不依赖 IP 属地(服务器任选);但 YouTube 自动化必须显式代理(`social-auto-upload` 的 `YT_PROXY` 已实证此坑:Chromium 不吃系统代理)。 +- 代理池定时健康检查(可用性 + 归属地),异常自动摘除;同平台多账号放不同 IP + 不同设备档案。 + +## 9. 账号稳定策略 + +- **每账号独立持久化浏览器档案**(user_data_dir):固定 UA/分辨率/时区/字体,不跨机器迁移。 +- **登录一律官方扫码**(APP 扫码天然绑定设备),禁止代登密码。 +- **Cookie 保鲜**:每日低频心跳访问 + 失效检测,失效即推送「重新扫码」到控制台与企业微信。 +- **发布节奏**:单账号人级频率上限,失败指数退避,避开平台风控高峰(大促/晚高峰更敏感)。 +- **视频指纹**:ffmpeg 重编码去除元数据,多平台分发避免同文件哈希直传(防搬运识别关联)。 +- 平台风控核心信号是「设备+IP+行为」三要素,三者全固定最稳——这也是 Agent 方案优于插件方案的根本原因。 + +## 10. 开源项目聚合与复用建议 + +| 项目 | 星标 | 许可 | 覆盖平台 | 复用方式 | +|---|---|---|---|---| +| dreammis/social-auto-upload | 14.4k | MIT | 抖音/快手/小红书/视频号/微博/B站/百家号/支付宝生活号/虎扑/TikTok/YouTube | **国内通道地基**:fork 后把本地脚本改造成 Agent 服务,uploader 层几乎不动 | +| inovector/mixpost | 3.5k | MIT | X/FB/IG/LinkedIn/Mastodon/Pinterest/TikTok/YouTube | MIT 可商用,国际侧可直接魔改 | +| gitroomhq/postiz-app | 34.9k | AGPL-3.0 | X/IG/TikTok/YouTube/Threads/Bluesky/Pinterest/LinkedIn/Reddit 等 | 自托管可用;闭源 SaaS 注意 AGPL 传染,仅作参考 | +| biliup/biliup | 5.4k | MIT | B站 | 投稿库 `bili_webup` 直接集成 | +| DevilJie/social-auto-upload-web-ui | 210 | MIT | 同上(Web 化) | Web+本地服务分层先例,前端可参考 | +| dorisoy/ShortVideo.AutoPublisher | 167 | MIT | 抖音/视频号/小红书/百家号/头条 | 视频号实现参考(.NET+Playwright),桌面 Agent 先例 | +| ddean2009/MoneyPrinterPlus | 6.8k | GPL-3.0 | 抖音/快手/小红书/视频号 | 视频号上传流程参考;GPL 注意 | +| white0dew/XiaohongshuSkills | 3.3k | MIT | 小红书 | 小红书发布流程参考(Agent 生态) | +| NanmiCoder/MediaCrawler | 63.1k | 自定义 | 小红书/抖音/快手/B站/微博(爬) | 登录态/签名逆向参考;发布在付费 Pro 版 | +| ReaJason/xhs | 2.2k | MIT | 小红书 | Web 协议封装,发布能力有限需自研上传 | +| tweepy | 11.2k | MIT | X | X 官方 API 客户端 | +| WhiskeySockets/Baileys | 10.8k | MIT | WhatsApp(私协议) | 备用;优先 Cloud API | +| davidteather/TikTok-Api | 6.6k | MIT | TikTok(私协议) | 备用;优先官方 API | +| subzeroid/instagrapi | 6.7k | 自定义 | IG(私协议) | 不推荐主线(封号风险高) | +| lich0821/WeChatFerry | 6.8k | MIT | 微信(已归档) | 仅参考 | + +**复用策略**:国内地基 `social-auto-upload`(MIT)→ 国际侧 Mixpost(MIT)→ B站 biliup → 视频号参考 tencent_uploader + ShortVideo.AutoPublisher。私协议库(instagrapi/Baileys/TikTok-Api)不作主线,仅官方 API 未覆盖时的知情备选。 + +## 11. 实现难度与可信度评估 + +| 模块 | 难度 | 可信度 | 说明 | +|---|---|---|---| +| Web 控制台 + 任务队列 | ★★ | ★★★★★ | 成熟技术栈,无风险 | +| 官方 API 通道(国际) | ★★ | ★★★★★ | OAuth + 官方 SDK,完全合规 | +| Agent 框架(WSS+凭据+代理) | ★★★ | ★★★★ | 工程量大但完全可控 | +| 抖音/快手/小红书自动化 | ★★★ | ★★★☆ | 有 MIT 开源可 fork;持续风控适配 | +| B站投稿 | ★★ | ★★★★★ | biliup 协议成熟稳定 | +| **视频号** | ★★★★★ | ★★ | 无 API、会话短、牵连微信,唯一硬骨头 | +| WhatsApp 群发/状态 | ★★★★ | ★★☆ | Cloud API 限模板;私协议有封号风险 | +| IP 代理管理 | ★★★ | ★★★★ | 采购 + 绑定 + 健康检查 | +| 多租户/权限/审计 | ★★★ | ★★★★★ | 常规业务功能 | + +**总评**:MVP(官方 API 通道 + 抖音/快手/小红书/B站)2-3 人 × 4-6 周;全量(含视频号)3-4 人 × 2-3 个月 + 持续风控维护(平台前端改版适配是长期成本,每周巡检)。 + +## 12. 分阶段实施路线 + +| 阶段 | 内容 | 周期 | 产出 | +|---|---|---|---| +| P0 验证 | 自托管 Mixpost 跑通 X/IG/YouTube | 1 周 | 验证国际侧价值与合规通道 | +| P1 MVP | 控制台 + 任务队列 + Agent 框架 + 抖音/快手/小红书/B站 | 3-4 周 | 可交付的第一版 | +| P2 协作 | 角色权限/审核流/审计/通知 | 1-2 周 | 多人协作闭环 | +| P3 攻坚 | 视频号(独立迭代)+ 微博/百家号 | 2-3 周 | 全平台覆盖 | +| P4 白通道 | 抖音开放平台/小红书专业号 API 接入(客户有资质时) | 并行 | 合规面扩大 | +| P5 商业化 | 多租户/计费/渠道 | 持续 | SaaS 化 | + +## 13. 合规边界(务必读) + +- **官方 API = 完全合规**(平台授权);**浏览器自动化 = 技术上违反多数平台「禁止自动化访问」条款**,实际执行风险是限流/封号。产品必须对客户**知情告知**,不可隐瞒。 +- **分级开通**:白名单通道(官方 API 平台)默认开启;灰通道(自动化)客户主动开通 + 签署风险告知书。 +- **红线不碰**:大规模矩阵养号、搬运去重绕过、刷量、私信轰炸——触碰即封号且可能产生法律风险。 +- **内容合规前置**:平台内容审核由平台执行,我方提供发布前自检提示,不代替审核。 +- **数据合规**:凭据加密最小化,素材仅在客户授权空间流转(个保法/数安法)。 +- **视频号特别告知**:微信账号是客户核心资产,封禁损失远大于其他平台——默认关闭、独立审批开通、建议专用微信号。 + +## 14. 风险清单与缓解 + +| 风险 | 影响 | 缓解 | +|---|---|---| +| 平台前端改版/选择器失效 | 自动化断链 | 模块化选择器 + 灰度监控 + 每周巡检(最大持续成本) | +| 登录态失效 | 发布失败 | 保鲜心跳 + 失效推送 + 一键重扫 | +| 账号风控 | 限流/封号 | 固定设备+IP+人级频率+养号策略 | +| 代理失效 | 整批失败 | 池健康检查 + 同区域自动切换 | +| API 涨价/收紧 | 成本/断供 | X 从 Basic 起步,预留自动化兜底 | +| 视频号牵连微信 | 重大损失 | 默认关闭 + 专用微信号 + 客户书面确认 | +| AGPL 传染 | 法律/商业 | 商用选 MIT 项目或自托管,律师审阅 | + +## 15. 「越简单越好」的产品体验设计 + +- **客户三步**:装 Agent(一条命令/安装包)→ 打开本地扫码面板绑定各平台 → 网站排期一键发布。 +- **控制台极简视图**:今天要发什么 / 发了没 / 失败原因 / 一键重试。 +- **发布回执**:每平台截图 + 链接留证,运营可查可复核。 +- **登录过期**:红点 + 企微提醒 + 扫码面板自动弹出。 +- **单人版与团队版同一套**:单人 = 跳过审核流,零学习成本。 + +## 16. 用户端 / 管理端体验走查(Q&A 补充) + +### 16.1 三种形态的「安装」真相 + +同一套 Agent 软件,只差运行地点: + +| 形态 | 装什么 | 谁来装 | 怎么装 | 扫码在哪出现 | +|---|---|---|---|---| +| 客户办公电脑(默认) | 桌面应用(系统托盘) | 客户 IT / 账号负责人 | 下载安装包,双击即用 | 应用窗口 / 托盘弹窗 | +| 客户自有 VPS | Docker 容器(无界面) | 客户 IT | 一条命令 / 一键脚本 | 控制台统一弹码(经 WSS 回传二维码图片,推荐) | +| 我方托管(增值) | **无需安装** | 我方 | 我方部署 | 客户控制台页面直接弹码 | + +- 无论哪种形态,客户要做的「账号动作」**只有扫码登录**(官方 APP 扫码,天然绑定设备);账号密码/验证码永不经过我方系统。 +- 住宅代理:办公电脑走企业宽带时**零配置**;VPS / 托管形态需购买住宅代理并粘贴代理账号密码(托管形态可由我方代采购)。 + +### 16.2 用户端体验(装 Agent 的那一端) + +- **首次 5 分钟**:装好 → 打开 → 登录企业工作区 → 输入配对码绑定控制台 → 逐个平台点「绑定」弹码 → 手机扫码 → 绿色对勾。此后常驻托盘,无感运行。 +- **日常几乎零操作**:托盘显示在线状态与待发任务数;登录过期时弹窗 + 企微/短信提醒「请重新扫码」;部分平台约每周需扫一次码。 +- **关键分工**:装机的人是企业 IT 或账号负责人,不是日常运营——运营全程只碰网站。 + +### 16.3 管理端体验(Web 控制台) + +- **运营**:素材库上传 → 编辑标题/话题 → 勾选平台与账号 → 排期日历选时间 → 提交。发布中实时进度(上传中/审核中/已发布 + 链接截图);失败显示原因 + 一键重试。 +- **审核**:待审列表 → 分平台预览 → 通过/驳回(附批注)。 +- **管理员**:账号台账(绑定状态/最后活跃/代理绑定)、成员与角色、审计日志、通知渠道(企微/钉钉/邮件)。 +- **单人版**:同一套界面自动隐藏审核流,运营即管理员,零学习成本。 + +### 16.4 平台方管理端(我方 SaaS 后台) + +- 租户/计费、Agent 在线监控与版本推送、渠道适配器健康巡检(平台改版监控)、代理池与风控事件台账(托管形态)、客户支持。 + +## 17. 工程实施:技术栈、联调环境与排期(Q&A 补充) + +### 17.1 可行性结论 + +- **能实现**。先例:dreammis/social-auto-upload(MIT)已跑通国内全平台上传流程,我们只把它从「本地脚本」改为「Agent 服务 + 控制台」;商业产品(易媒/融媒宝)同构。真正的不确定性只在平台风控对抗,不在架构。 +- 「软件与网站连接很慢,即使本地调用也很慢」是**工程问题**:按 17.2 对照排查 + 17.3 分段计时,通常 5 分钟可定位。 + +### 17.2 连接慢的十大嫌疑 + +| 症状 | 根因与修复 | +|---|---| +| 每条消息都有固定 100-500ms 延迟 | 每次请求重新鉴权(bcrypt 重算)→ 改会话 Token/JWT 校验 | +| 本机访问 localhost 也慢几秒/偶发超时 | IPv6 陷阱:Node17+ 把 localhost 解析到 ::1,服务端只监听 127.0.0.1 → 显式用 127.0.0.1、--dns-result-order=ipv4first,或双栈监听 | +| 开了代理软件后本地调用变慢 | HTTP(S)_PROXY 环境变量劫持 localhost → NO_PROXY 加入 localhost,127.0.0.1 | +| 客户端执行发布时心跳/消息全卡住 | 单线程串行:Playwright 等重活阻塞消息循环 → 执行器独立 worker,消息与执行分离 | +| 状态更新「慢半拍」 | HTTP 轮询代替长连接 → 改 WebSocket/SSE 推送 | +| 每次任务重新建连 | 连接未复用 / TLS 每次握手 → 持久连接 + 会话复用 | +| 局域网跨机慢 | 路由器 AP 隔离 / mDNS 主机名解析慢 → 用 IP 直连、关 AP 隔离 | +| Windows 上整体偏慢 | Defender 实时扫描客户端进程(Go core/浏览器引擎)→ 加排除项,先用裸 curl 测基线 | + +### 17.3 分段计时定位法(本仓库 `tools/diag-connection.mjs`) + +| 环节 | 局域网正常值 | 偏大指向 | +|---|---|---| +| TCP connect | <2ms | 防火墙 / IPv6 / 代理 | +| WS 握手 open | <10ms | 服务端 WS 框架 / 鉴权 | +| 首条消息 RTT | <20ms | 串行往返过多 / 服务端处理 | +| 心跳间隔抖动 | 稳定 ±1s | 客户端单线程阻塞 | +| 服务端处理耗时 | 与接口总延迟之差=网络 | DB 慢查询 / N+1 / 同步 IO | + +### 17.4 技术栈 + +> 2026-08-20 注:本节为早期 JS 栈草案,已被定版取代——技术栈以 docs/tech-stack.md 与 docs/delivery-plan.md 为准(MySQL + 前后端 Go 统一,页面层 React+TDesign)。 + +| 模块 | 选型 | 理由 | +|---|---|---| +| 仓库组织 | pnpm monorepo + Turborepo | apps/web、apps/agent-core、apps/agent-desktop 共享协议包 | +| 网站 | Next.js 15 + TS + Tailwind + shadcn/ui | 全栈一体,API Routes 即后端 | +| 数据 | PostgreSQL + Drizzle + Redis + BullMQ | 任务队列与排期靠 Redis | +| 实时 | Socket.IO(WebSocket) | 心跳/幂等/重连开箱即用 | +| 客户端核心 | Node + Playwright + ws + better-sqlite3 | 凭据本地加密存储;执行器独立子进程 | +| 客户端壳 | Electron + electron-builder | 托盘 + 扫码窗口;CI 出 Windows 包 | +| 素材 | 一期:服务器本地盘(StorageDriver 抽象);二期:MinIO/OSS | 直链下载(带短时效签名 token) | +| 测试 | vitest + Playwright + MockUploader 假执行器 | 先打通链路再上真实平台 | + +### 17.5 内网联调环境(Mac=服务端,Windows=客户端) + +完全可行,标准做法: + +1. Mac:web 与 WS 服务绑定 0.0.0.0(`next dev -H 0.0.0.0`);查 Mac 局域网 IP:`ipconfig getifaddr en0`。 +2. Windows:Agent 配置 `EP_WS=ws://:<端口>`(用 IP,不用主机名)。 +3. 防火墙:macOS 放行 node 入站;Windows 出站默认放行。 +4. 路由器:关闭 AP 隔离(访客网络常见坑)。 +5. 先用 curl/wscat 从 Windows 测通 Mac 端口,再启动 Agent。 +6. 公司网络隔离时:ZeroTier / Tailscale 组虚拟局域网。 +7. 内网阶段用 ws:// 即可;生产才上 wss:// + 证书。 + +### 17.6 开发顺序与周期 + +| 阶段 | 内容 | 周期 | +|---|---|---| +| 0 协议定版+骨架 | WS 协议 JSON Schema、假执行器打通全链路、延迟基线 <300ms | 1 周 | +| 1 网站功能 | 租户/角色/账号台账/素材/排期/审核/通知 | 2-3 周 | +| 2 Agent 真实平台 | 抖音/快手/小红书/B站 uploader(参考 social-auto-upload) | 3-4 周 | +| 3 联调测试 | 异常注入:断网/掉线/登录过期/重复任务 | 1-2 周 | +| 4 视频号攻坚 | 独立迭代,默认关闭 | 2 周 | +| 5 打包分发 | Windows 安装包 + 自动更新 | 1 周 | + +- **单人 10-13 周,双人并行 6-8 周**。 +- 管理预期:网站 CRUD 与客户端框架确实不长(各 2-3 周),但**时间黑洞是平台 uploader 的适配与风控对抗**,不是网站也不是客户端框架;此后每周约 0.5-1 天持续维护。 + +## 18. 协议层考证:局域网慢的根因与实测基线 + +### 18.1 实测基线(2026-08 本机实测,Apple Silicon) + +| 环节 | 实测 | 结论 | +|---|---|---| +| TCP 连接 127.0.0.1 x100 | avg 0.14ms(p99 1.17ms) | 协议开销可忽略 | +| TCP 连接 localhost(走 IPv6 解析) | avg 0.49ms(p99 4.33ms) | **IPv6 路径慢 3.5 倍**;若防火墙丢弃 ::1 包将变成秒级 | +| WS 握手(新建连接)x30 | avg 0.58ms | 建连便宜,但别每条消息都建 | +| 单连接消息 RTT x2000 | **avg 0.07ms(p99 0.14ms)** | 协议真实下界:亚毫秒 | +| 每消息新建连接 x30 | avg 0.75ms | 是复用连接的 **10 倍** | +| 单次 bcrypt 级哈希(pbkdf2 100k) | 15.9ms | 每次请求重算鉴权 = 每条消息 +16ms(Windows 更慢 50-150ms) | +| 串行 10 次往返 | 0.91ms | 本机便宜;跨机时 = 10×RTT + 处理 | + +**考证结论**:协议层(TCP+WS 复用连接)本机只有 0.07ms。局域网环境「慢」必然是下列上层之一叠加: + +### 18.2 局域网环境下慢的候选层(延迟预算表) + +| 层 | 典型增量 | 判断方法 | +|---|---|---| +| 协议 RTT(复用连接) | <1ms | 基准 | +| WiFi 抖动 / 客户端节能 | +1~20ms | 换有线对比 | +| 丢包重传(TCP RTO) | **+200ms 起/包** | ping 查丢包率 | +| 每请求重算鉴权(bcrypt 级) | +16~150ms | 与免鉴权接口对比压测 | +| 每消息新建连接 / TLS 握手 | 本地 +0.5ms,跨机 +数 RTT | 对比复用与不复用 | +| IPv6 陷阱 | 本地 +0.35ms,丢包场景秒级 | 对比 127.0.0.1 与 localhost | +| HTTP 轮询 | 秒级(等于轮询间隔) | 协议审计 | +| Socket.IO 降级为长轮询 | 秒级 | 检查实际 transport | +| 服务端阻塞 / 慢 DB / N+1 | 不定 | 全链路埋点 | + +### 18.3 协议层五条铁律(让「慢」结构上不可能发生) + +1. **一条 WSS 连接用到底**:Agent→Server 单连接复用,不每任务建连;开启 TCP_NODELAY。 +2. **鉴权只做一次**:连接建立时 auth 一次,后续走连接态 + 消息级 HMAC 校验(微秒级),杜绝每请求重算密码哈希。 +3. **执行与消息分离**:Playwright/上传放独立 worker 进程,绝不阻塞消息循环(心跳卡死是「假慢」主因)。 +4. **消息合并与节流**:ack 合并、进度回报节流 1 次/s,避免 chatty 串行往返。 +5. **全链路埋点**:每条消息带 ts、ack 带 ackTs,服务端聚合各段 delay 指标并告警——「慢在哪」永远用数据说话,不再靠猜。 + +## 19. 浏览器调用最小化与指纹模拟 + +### 19.1 浏览器调用清单(原则:协议能办的事绝不开浏览器) + +| 操作 | 触发浏览器? | 替代方案 | +|---|---|---| +| 登录取码 | 否 | 平台二维码 API(**仅视频号必须在浏览器内取码**) | +| 登录态校验 / 保鲜 | 否 | 轻量 HTTP API 心跳(首页/用户信息接口),空闲时浏览器保持关闭 | +| B站上传发布 | 否 | 投稿协议纯 HTTP(参考 biliup / bili_webup) | +| 抖音/快手/小红书上传 | **是(仅此一步)** | 冷启动 → 会话复用 → 用完即关 | +| 定时发布设置 | 部分可 API | 能用 API 就不开浏览器 | +| 视频号全流程 | 是 | 无替代(唯一通道) | +| 二次验证 | 视类型 | QR/短信/APP确认推送人工;滑块低风控平台本地尝试 | + +### 19.2 浏览器生命周期 + +- **空闲**:浏览器保持关闭,登录态靠协议心跳保鲜 → 心跳与消息零浏览器开销。 +- **执行**:仅「上传发布」阶段冷启动;同平台同账号任务排队复用同一实例(会话复用)。 +- **结束**:任务完成即关,释放内存与句柄。 + +### 19.3 指纹模拟要点 + +- **用客户机器上真实 Chrome**(playwright channel:`chrome`),不用捆绑 Chromium——JA3/UA/指纹与真实浏览器的差异本身就是风控信号。 +- 每账号独立 user_data_dir:固定 UA/分辨率/时区/语言/字体/hardwareConcurrency/deviceMemory。 +- playwright-extra + stealth 插件;抹除 navigator.webdriver 与 CDP 自动化特征。 +- 网络层:固定代理、WebRTC 防泄漏、时区与代理归属地一致。 +- 行为层:可选「先浏览主页再发布」养号动作,操作间隔随机化(人级)。 +- **铁律:一档案 = 一平台一账号一IP,永不交叉复用。** + +## 20. 二次验证场景与兜底设计 + +### 20.1 统一挑战模型(Challenge) + +- 消息:challenge.push(Agent→Server:类型+凭证+平台+账号)/ challenge.resolve(Server→Agent:用户输入结果)。 +- 类型:qr(二维码)/ confirm(APP确认)/ sms(验证码)/ captcha(滑块点选)/ pending(人工处理)。 +- 状态:挑战中 → 已解决 / 超时挂起 / 放弃。 + +### 20.2 五类挑战与兜底 + +| 类型 | 常见平台 | 检测 | 兜底流程 | 超时处理 | +|---|---|---|---|---| +| 二维码登录 | 抖音/快手/小红书/B站常态 | 登录页出现 QR | 推控制台+企微,码过期自动刷新 | 5min → 任务挂起 | +| APP确认「是本人」 | 抖音/小红书异地登录 | 页面提示确认 | 推送手机确认,Agent 轮询登录态 | 5min → 挂起 | +| 短信/邮箱验证码 | 各平台敏感操作 | 输入框出现 | 控制台弹输入框(带尾号提示)→ 回填,错可重输 2 次 | 3min → 挂起 | +| 滑块/点选 | 上传前风控 | 验证组件出现 | 默认截图推人工拖拽;低风控平台本地自动尝试,失败转人工 | 3min → 挂起 | +| 人机识别失败 | TikTok 等国际平台 | 登录被拒 | 推送人工登录指引 | 转人工队列 | + +### 20.3 原则 + +- **账号稳优先**:验证码类挑战人工兜底为主、Agent 硬解为辅,绝不为速度牺牲账号。 +- 挑战走控制面通道(不与平台流量混在一起)。 +- 挂起任务可一键重发/放弃;所有挑战过程留审计。 + +## 21. 详细端到端逻辑图(重绘) + +### 21.1 全链路时序(用户→端→平台→用户 + 重试/挑战) + +```mermaid +sequenceDiagram + participant OP as 运营 + participant S as 服务器 + participant A as Agent + participant PL as 平台 + participant IT as 账号负责人 + Note over A,S: 单条WSS长连接复用(实测RTT 0.07ms) + OP->>S: 提交任务(素材/平台/排期) + S->>S: 校验→审核→入队→排期触发 + S->>A: task.push(taskId,加密载荷) + A-->>S: ack(taskId) 幂等确认 + A->>A: 选账号→注入代理→协议级登录态校验(不启浏览器) + alt 登录态正常 + A->>PL: 冷启动浏览器→上传素材→提交发布 + PL-->>A: 发布回执+截图 + A->>S: task.result(success) + S-->>OP: 实时状态+链接 + else 平台触发二次验证 + PL-->>A: 挑战(扫码/短信/APP确认/滑块) + A->>S: challenge.push(类型,凭证) + S-->>IT: 控制台+企微提醒 + IT->>S: 扫码或输入验证码 + S->>A: challenge.resolve(结果) + A->>PL: 继续发布流程 + else 网络类失败 + A->>A: 指数退避 1m/5m/15m/1h + A->>PL: 自动重试上传 + else 登录态失效 + A->>S: challenge.push(qr) + S-->>IT: 推送扫码提醒 + IT->>S: 扫码成功 + S->>A: challenge.resolve + A->>PL: 恢复执行 + else 风控拦截 + A->>S: task.result(fail,原因) + S->>S: 账号冷却+人工队列 + S-->>OP: 告警通知 + end +``` + +### 21.2 重试与降级状态机 + +```mermaid +stateDiagram-v2 + [*] --> 队列中 + 队列中 --> 执行中: Agent认领 + 执行中 --> 已发布: 平台回执成功 + 执行中 --> 网络重试: 网络/超时错误 + 网络重试 --> 执行中: 退避1m/5m/15m/1h + 网络重试 --> 失败终态: 超上限N次 + 执行中 --> 挑战中: 需扫码/验证码 + 挑战中 --> 执行中: 用户完成挑战 + 挑战中 --> 挂起: 挑战超时 + 挂起 --> 执行中: 用户稍后处理 + 执行中 --> 账号冷却: 风控拦截 + 账号冷却 --> 执行中: 冷却结束+人工确认 + 账号冷却 --> 失败终态: 人工放弃 + 失败终态 --> 队列中: 人工一键重发 + 已发布 --> [*] + 失败终态 --> [*] +``` + +### 21.3 二次验证挑战兜底流程 + +```mermaid +flowchart TB + TR["平台触发二次验证"] --> DET{"Agent检测挑战类型"} + DET -->|二维码登录| QR["取码→推控制台+企微
码过期自动刷新"] + DET -->|APP确认| CF["推送手机确认提醒
Agent轮询登录态"] + DET -->|短信/邮箱| SMS["控制台弹输入框
用户输入→回传回填
错可重输2次"] + DET -->|滑块/点选| CAP["默认截图推人工
低风控平台本地尝试"] + QR --> OK{"完成?"} + CF --> OK + SMS --> OK + CAP --> OK + OK -->|是| CONT["Agent继续发布流程"] + OK -->|否/超时| PEND["任务挂起→人工队列
一键重发/放弃"] + PEND --> CONT +``` + +## 22. 登录链路优化:把 10 秒级登录压到 2 秒级 + +### 22.1 拆解:登录到底慢在哪 + +朴素实现(浏览器全流程)逐环节耗时:启动浏览器 → 加载登录页 → 找/截二维码 → 传图 → 等扫码 → 轮询检测 → 换取登录态。其中「浏览器 + 页面 + 轮询」是机器耗时大头,人的扫码时间(3-10s)无法压缩但可以重叠。 + +| 环节 | 朴素实现 | 优化后 | 手段 | +|---|---|---|---| +| 启动浏览器 | 1-3s(冷启动) | **0s** | API 取码免浏览器(B站已验证,biliup 直接出 qrcode.png);视频号用常驻温实例 | +| 页面加载渲染 | 2-5s | ~1s(仅视频号) | 直达登录页 + 路由拦截广告/统计/字体资源 | +| 抽取二维码 | 0.5-2s(截图/找元素) | **<0.1s** | DOM 取二维码图片 URL 或 API 直接返回 token(开源项目 utils/login_qrcode 即此模式) | +| 码跨层传递 | 0.2-1s(传截图) | **<50ms** | 只传 token/图片URL,控制台本地重渲染——更快且**更清晰**(截图压缩会降低扫码成功率) | +| 检测扫码成功 | 轮询 1-3s/次 | 0.3-0.5s | 短轮询 300-500ms;或拦截页面自身状态 XHR 即时检测 | +| 换取登录态 | 1-2s(页面跳转等待) | 0.2-0.5s | API 换取 cookie/token | +| **机器合计** | **约 10-16s** | **约 1-2s**(视频号场景 3-4s) | 用户扫码 3-10s 不变,但成为唯一等待 | + +### 22.2 五条铁律 + +1. **让登录少发生**:cookie 保鲜 + refresh 接口 + 过期预测(登录是低频事件,最好的登录优化是「不登录」)。 +2. **能 API 不浏览器**:B站纯 API 已验证;抖音/小红书/快手优先尝试其 Web 登录 API,失败再回退浏览器。 +3. **只传 token 不传截图**:所有层传递二维码内容/图片 URL,渲染端本地重绘。 +4. **检测即时化**:300-500ms 短轮询 + 页面网络钩子双通道。 +5. **等待重叠**:多平台并行取码、预计过期前预热、控制台+企微+托盘同时推送。 + +### 22.3 视频号例外(唯一必须浏览器的登录) + +- 温浏览器常驻:登录需求出现前预启动,不冷启动。 +- 直达登录页 + 资源拦截:只加载必要资源,页面 5s→1s。 +- 取码:DOM 抓二维码图片 URL,不截图。 +- 检测:注入 MutationObserver 监听登录态跳转 + 拦截页面状态 XHR。 +- 提示:抖音/快手现可选用 patchright(补丁版 Playwright,开源项目已采用),指纹绕过更稳。 + +## 23. 系统设计文档(网页端 + 客户端 App) + +完整系统设计(两端功能清单、连接方案评估、四层鉴权、REST/WS 协议、配对与登录流程、边界问题)见独立文档:docs/system-design.md。 + +## 附录:参考链接 + +- https://github.com/dreammis/social-auto-upload +- https://github.com/DevilJie/social-auto-upload-web-ui +- https://github.com/dorisoy/ShortVideo.AutoPublisher +- https://github.com/gitroomhq/postiz-app +- https://github.com/inovector/mixpost +- https://github.com/biliup/biliup +- https://github.com/SocialSisterYi/bilibili-API-collect (已归档) +- https://github.com/NanmiCoder/MediaCrawler +- https://github.com/ReaJason/xhs +- https://github.com/tweepy/tweepy +- https://github.com/WhiskeySockets/Baileys +- https://github.com/davidteather/TikTok-Api +- https://github.com/subzeroid/instagrapi +- 官方文档:developers.facebook.com(IG)、developers.google.com/youtube、developers.tiktok.com、developer.x.com、open.kuaishou.com、developer.open-douyin.com、open.xiaohongshu.com、channels.weixin.qq.com diff --git a/docs/page-wireframes.md b/docs/page-wireframes.md new file mode 100644 index 0000000..bee63af --- /dev/null +++ b/docs/page-wireframes.md @@ -0,0 +1,334 @@ +# EveryPublish 页面线框图全集(ASCII) + +> 2026-08-20 · 与 docs/delivery-plan.md 日计划对应(D5-D7 页面开发按本图实现)。标注行=推荐使用的 TDesign 组件与 starter 模板。 + +## 0. 通用布局骨架(除登录页外所有网页端页面共用) + ++------------------------------------------------------------------+ +| Logo EveryPublish 🔔通知(角标) 头像 ▾ | ++-------------+----------------------------------------------------+ +| 侧边菜单 | 面包屑: 首页 / 发布管理 / 任务列表 | +| 工作台 | +------------------------------------------------+| +| 发布管理 ▾ | | || +| 任务列表 | | 页面内容区(各页不同) || +| 新建发布 | | || +| 排期日历 | | || +| 素材库 | | || +| 账号管理 | | || +| 审核中心 | | || +| 通知与挑战 | | || +| 成员与权限 | | || +| 设置 ▾ | +------------------------------------------------+| +| 审计日志 | | ++-------------+----------------------------------------------------+ + +组件: Layout + Menu(侧边) + Breadcrumb + Dropdown(头像) + Badge(通知角标) +说明: admin 角色在头像下拉出现「平台管理」入口,跳 /admin 域(同骨架,菜单替换)。 + +## 1. 登录页(isFullPage 全屏,无侧边菜单) + ++-----------------------------------------------------------+ +| | +| [Logo EveryPublish] | +| +---------------------------------+ | +| | 账号 [___________________] | | +| | 密码 [___________________] | | +| | [x] 记住我 忘记密码? | | +| | [ 登 录 ] | | +| +---------------------------------+ | +| | ++-----------------------------------------------------------+ + +组件: Form + Input + Checkbox + Button; 模板: pages/Login 改造(去第三方登录) +验收: 登录成功进入工作台; 失败提示; refresh 自动续期。 + +## 2. 仪表盘(工作台) + ++------------------------------------------------------------------+ +| [今日发布:12] [成功:10] [失败:2] [待处理挑战:1] | +| Agent 在线状态: ● 在线(1台) 最后心跳 3s 前 [查看设备] | +| 最近任务: | +| | 素材 | 标题 | 平台 | 状态 | 时间 | 操作 | | +| | 图 | xxx | 抖音 | 已发布 | 10:32 | 查看 | | +| | 图 | yyy | 快手 | 失败 | 10:30 | 重试 | | ++------------------------------------------------------------------+ + +组件: Card + Statistic(可用 echarts 现成卡片或纯文本) + Table + Tag + Badge +模板: pages/Dashboard/Base 改造 +验收: 数字与后端统计一致; Agent 状态实时。 + +## 3. 任务列表(发布管理) + ++------------------------------------------------------------------+ +| 筛选: [状态▾全部] [平台▾全部] [日期范围] [搜索标题____] [查询][重置] | +| [ + 新建发布 ] | +| | 素材 | 标题 | 平台 | 账号 | 排期时间 | 状态 | 操作 | | +| | 图 | xxx | 抖音 | 官号 | 10:30 | 已发布 | 查看/重试 | | +| | 图 | yyy | 小红书 | 官号 | 明天09:00| 待审核 | 查看/取消 | | +| ... | +| < 1 2 3 ... 10 > 共 123 条 | ++------------------------------------------------------------------+ + +组件: Table + Select + DateRangePicker + Input + Button + Tag + Pagination + Popconfirm(取消) +模板: pages/List/Base +验收: 筛选/分页正确; 重试与取消二次确认; 状态实时刷新。 + +## 4. 新建发布(分步向导) + ++------------------------------------------------------------------+ +| 步骤条: ① 选择素材 → ② 平台与账号 → ③ 编辑内容 → ④ 定时与提交 | +| | +| 步骤①: 素材卡片网格(点选) [从素材库选择][上传] | +| +------+ +------+ +------+ | +| | 缩略图 | | 缩略图 | | 缩略图 | (选中高亮✓) | +| | 名称 | | 名称 | | 名称 | | +| +------+ +------+ +------+ | +| 步骤②: [x]抖音 [x]快手 [x]小红书 [x]B站 每平台: [账号▾官方号] | +| 步骤③: 标题[________________] 话题[#____] 描述[多行textarea] | +| 右侧: 各平台发布预览卡片 | +| 步骤④: (•)立即发布 ( )定时 [日期时间选择器] [提交审核] | ++------------------------------------------------------------------+ + +组件: Steps + Grid(Card选择) + Upload + Checkbox + Select + Input + Textarea + Radio + DatePicker +模板: pages/Form/Step +验收: 必填校验; 提交后进入待审核/直接排期(视配置)。 + +## 5. 排期日历 + ++------------------------------------------------------------------+ +| [◀ 2026年8月 ▶] [今天] 图例: ●待发 ●已发 ●失败 | +| 一 二 三 四 五 六 日 | +| 1 2 3 4 5 6 | +| 7 8 9 [10●2] 11 12 13 (右侧/下方列出当日任务) | +| 14 15 16 17 18 19 20 | 09:00 xxx 抖音 待审核 | | +| ... | 14:00 yyy B站 已发布 | | ++------------------------------------------------------------------+ + +组件: Calendar(TDesign 1.x, 若无则以卡片网格自绘) + List + Tag +模板: 新建页面 +验收: 月视图任务点正确; 点击日期过滤当日任务。 + +## 6. 任务详情(Drawer 侧滑) + ++------------------------------------------------------------------+ +| X 任务详情 #1024 [重试][关闭] | +| 基本信息: 标题 / 平台 / 账号 / 创建人 / 排期时间 | +| 事件时间线: | +| ● 10:29:30 任务下发 Agent | +| ● 10:30:01 开始上传素材(进度 100%) | +| ● 10:32:10 平台审核通过, 已发布 | +| ● 10:32:11 回执截图 [缩略图][查看大图] 链接: https://... | +| 失败场景: 失败原因红色标注 + [重试] | ++------------------------------------------------------------------+ + +组件: Drawer + Descriptions + Timeline + ImageViewer + Tag + Button +模板: pages/Detail/Base 改造 +验收: 事件流完整; 截图可放大; 失败原因可见。 + +## 7. 素材库 + ++------------------------------------------------------------------+ +| [上传素材] 分组:[全部▾] 搜索:[____] 视图:[卡片][列表] | +| +--------+ +--------+ +--------+ | +| | 缩略图 | | 缩略图 | | 缩略图 | | +| | 名称.mp4| | 名称.jpg| | ... | | +| | 128MB | | 2.1MB | | | | +| | [预览][删除] | [预览][删除] | | | | +| +--------+ +--------+ +--------+ | +| 上传弹窗: [拖拽文件到此处] 进度条: ▓▓▓▓▓▓░░ 80% | ++------------------------------------------------------------------+ + +组件: Upload(dragger) + Progress + Grid + ImageViewer + Select + Input + Popconfirm +模板: pages/List/Card +验收: 大文件上传进度; sha256 入库; 预览/删除正常。 + +## 8. 账号台账 + 绑定挑战弹窗 + ++------------------------------------------------------------------+ +| [ + 绑定账号 ] | +| | 平台 | 账号昵称 | 状态 | 健康度 | 代理 | 最后活跃 | 操作 | | +| | 抖音 | xxx官方 | 已绑定 | ●良好 | 无 | 2小时前 | 重扫/解绑 | | +| | 快手 | yyy | 登录过期 | ●差 | 无 | 5天前 | 重新扫码 | | +| +--------------------------------------------------------------+ | +| 绑定弹窗: 选择平台[▾] → 二维码展示区 → 状态: 等待扫码... | +| [刷新二维码] (二维码由 token 本地渲染, 非截图) | ++------------------------------------------------------------------+ + +组件: Table + Dialog + Select + Button + Tag + Badge + qrcode.react(唯一新增依赖) +模板: pages/List/Base + Dialog +验收: 发起绑定→Agent 取码→弹窗显示→手机扫码→台账变绿。 + +## 9. 审核中心 + ++------------------------------------------------------------------+ +| 待审列表: | +| | 素材 | 标题 | 平台数 | 提交人 | 提交时间 | 操作 | | +| | 图 | xxx | 3 | 运营A | 10:00 | [预览][通过][驳回] | | +| 预览 Drawer: 左侧各平台 Tab 预览效果; 右侧批注框 | +| [通过] 或 [驳回 + 批注原因______] | ++------------------------------------------------------------------+ + +组件: Table + Drawer + Tabs + Textarea + Button +模板: pages/List/Base + Drawer +验收: 通过→排期; 驳回→回草稿+通知提交人。 + +## 10. 挑战中心(通知与挑战) + ++------------------------------------------------------------------+ +| 待处理: [类型] 二维码登录 [平台]抖音 [账号]xxx [时间]2分钟前 [处理] | +| 已完成: 短信验证码 小红书 ... 1小时前 ✓已解决 | +| QR 弹窗: 二维码展示(自动刷新) 提示: 请用抖音APP扫码 | +| 验证码弹窗: 已发送至手机尾号 1234 [输入框______] [提交] (可重输2次) | ++------------------------------------------------------------------+ + +组件: List/Table + Dialog + Input + Button + Tag + qrcode.react +验收: 五类挑战均可人工完成; 超时挂起可一键重发。 + +## 11. 通知 + ++------------------------------------------------------------------+ +| [全部已读] | +| ● 10:32 任务#1024 抖音 发布失败: 风控拦截 [查看] 已读 | +| ○ 10:00 抖音官方号 登录即将过期, 请重新扫码 [去扫码] 未读 | +| ○ 09:30 成员 运营B 提交了待审核任务 [去审核] 未读 | ++------------------------------------------------------------------+ + +组件: List + Badge + Button +验收: 事件产生即推送; 已读状态持久。 + +## 12. 成员与权限 + ++------------------------------------------------------------------+ +| [ + 邀请成员 ] | +| | 姓名 | 手机 | 角色 | 状态 | 加入时间 | 操作 | | +| | 张A | 138.. | 管理员 | 正常 | 08-01 | 改角色/停用 | | +| | 李B | 139.. | 运营 | 正常 | 08-10 | 改角色/停用 | | +| 邀请弹窗: 角色[▾运营] → 生成邀请链接 [复制] | ++------------------------------------------------------------------+ + +组件: Table + Dialog + Select + Button + Message(复制成功) +模板: pages/User +验收: 角色权限隔离生效; 停用成员无法登录。 + +## 13. 设置(四个子页) + ++------------------------------------------------------------------+ +| Tab: [工作区资料] [安全] [通知渠道] [Agent设备] | +| 资料: 名称[____] Logo[上传] 简介[____] [保存] | +| 安全: 2FA 开关 [○关(默认)] 登录设备: |Win Chrome| 杭州 | [吊销] | | +| 通知渠道: 企微[○开 配置] 钉钉[○关] 邮件[○关] | +| Agent设备: | 设备名 | 状态●在线 | 版本 | 最后心跳 | [吊销][指令] | | ++------------------------------------------------------------------+ + +组件: Tabs + Form + Switch + Upload + Table + Popconfirm +验收: 2FA 默认关; 吊销设备后客户端失联。 + +## 14. 审计日志 + ++------------------------------------------------------------------+ +| 筛选: [操作人____] [动作▾] [时间范围] [查询] | +| | 时间 | 操作人 | 动作 | 对象 | 详情 | | +| | 10:30| 张A | 通过 | 任务#1024 | 审核通过 | | +| | 10:00| 李B | 提交 | 任务#1024 | 提交待审核 | | ++------------------------------------------------------------------+ + +组件: Table + Select + DateRangePicker + Input +验收: 全操作可查。 + +## 15. admin · 租户管理 + ++------------------------------------------------------------------+ +| | 租户名称 | 套餐 | 成员数 | Agent数 | 状态 | 创建时间 | 操作 | | +| | 某某科技 | 试用 | 5 | 1 | 正常 | 08-01 | [停用]| | ++------------------------------------------------------------------+ + +组件: Table + Popconfirm +验收: 停用后租户不可登录。 + +## 16. admin · Agent 总览 + ++------------------------------------------------------------------+ +| | 租户 | 设备名 | 版本 | 在线 | 最后心跳 | 操作 | | +| | 某某 | PC-01 | 0.1 | ●在线 | 3s前 | [吊销][远程指令] | | ++------------------------------------------------------------------+ + +组件: Table + Badge + Popconfirm +验收: 在线状态实时; 吊销即时生效。 + +## 17. 客户端 · 配对引导(首次启动) + ++----------------------------------------------------------+ +| EveryPublish 发布助手 [—][□][X] | +| | +| 第一步: 在网页端 设置→Agent设备 获取配对码 | +| 配对码: [______] | +| [ 绑 定 ] | +| 状态: 绑定成功 ✓ / 失败原因(红字) | ++----------------------------------------------------------+ + +组件: WinUI 原生 (StackPanel/TextBox/Button/InfoBar) +验收: 5 分钟完成配对; 失败有明确原因。 + +## 18. 客户端 · 托盘菜单 + ++--------------------+ +| ● 已连接(延迟 12ms)| +| 待发任务: 3 | +| 打开面板 | +| 重新扫码 | +| 查看日志 | +| 退出 | ++--------------------+ + +组件: H.NotifyIcon 托盘菜单(Win32 interop) +验收: 最小化到托盘; 状态随连接实时变化。 + +## 19. 客户端 · 主面板 + ++----------------------------------------------------------+ +| 服务器: ● 已连接 延迟 12ms 版本 0.1.0 | +| 账号: | +| | 平台 | 状态 | 健康度 | 操作 | | +| | 抖音 | 已绑定 | ●良好 | [重新扫码] | | +| | 快手 | 过期 | ●差 | [扫码修复] | | +| 待发任务: 3 今日已发: 5 | +| [打开日志] [设置] | ++----------------------------------------------------------+ + +组件: WinUI 原生 (ListView/GridView/ProgressRing) +验收: 状态与服务器一致; 扫码入口直达挑战窗口。 + +## 20. 客户端 · 扫码挑战窗口 + ++----------------------------------------------------------+ +| 请使用 抖音APP 扫描二维码登录 | +| ▓▓▓▓▓▓▓▓ (二维码, token 本地渲染) ▓▓▓▓▓▓▓▓ | +| 状态: 等待扫码... → 已扫码,请在手机确认 → 登录成功 | +| [刷新二维码] [取消] | ++----------------------------------------------------------+ + +组件: WinUI + QRCoder(C#) +验收: 码过期自动刷新; 状态实时。 + +## 21. 客户端 · 验证码输入窗口 + ++----------------------------------------------------------+ +| 小红书 需要短信验证码 | +| 已发送至 138****1234 | +| [______] [提交] (剩余重试 2 次) | ++----------------------------------------------------------+ + +组件: WinUI TextBox/Button +验收: 回传服务器→Agent 回填; 错误可重输。 + +## 22. 客户端 · 设置(代理与日志) + ++----------------------------------------------------------+ +| Tab: [代理] [日志] [关于] | +| 代理: 每账号代理配置: 抖音官号 → [socks5://____] [测试] | +| 日志: 文本区(滚动) + [导出] [清空] | +| 关于: 版本 0.1.0 [检查更新] | ++----------------------------------------------------------+ + +组件: WinUI Pivot + TextBox + Button +验收: 代理测试连通性; 日志可导出。 diff --git a/docs/prd.md b/docs/prd.md new file mode 100644 index 0000000..9aabdbd --- /dev/null +++ b/docs/prd.md @@ -0,0 +1,173 @@ +# EveryPublish PRD 产品需求文档 + +> 版本 v1.0 · 2026-08-20 · 状态:待评审 · 技术栈以 docs/tech-stack.md 为准 + +## 1. 文档信息与文档体系 + +### 1.1 版本记录 + +| 版本 | 日期 | 说明 | +|---|---|---| +| v1.0 | 2026-08-20 | 初稿,覆盖一期 MVP 全部功能需求 | + +### 1.2 文档体系位置(PRD 与其他文档的关系) + +| 层级 | 文档 | 回答的问题 | 本项目对应 | +|---|---|---|---| +| 商业/市场 | MRD / BRD | 为什么做、市场与商业 | 并入本文档 §2,暂不单列 | +| **产品需求** | **PRD(本文档)** | **做什么、给谁用、什么体验** | docs/prd.md | +| 技术需求/规格 | TRD / SRS | 怎么做、接口与协议 | docs/system-design.md + docs/tech-stack.md | +| 流程与进度 | 逻辑图 / 排期 | 流程怎么走、何时交付 | docs/logic-diagrams.md + docs/delivery-plan.md | + +> 注:SPDC / MDC 非行业标准缩写。若指「技术方案类」(SPDC≈方案/规格)与「模块开发上下文」(MDC≈模块设计),在本文档体系中分别由 system-design/tech-stack 与 logic-diagrams 承接;待确认团队定义后对齐(见 §12 开放问题)。 + +## 2. 产品概述 + +### 2.1 背景与问题(痛点) + +- 企业通常只有一个官方账号、多名运营协作;现有开源发布工具均为「单人本地扫码发布」,多人共用账号混乱、无审核、无审计。 +- 国内平台(抖音/快手/小红书/视频号)无公开内容发布 API,只能浏览器自动化;IP 风控要求国内账号用国内 IP、国外账号用国外 IP。 +- 客户诉求:越简单越好、发布越快、账号越稳、覆盖全部平台、不违反平台官方规则。 + +### 2.2 产品定位(一句话) + +**企业多平台官方账号的发布中台:网页控制台协作、审核、排期,客户端单点持号、自动发布。** + +### 2.3 目标用户 + +| 用户 | 场景 | 优先级 | +|---|---|---| +| 企业新媒体运营团队 | 单官方账号 + 多运营协作,需审核与审计 | 主 | +| 代运营 / MCN | 多客户多账号,需隔离与批量 | 次 | +| 单人创作者 | 自用简化版(隐藏审核流) | 次 | + +### 2.4 核心价值主张 + +- **简单**:装客户端 → 扫码绑定 → 网页排期,三步上手,零配置。 +- **快**:任务下发 <300ms,多平台并行发布,失败一键重试。 +- **稳**:凭据单机加密持有,设备+IP+行为三固定,账号风控最小化。 +- **覆盖**:一期国内四平台(抖音/快手/小红书/B站),二期视频号+国际平台官方 API 通道。 +- **合规**:官方 API 优先;自动化通道分级开通、知情告知;明确不做矩阵养号等灰产。 + +### 2.5 职责边界(网站 vs 客户端,两类账号) + +**一句话**:网站 = 任务分配中枢 + 状态看板;客户端 = 持号 + 登录鉴权 + 发布执行。发布流量永不经过网站服务器。 + +| 账号类型 | 谁负责登录鉴权 | 网站/服务器的角色 | +|---|---|---| +| 控制台用户账号(运营/管理员登录后台,如 demo@everypublish.io) | 网站(注册/登录/JWT) | 管理面板本体,分配任务的人 | +| 平台账号(抖音/快手/小红书/B站/视频号/海外,即发布目标账号) | 客户端 Agent(扫码/验证/本机保存凭据) | 只下发任务 + 接收状态,**零凭据** | + +- 网站对平台账号**不负责登录鉴权**,只做两件事:**下发/分配发布任务**(<1KB 元数据经 WSS 推给客户端)与**接收执行状态回传**。 +- 平台账号的登录、扫码验证、凭据保存、实际发布全部在客户端(客户自己的电脑)完成;服务器账号表只存 `平台 / 备注 / 状态 / 绑定设备` 等元数据,**不含任何 cookie / 密码 / token**。 +- 绑定流程示例:网站生成「挑战」记录并推给客户端 → 客户端拉起扫码 → 客户端回传「已解决」→ 网站把账号标记为「已绑定」。网站全程不接触、不存储平台凭据。 + +## 3. 范围 + +### 3.1 一期(MVP) + +- 网页端全部功能:用户端(客户工作台)+ 平台端(admin 角色)。 +- 客户端 Windows 版:安装配对、托盘常驻、扫码挑战、自动更新。 +- 平台:抖音 / 快手 / 小红书 / B站(视频号二期)。 +- 素材:服务器本地盘 + 签名直链下载。 +- 2FA:能力预留,默认关闭。 + +### 3.2 二期(不在本期验收) + +- 视频号、官方 API 通道(X/IG/YouTube/TikTok)、OSS 双桶与断点续传、托管 Agent、代理采购入口、数据报表增强、Windows 服务化(支持客户 VPS 形态)。 + +### 3.3 明确不做(红线) + +- 矩阵养号、批量注册、搬运去重绕过、刷量、私信轰炸;违反平台规则的功能一律不做。 + +## 4. 用户角色与权限 + +| 角色 | 端 | 核心权限 | +|---|---|---| +| 管理员 | 网页端 | 成员/角色、账号台账、绑定/解绑、Agent 设备、审计、设置 | +| 审核员 | 网页端 | 审核通过/驳回、查看素材与任务 | +| 运营 | 网页端 | 素材上传、创建/排期/重试任务、查看结果 | +| 账号负责人(装机人) | 客户端 | 安装配对、扫码绑定、处理登录过期 | +| 平台 admin | 网页端 | 租户管理、Agent 总览、渠道状态(后置) | + +## 5. 功能需求 + +> 优先级:P0 = 一期必须;P1 = 一期尽量;P2 = 二期。 + +### 5.1 网页端 · 用户端(客户工作台) + +| 模块 | 功能 | 优先级 | 验收要点 | +|---|---|---|---| +| 认证与账户 | 账号密码登录、refresh 轮换、登出、登录设备管理;2FA 默认关 | P0 | 登录成功/登出后会话失效;refresh 自动续期 | +| 工作区与成员 | 成员邀请、角色分配、停用 | P0 | 角色权限隔离正确 | +| 账号台账 | 平台账号列表(绑定状态/健康度/代理/最后活跃)、发起绑定(生成扫码挑战)、重新扫码、解绑 | P0 | 绑定→扫码→台账变绿全流程 | +| 素材库 | 上传(multipart+进度)、列表/分组/标签、预览、删除 | P0 | 大文件上传成功;sha256 校验入库 | +| 发布任务 | 新建(选素材→平台账号→标题话题→定时/立即→提交审核)、任务列表(状态筛选)、排期日历、任务详情(事件时间线+回执截图+失败原因)、重试、取消 | P0 | 任务全生命周期流转正确 | +| 审核流 | 待审队列、分平台预览、通过/驳回+批注、可配置跳过 | P0 | 驳回回退草稿;跳过时直接排期 | +| 挑战中心 | QR 从 token 本地渲染、验证码输入、APP确认指引、超时挂起、一键重发 | P0 | 五类挑战均可人工完成 | +| 通知 | 站内通知(挑战/失败/登录过期)、已读 | P0 | 事件产生即推送 | +| 审计 | 全操作日志查询 | P0 | 谁在何时做了什么可查 | +| 设置 | 工作区资料、2FA 开关(默认关)、通知渠道(企微/钉钉/邮件)、Agent 设备列表与吊销 | P0/P1 | 设备吊销后客户端失联 | + +### 5.2 网页端 · 平台端(admin) + +| 模块 | 功能 | 优先级 | 验收要点 | +|---|---|---|---| +| 租户管理 | 客户列表、状态、配额(简单)、停用 | P0 | 停用后租户不可登录 | +| Agent 总览 | 设备列表:在线/版本/强制吊销/远程指令 | P0 | 在线状态实时 | +| 渠道状态 | 平台适配器状态、灰度开关 | P2 | — | +| 风控台账 | 风控事件、账号冷却记录 | P2 | — | + +### 5.3 客户端(Windows) + +| 模块 | 功能 | 优先级 | 验收要点 | +|---|---|---|---| +| 安装与配对 | 安装包、配对码绑定工作区、首次引导 | P0 | 新机 5 分钟内完成配对 | +| 托盘常驻 | 开机自启、最小化托盘、在线状态显示 | P0 | 重启后自动恢复连接 | +| 扫码与挑战 | QR 展示/自动刷新、验证码回填、APP确认轮询 | P0 | 与网页端挑战中心联动 | +| 任务执行 | 接收/幂等去重/执行/回传;浏览器档案与指纹 | P0 | 重复任务不重复执行 | +| 运维 | 本地日志、自动更新、连接诊断 | P0/P1 | 断线指数退避自愈 | + +## 6. 关键流程(引用 docs/logic-diagrams.md) + +- 账号绑定(图8 登录链路优化):API 取码 → 只传 token → 本地重渲染 → 短轮询检测。 +- 发布全链路(图6):提交 → 审核 → 排期 → 下发 → 执行 → 回传 → 实时状态;异常分流(网络重试/挑战/风控冷却)。 +- 二次验证兜底(图7):五类挑战人工兜底,超时挂起、一键重发。 +- 重试状态机(图5):网络退避 1m/5m/15m/1h;登录态转挑战;风控转人工。 + +## 7. 非功能需求 + +| 类别 | 指标 | +|---|---| +| 性能 | 任务下发 <300ms;网页页面 P95 <2s;WSS 心跳 10s/离线判定 25s | +| 可靠性 | 断线重连 <10s 恢复;任务幂等(同任务不重复);挑战超时策略完整 | +| 安全 | 四层鉴权;凭据不出本机(服务器零凭据);日志脱敏;审计完整 | +| 易用性 | 三步上手;登录过期提醒+一键重扫;失败原因可见+一键重试 | +| 合规 | 自动化通道知情告知并签署;内容发布前自检提示;红线功能拒绝 | +| 可维护 | 协议版本化(zod/Go 侧校验);平台适配器独立可灰度 | + +## 8. 数据需求(一期最小) + +- 任务事件流(状态/时间/原因/截图);账号健康度(登录态预测过期);发布统计(成功数/失败原因分布);审计与通知记录。 + +## 9. 约束与依赖 + +- 平台风控与前端改版(最大持续风险);IP/代理采购(客户侧);Windows 10/11 环境;扫码需客户手机(微信/抖音等 APP)。 +- 一期素材存服务器本地盘:需磁盘配额与清理策略。 + +## 10. 验收标准 + +见 docs/delivery-plan.md §7 最终验收清单(登录与权限 / 任务全生命周期 / <300ms 与幂等 / 挑战全流程 / 素材校验 / 四平台真实发布 / Windows 新机全流程 / 凭据不出本机 / 安装包与自动更新)。 + +## 11. 里程碑 + +见 docs/delivery-plan.md §6 日计划(D1-D13 + 缓冲):D1-D7 网站端,D8-D11 客户端,D12-D13 联调交付。 + +## 12. 开放问题(待评审确认) + +1. SPDC / MDC 的确切定义——请提供全称,以对齐文档体系。 +2. 定价与计费模式(订阅/按账号数/按平台数)。 +3. 通知渠道一期范围(企微/钉钉/邮件是否全做)。 +4. 代理采购入口是否一期内置(默认客户自带)。 +5. 「手机版本」此前表述——指首版还是未来手机端 App,需确认。 +6. 单人版是否一期提供(隐藏审核流)。 diff --git a/docs/progress.md b/docs/progress.md new file mode 100644 index 0000000..6721971 --- /dev/null +++ b/docs/progress.md @@ -0,0 +1,37 @@ +# EveryPublish 开发进度跟踪 + +> 状态:进行中 · 阶段一(网页端 D1-D7)。目标:goal-8c8e81e4-d2ed-4ba9-a4f7-d413c4f9e00b。 +> 更新规则:每天结束更新本表;检查点①/②需用户确认后才进入下一阶段。 + +## 总览 + +| 阶段 | 状态 | 说明 | +|---|---|---| +| 阶段一 网页端 D1-D7 | 🟡 进行中 | D1-D7 全部完成,进入检查点①网页端手动测试 | +| 检查点① 网页端手动测试 | ✅ 用户已验收确认 | 用户确认进入阶段二 | +| 阶段二 客户端 D8-D11 | 🟡 进行中 | D8-D11 代码全部交付,进入检查点②交付用户测试 | +| 检查点② 交付用户测试 | ✅ 已交付 | 最终产物切换为 Electron 壳(client/desktop,正规安装包+正常 UI 四页签)替代 WinUI3 源码交付;NSIS 安装包 `EveryPublish Setup 0.2.0.exe` 构建成功(图标 app.ico 修复后 EB_EXIT=0,内嵌 agent-core.exe);服务器部署包 dist/deploy(linux-amd64/windows-amd64+web+docker-compose+.env 样例)齐备;内网连接/填写说明(docs/client-install-guide.md + dist/deploy/deploy-README.md + 测试账号方案)交付;模拟 E2E 第 N 轮实测:登录→设备在线(winui-fakeall/core-d8)→账号 active→素材 ready→任务→submit→approve→假执行→status success;等用户在内网 192.x 实机验收 | +| 阶段三 联调交付 D12-D13 | ⚪ 未开始 | | + +## 逐日验收 + +| 日 | 任务 | 验收标准 | 状态 | 证据 | +|---|---|---|---|---| +| D1 | 工程化与骨架 | dev 无报错、菜单空壳可点、/health 200、DB 连上 | ✅ 通过 | 后端:/health 200(db:true,redis:true)、12 表 AutoMigrate、go build/vet/run 通过(:8090);前端:示例页+mock 清理、17 个页面空壳+2 组菜单路由、vite dev :3003 启动 200、代理到后端 404(gin) 正常、tsc --noEmit 0 错误 | +| D2 | 认证与工作区 API | 单测过;前端登录页真实登录 | ✅ 通过 | 后端:Argon2id 口令、JWT access 15min/refresh 7d 轮换(Redis jti 吊销)、register/login/refresh/logout/me、workspaces CRUD、members 邀请(JWT)/加入/改角色/移除、审计中间件落库;go test 单测 4 项+集成测试 2 场景全过(真 MySQL everypublish_test + 真 Redis);前端:登录/注册页接真实 API、401 自动刷新重试(单飞)、路由守卫、启动拉取用户信息、Header 显示昵称+登出;tsc 0 错误;经 vite 代理 curl 验证登录 200/错误密码 401 | +| D3 | 业务 API | 单测+接口文档;curl 全接口可跑 | ✅ 通过 | 账号台账 CRUD+bind→challenge(平台白名单/绑定中禁删);素材 multipart 流式 sha256+同工作区去重+签名直链(10min 一次性 200→404 已验证);任务状态机(纯函数 4 项单测:happy path/失败重试/挂起恢复/非法转移) + CRUD/submit/approve/reject/resubmit/retry/cancel+驳回/通过自动通知;挑战 solve/resend/suspend+过期软置;通知列表/已读/全部已读;go test 全过;curl E2E 账号/素材/直链/任务/通知全链路 200;接口文档 docs/api.md(含状态机图) | +| D4 | WSS 网关+假执行器 | 假执行器闭环 <300ms | ✅ 通过 | 双通道 WSS:/ws/agent(Ed25519 hello 验签,坏签名拒绝已验证)+ /ws/browser(JWT 订阅);心跳/hello.ack 签名;task.push→ack→result 全链路;challenge.new→solve→账号激活;设备配对码(5min 一次性);下发器(审批即时触发+1s 轮询兜底,先落库后推送防竞态);假执行器 server/cmd/fake-agent;集成测试全链路 PASS,下发延迟实测 28.7ms(<300ms);前端 utils/ws.ts 自动重连 + 工作台真实页面(设备在线状态+最近任务实时变绿),经浏览器通道收到 3 条 task.status 事件(末条 success) | +| D5 | 页面① | 页面与真实 API 联通 | ✅ 通过 | 登录/仪表盘(D2/D4 已真实化)+ 成员页(列表/邀请弹窗生成链接+复制/行内改角色/移除,owner 保护)+ 设置 4 页(工作区资料可改名保存、我的账号、2FA 默认关留位、通知渠道二期留位、设备列表+吊销)+ 邀请接受页 /invite(登录态判断/邮箱校验/成功引导);后端补 PUT /agent/devices/:id/revoke(吊销+强制下线)与 PUBLIC_BASE_URL(浏览器链接与 Agent 直链分离);tsc 0 错误;6 页面全 200;邀请链接实测指向 3003 前端;吊销实测生效 | +| D6 | 页面② | 全流程可点通 | ✅ 通过 | 素材库(Upload 组件接真实上传、类型/分组筛选、sha256/大小/签名直链弹窗+复制、删除);账号台账(平台标签/状态/健康度/IP 画像、添加弹窗、绑定弹窗监听 challenge.status WS 自动关闭);任务列表(状态筛选、按状态渲染操作:提交/通过/驳回(批注弹窗)/重提/重试/取消/删除、详情 Drawer、WS 实时刷新);新建发布四步向导(选素材→选账号(仅 active)→填内容(TagInput/定时 DatePicker)→确认提交);排期日历(Calendar 组件按日展示任务 Tag);tsc 0 错误;5 页面全 200;全流程模拟:素材经前端代理上传→新账号→bind→假执行器扫码→active→向导 payload 建任务→提交→审核→自动下发→status:success | +| D7 | 页面③+admin | 角色权限正确、admin 入口可用 | ✅ 通过 | 审核中心(待审列表+预览抽屉+通过/驳回批注);挑战中心(QR 本地渲染 TDesign QRCode+验证码输入+挂起/一键重发,WS 实时刷新);通知(未读角标+已读/全部已读);审计(动作筛选+分页);admin 两页(租户列表/停用启用、Agent 总览/强制吊销);菜单按角色过滤(meta.admin 仅 admin 可见);后端 /admin 组 AdminRequired 守卫;实测:普通用户 admin API=403、admin 账号=200;tsc 0 错误;6 页面全 200 | +| D8 | agent-core | core 单测过;与服务器假执行闭环 | ✅ 通过 | client/core(Go):设备身份(Ed25519 密钥 0600 持久化)、凭据保险库(AES-256-GCM,主密钥独立文件,防篡改单测)、执行器框架(Registry+兜底+ChallengeSolver)、假执行器、WSS 客户端(1s/5s/15s/60s 指数退避重连+握手重置、15s 心跳、写锁防并发写、任务幂等:已执行任务重放缓存结果不重复执行)、配对(AutoPair/PairWithCode)、配置持久化;单测 4 包全过(含 stub 服务器握手验签/任务往返/幂等重放/断线重连);Windows 交叉编译 agent-core.exe 9.0M 成功;真服务器闭环:core 设备指定绑定→挑战自动解决→账号 active→任务下发→执行→success(core 日志全程确认) | +| D9 | WinUI3 壳 | Windows 实机:安装→配对→假任务→回传 | ✅ 代码交付(实机验收并入检查点②) | Go 侧:core 新增 localhost 控制服务(随机端口+token 写 local.json,/status 含 server/deviceId/name、/challenges、/challenges/:id 提交、/pair 配对回调,401 鉴权)+ 挑战队列/人工解决/在线状态接口,单测 5 包全绿,curl 实测 401/状态/配对错误传播全通过;WinUI 壳:csproj(net8.0-windows10.0.19041.0+WindowsAppSDK 1.5 自包含+H.NotifyIcon.WinUI)+MainWindow(顶部导航4页/关闭最小化托盘/托盘右键打开退出)+状态页(3s轮询)/配对页/挑战页(2s轮询+验证码提交)/设置页(服务器地址+开机自启+重启核心)+Watchdog(5s心跳/崩溃自拉起/3次失败弹窗)+AutoStart(HKCU Run)+打包脚本 build-installer.ps1(生成图标→Go 交叉编译→dotnet publish→组装→zip);Mac 无法编译 C#/WinUI,Windows 构建步骤与验证清单见 client/ui/README.md | +| D10 | B站真实发布 | B站真实发布成功 | ✅ 代码交付(真实账号发布验证并入检查点②) | client/core/internal/exec/bilibili:无浏览器投稿全链路(preupload→upos/bda2 分片上传→分片合并→x/vu/web/add 投稿,协议依据 biliup MIT 源码)+ 扫码登录(passport web qrcode generate→poll 轮询 86101/86090/0→跨域 URL 提取 SESSDATA/bili_jct/DedeUserID→加密保存保险库);ChallengeSolver 接口扩展 onQR 回调(二维码推壳展示);执行器接入 main(B站走真实链路,其它平台假兜底);单测 3 项全过(stub 全链路:分片参数/合并 parts 数/csrf/投稿体校验;QR 登录二次轮询成功+凭据落库;未扫超时);go vet 干净;agent-core.exe 交叉编译成功 | +| D11 | 抖音/快手/小红书 | 三平台真实发布+截图回执 | ✅ 代码交付(真实账号发布验证并入检查点②) | client/core/internal/exec/{cdp,cdnbase,douyin,kuaishou,xiaohongshu}:CDP 基础设施(rod-launcher 启动、每账号独立档案 UserDataDir、proxy-server 代理绑定、go-rod/stealth 指纹反自动化、系统 Chrome/Edge 自动探测、cookie 注入/导出、扫码登录通用流程:登录页→QR 截图落盘 file://→onQR 推壳→轮询 URL 离开登录页→cookie 加密入库);通用发布流程(上传页→注 cookie→传视频→等表单 180s→填标题/描述话题→点发布→等成功跳转→截图回执 PNG);三平台选择器按 social-auto-upload 2026-06 版配置(抖音 creator-micro/content/manage 成功特征、快手 article/manage/video、小红书 publish/success);main 注册三平台真实链路;单测 4 项(JSON 转义往返/素材下载/HTTP 错误/Chrome 路径探测);WinUI 挑战页补 Image 二维码显示(http/file:// 均可);go vet 干净;agent-core.exe 交叉编译 14M 成功 | +| D12 | 联调+异常注入 | 验收清单全过 | ⚪ | | +| D13 | 打包+内部试用 | 新机安装走通、交付物齐 | 🟡 打包完成待用户试用 | Electron 壳(四页签 UI/托盘/开机自启/fakeAll)+NSIS 安装包构建成功(见检查点②);部署包+安装指南+测试账号说明齐;用户内网实机安装与联调由 D12-D13 继续 | + +## 服务端口约定 + +- 本机开发:Go 后端 :8090(8080 被 nginx 占用);MySQL 3306(Docker everypublish-mysql);Redis 6379(本机)。 +- 前端 dev:Vite :3002(starter 默认),/api 走 vite proxy 到 :8090。 diff --git a/docs/system-design.md b/docs/system-design.md new file mode 100644 index 0000000..7e40be1 --- /dev/null +++ b/docs/system-design.md @@ -0,0 +1,234 @@ +# 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 即够;凭据/挑战类数据才端到端加密。 \ No newline at end of file diff --git a/docs/tech-stack.md b/docs/tech-stack.md new file mode 100644 index 0000000..02f056b --- /dev/null +++ b/docs/tech-stack.md @@ -0,0 +1,47 @@ +# EveryPublish 技术栈定版(唯一权威来源) + +> 2026-08-20 · 其他文档中与本文件冲突之处,以本文件为准。 + +## 1. 总览 + +| 端 | 层 | 技术 | +|---|---|---| +| 网页端 | 页面(UI) | React 18 + TypeScript + TDesign React(Tencent starter,MIT,已拉取 apps/web) | +| 网页端 | 服务端/API | **Go**:gin + coder/websocket + asynq + GORM + argon2 | +| 客户端 | UI 壳 | WinUI 3(C# / .NET 8) | +| 客户端 | 核心 | **Go**:WSS 客户端 + go-rod(CDP)+ 凭据保险库 | +| 数据 | 数据库 | **MySQL 8.x**(GORM + go-sql-driver/mysql;迁移 golang-migrate) | +| 数据 | 队列/缓存 | Redis 7(asynq) | +| 存储 | 一期 | 服务器本地盘(签名直链下载) | +| 存储 | 二期 | OSS 双桶(StorageDriver 抽象预留) | + +## 2. 「网页端与后端都用 Go」的说明 + +- **页面层仍是 React + TDesign**(starter 已拉取预览,MIT 可商用),不写自定义样式。 +- 「用 Go」指两处:**网页端的服务端/API 层** 与 **客户端 agent-core**,统一 Go 语言。 +- 二者共享同一协议 Go module(server 与 agent-core 引用同一 shared/proto 包),消息 Schema 只写一次。 + +## 3. Go 选型明细 + +- Web 框架:gin +- WebSocket:github.com/coder/websocket(原生 JSON 协议,不用 Socket.IO) +- 任务队列:asynq(Redis) +- ORM/DB:GORM + go-sql-driver/mysql(MySQL 8.x);迁移 golang-migrate +- 鉴权:golang-jwt + x/crypto/argon2(用户);Ed25519 设备签名(Agent) +- 浏览器自动化(客户端):go-rod + rod-stealth,驱动系统 Chrome/Edge +- 常驻服务(二期):kardianos/service + +## 4. 目录结构(monorepo) + +- web/ —— React 前端(现有 apps/web 的 starter) +- server/ —— Go 服务器(REST + WSS 网关 + 队列 worker) +- client/ui —— WinUI 3 壳(C#) +- client/core —— agent-core(Go) +- shared/ —— 协议 Go module(server 与 core 共用) +- tools/ —— 联调诊断(diag-connection.mjs 等) + +## 5. 版本与工具 + +- Go 1.23+;Node 20 LTS(前端,starter 依赖升级见交付文档 D1);.NET 8;MySQL 8.x;Redis 7 +- 包管理:pnpm(前端)、go mod(后端) +- CI:GitHub Actions(前端构建 / Go 单测 / WinUI 打包) diff --git a/docs/web-test-report.md b/docs/web-test-report.md new file mode 100644 index 0000000..1a9397a --- /dev/null +++ b/docs/web-test-report.md @@ -0,0 +1,60 @@ +# EveryPublish 网页端测试报告(检查点①) + +> 日期:2026-08-20 · 范围:阶段一网页端 D1-D7 交付 · 结论:**核心链路全部通过,可进入用户验收** + +## 一、测试概况 + +| 项 | 结果 | +|---|---| +| Go 单元+集成测试 | ✅ 全绿(auth 4 项、task 状态机 4 项、API 集成 4 场景、WSS 全链路 1 场景) | +| TypeScript 类型检查 | ✅ `tsc --noEmit` 0 错误 | +| 前端生产构建 | ✅ `pnpm build` 成功(仅 chunk 体积提示,非错误) | +| 手动链路测试 | ✅ 24 项断言 24 通过(含 3 项环境/断言修正后复测通过) | +| 断线重连兜底 | ✅ 下线不丢任务、重连 1 秒内自动补发 | + +## 二、六条核心链路测试结果 + +| # | 链路 | 关键断言 | 结果 | +|---|---|---|---| +| 1 | 注册登录 | 注册 200 / 重复邮箱 409 / 错误密码 401 / 刷新轮换后旧 refresh 401 / me 查询 | ✅ | +| 2 | 工作空间 | 创建 / 改名 | ✅ | +| 3 | 素材直传 | 上传 sha256 / 同文件去重 dedup=true / 直链一次 200 二次 404(一次性) | ✅ | +| 4 | 账号绑定 | 新增→bind→challenge 推送→扫码→active(绑定到设备) | ✅ | +| 5 | 任务全生命周期 | 创建→提交→审核→下发→执行→success;驳回→rejected→重提;取消 | ✅ | +| 6 | 成员协作 | 邀请→加入→角色 operator 生效 | ✅ | +| 7 | 审计 | auth.login / task.create 等动作埋点落库、按动作筛选 | ✅ | +| 8 | 权限 | 普通用户访问 admin API = 403;admin 账号 = 200 | ✅ | + +## 三、发现的问题与处理 + +| 问题 | 类型 | 处理 | +|---|---|---| +| 假执行器未绑定到测试工作空间(导致绑定/下发无设备) | 测试环境 | 每工作空间独立假执行器实例;非代码缺陷 | +| 审计断言用 `=1` 而实际多记录 | 测试脚本 | 修正断言为存在性;审计功能正常 | +| 下发器 ack 竞态(先推后写库) | 代码缺陷 | 已修复:先落库 dispatched 再推送(D4 已改) | +| 服务器异常退出残留 online 状态 | 代码缺陷 | 已修复:启动自愈重置 offline | +| 8080 被 nginx 占用 | 环境 | 后端改用 :8090 | + +## 四、用户验收清单(请您逐项验证) + +1. 打开 http://127.0.0.1:3003/login/index 注册新账号并登录(应进入工作台)。 +2. 工作台:看到 Agent 设备(在线/离线)与最近任务实时状态。 +3. 账号管理:添加账号 → 点「绑定」→ 弹窗等待 → 客户端扫码后自动变「已绑定」(无客户端时可跳过,观察「绑定中」状态)。 +4. 素材库:上传视频 → 列表出现,重复上传同一文件应提示去重。 +5. 发布管理 → 新建发布:四步向导选择素材/账号/填内容 → 创建草稿。 +6. 任务列表:草稿「提交审核」→ 审核中心「通过」→ 状态变绿「发布成功」(需在线客户端)。 +7. 成员管理:邀请一个邮箱 → 生成链接 → 复制。 +8. 挑战中心 / 通知 / 审计:数据可查看、操作可用。 +9. 设置 → Agent 设备:可吊销设备(吊销后设备下线)。 +10. 退出登录 → 重新登录(令牌刷新正常)。 + +## 五、环境与运行状态 + +- 后端 Go 服务:http://127.0.0.1:8090(`/health` 200) +- 前端 dev:http://127.0.0.1:3003(生产构建已通过,`apps/web/dist/`) +- MySQL(Docker everypublish-mysql)+ Redis(本机)运行中 +- 假执行器:demo 工作空间 + tester1 工作空间各一台(模拟客户端) + +## 六、结论 + +网页端阶段一交付满足《docs/delivery-plan.md》§7 第 1-5 项验收。**建议进入用户验收;确认通过后开始阶段二(Windows 客户端 D8-D11)。** diff --git a/shared/go.mod b/shared/go.mod new file mode 100644 index 0000000..7dc92f7 --- /dev/null +++ b/shared/go.mod @@ -0,0 +1,3 @@ +module everypublish/shared + +go 1.23 diff --git a/shared/proto/messages.go b/shared/proto/messages.go new file mode 100644 index 0000000..086e80c --- /dev/null +++ b/shared/proto/messages.go @@ -0,0 +1,138 @@ +package proto + +import ( + "encoding/json" + "time" +) + +// ---- WSS 消息类型(server 与 agent-core 共用,Schema 只写一次)---- +const ( + TypeHello = "hello" + TypeHelloAck = "hello.ack" + TypeHeartbeat = "heartbeat" + TypeHeartbeatAck = "heartbeat.ack" + TypeTaskPush = "task.push" + TypeTaskAck = "task.ack" + TypeTaskResult = "task.result" + TypeTaskCancel = "task.cancel" + TypeChallenge = "challenge.new" + TypeChallengeAck = "challenge.ack" + TypeChallengeSolve = "challenge.solve" + TypeAgentStatus = "agent.status" + TypeError = "err" +) + +// Envelope WSS 统一信封:网关只透传小于1KB元数据,素材一律走签名直链 +type Envelope struct { + ID string `json:"id"` + Type string `json:"type"` + Payload json.RawMessage `json:"payload"` + TS int64 `json:"ts"` +} + +// NewEnvelope 构造信封 +func NewEnvelope(id string, typ string, payload interface{}) *Envelope { + raw, err := json.Marshal(payload) + if err != nil { + raw = []byte("{}") + } + return &Envelope{ID: id, Type: typ, Payload: raw, TS: time.Now().UnixMilli()} +} + +// DeviceHello Agent 上线:设备ID + Ed25519 签名(nonce||ts)防伪造 +type DeviceHello struct { + DeviceID string `json:"deviceId"` + Nonce string `json:"nonce"` + TS int64 `json:"ts"` + Sig string `json:"sig"` + Version string `json:"version"` +} + +// HelloAck 服务器应答(携带服务端 nonce 供会话内签名) +type HelloAck struct { + ServerNonce string `json:"serverNonce"` + SessionID string `json:"sessionId"` + ServerTS int64 `json:"serverTs"` + Sig string `json:"sig"` +} + +// Heartbeat 心跳(30s) +type Heartbeat struct { + DeviceID string `json:"deviceId"` + TS int64 `json:"ts"` +} + +// HeartbeatAck 心跳应答 +type HeartbeatAck struct { + ServerTS int64 `json:"serverTs"` +} + +// TaskPush 任务下发(仅元数据;素材 URL 为签名直链) +type TaskPush struct { + TaskID string `json:"taskId"` + Platform string `json:"platform"` + AccountID string `json:"accountId"` + AccountName string `json:"accountName"` + Title string `json:"title"` + Content string `json:"content"` + Tags []string `json:"tags"` + MaterialURLs []string `json:"materialUrls"` + ScheduleAt int64 `json:"scheduleAt"` + Priority int `json:"priority"` +} + +// TaskAck Agent 确认接收 +type TaskAck struct { + TaskID string `json:"taskId"` + Accept bool `json:"accept"` + Reason string `json:"reason,omitempty"` +} + +// TaskResult 执行结果回传 +type TaskResult struct { + TaskID string `json:"taskId"` + Status string `json:"status"` + PublishedURL string `json:"publishedUrl,omitempty"` + Receipts []string `json:"receipts,omitempty"` + Error string `json:"error,omitempty"` + FinishedAt int64 `json:"finishedAt"` +} + +// TaskCancel 取消任务 +type TaskCancel struct { + TaskID string `json:"taskId"` + Reason string `json:"reason,omitempty"` +} + +// Challenge 二次验证挑战(扫码/验证码/APP确认/人工兜底) +type Challenge struct { + ChallengeID string `json:"challengeId"` + AccountID string `json:"accountId"` + Platform string `json:"platform"` + Kind string `json:"kind"` + QRToken string `json:"qrToken,omitempty"` + QRURL string `json:"qrUrl,omitempty"` + Prompt string `json:"prompt"` + ExpiresAt int64 `json:"expiresAt"` +} + +// ChallengeAck 挑战应答 +type ChallengeAck struct { + ChallengeID string `json:"challengeId"` + Action string `json:"action"` +} + +// ChallengeSolve 网页端人工完成挑战 +type ChallengeSolve struct { + ChallengeID string `json:"challengeId"` + Value string `json:"value,omitempty"` +} + +// AgentStatus Agent 状态上报 +type AgentStatus struct { + DeviceID string `json:"deviceId"` + Online bool `json:"online"` + Busy bool `json:"busy"` + Version string `json:"version"` + TS int64 `json:"ts"` +} diff --git a/tools/diag-connection.mjs b/tools/diag-connection.mjs new file mode 100644 index 0000000..5f85d8b --- /dev/null +++ b/tools/diag-connection.mjs @@ -0,0 +1,56 @@ +#!/usr/bin/env node +// 连接诊断: TCP / WS 分段计时, 零依赖, Node >= 22 (内置 WebSocket) +// 用法: node tools/diag-connection.mjs ws://192.168.1.5:3001 [次数] +// 说明: WS 模式下服务端需对 {"type":"ping"} 回应任意消息(建议 {"type":"pong"}) +import net from "node:net"; + +const url = process.argv[2]; +const times = Number(process.argv[3] || 5); +if (!url) { + console.error("用法: node tools/diag-connection.mjs [次数]"); + process.exit(1); +} +const u = new URL(url); +const host = u.hostname; +const port = Number(u.port || (u.protocol === "wss:" || u.protocol === "https:" ? 443 : 80)); +const isWs = u.protocol === "ws:" || u.protocol === "wss:"; +const tcpMs = [], openMs = [], rttMs = []; + +for (let i = 0; i < times; i++) { + const t0 = performance.now(); + try { + await new Promise((res, rej) => { + const s = net.connect({ host, port }); + const to = setTimeout(() => { s.destroy(); rej(new Error("TCP timeout")); }, 3000); + s.once("connect", () => { clearTimeout(to); tcpMs.push(performance.now() - t0); s.destroy(); res(); }); + s.once("error", (e) => { clearTimeout(to); rej(e); }); + }); + } catch (err) { tcpMs.push(-1); console.warn("第" + (i+1) + "次 TCP 失败: " + err.message); } + if (!isWs) continue; + const t1 = performance.now(); + try { + const ws = new WebSocket(url); + await new Promise((res, rej) => { ws.onopen = res; ws.onerror = () => rej(new Error("WS open 失败")); }); + openMs.push(performance.now() - t1); + const t2 = performance.now(); + ws.send(JSON.stringify({ type: "ping", t: Date.now() })); + await new Promise((res) => { + const to = setTimeout(() => { rttMs.push(-1); res(); }, 3000); + ws.onmessage = () => { clearTimeout(to); rttMs.push(performance.now() - t2); res(); }; + }); + ws.close(); + } catch (err) { openMs.push(-1); console.warn("第" + (i+1) + "次 WS 失败: " + err.message); } +} + +const stat = (a) => { + const v = a.filter((x) => x >= 0); + if (!v.length) return "全部失败"; + const avg = v.reduce((s, x) => s + x, 0) / v.length; + return "avg=" + avg.toFixed(1) + "ms min=" + Math.min(...v).toFixed(1) + "ms max=" + Math.max(...v).toFixed(1) + "ms"; +}; +console.log("目标: " + url + " (" + host + ":" + port + ") x" + times); +console.log("TCP连接 : " + stat(tcpMs) + " <- >20ms 查防火墙/IPv6/代理"); +if (isWs) { + console.log("WS握手 : " + stat(openMs) + " <- >50ms 查服务端WS框架/鉴权"); + console.log("消息RTT : " + stat(rttMs) + " <- >50ms 查串行往返/服务端处理"); +} \ No newline at end of file