docs: 项目文档、设计图、工具、协议模块、部署说明

This commit is contained in:
Qiufeng
2026-08-20 20:38:21 +08:00
commit bf25d8beac
46 changed files with 2965 additions and 0 deletions
+36
View File
@@ -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
+22
View File
@@ -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
+59
View File
@@ -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)
@@ -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
@@ -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
@@ -0,0 +1,11 @@
flowchart LR
subgraph M["Mac 开发机 内网测试环境"]
WEB["网页端 dev"] --> S["服务器 API + WSS网关"]
end
subgraph W["Windows 客户机"]
APP["客户端App Agent<br/>WinUI壳+Go执行核心"]
end
W -->|"WSS ws://局域网IP:端口"| S
S --> DB[("MySQL + Redis")]
S --> FS[("素材 服务器本地盘")]
APP -->|"直链下载素材 带签名token"| FS
+8
View File
@@ -0,0 +1,8 @@
flowchart LR
ACC["账号台账"] --> REG{"账号归属地?"}
REG -->|国内平台| POOL1["国内住宅IP池<br/>芝麻/快代理/922S5等"]
REG -->|国外平台| POOL2["海外住宅IP池<br/>BrightData/IPRoyal等"]
POOL1 --> BIND["一账号绑定一IP<br/>长期固定不轮换"]
POOL2 --> BIND
BIND --> CTX["浏览器Context级注入<br/>非全局TUN 防串账号"]
CTX --> EXEC["执行发布"]
@@ -0,0 +1,17 @@
stateDiagram-v2
[*] --> 队列中
队列中 --> 执行中: Agent认领
执行中 --> 已发布: 平台回执成功
执行中 --> 网络重试: 网络/超时错误
网络重试 --> 执行中: 退避1m/5m/15m/1h
网络重试 --> 失败终态: 超上限N次
执行中 --> 挑战中: 需扫码/验证码
挑战中 --> 执行中: 用户完成挑战
挑战中 --> 挂起: 挑战超时
挂起 --> 执行中: 用户稍后处理
执行中 --> 账号冷却: 风控拦截
账号冷却 --> 执行中: 冷却结束+人工确认
账号冷却 --> 失败终态: 人工放弃
失败终态 --> 队列中: 人工一键重发
已发布 --> [*]
失败终态 --> [*]
@@ -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
@@ -0,0 +1,13 @@
flowchart TB
TR["平台触发二次验证"] --> DET{"Agent检测挑战类型"}
DET -->|二维码登录| QR["取码→推控制台+企微<br/>码过期自动刷新"]
DET -->|APP确认| CF["推送手机确认提醒<br/>Agent轮询登录态"]
DET -->|短信/邮箱| SMS["控制台弹输入框<br/>用户输入→回传回填"]
DET -->|滑块/点选| CAP["默认截图推人工<br/>低风控平台本地尝试"]
QR --> OK{"完成?"}
CF --> OK
SMS --> OK
CAP --> OK
OK -->|是| CONT["Agent继续发布流程"]
OK -->|否/超时| PEND["任务挂起→人工队列<br/>一键重发/放弃"]
PEND --> CONT
@@ -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: 状态变绿
+17
View File
@@ -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)
@@ -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
+25
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"args":["--no-sandbox","--disable-gpu"]}
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

+13
View File
@@ -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
+47
View File
@@ -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="你的邮箱";`)。
+35
View File
@@ -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:
+161
View File
@@ -0,0 +1,161 @@
# EveryPublish 服务端接口文档(v1)
> 基地址:`/api/v1` · 统一响应:`{"code":0,"message":"ok","data":{...}}`,code!=0 为业务错误。
> 认证:`Authorization: Bearer <accessToken>`(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=<JWT>`(只读订阅)
| 事件 | 说明 |
|---|---|
| `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 状态) |
+32
View File
@@ -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`,卸载后如需彻底清除请手动删除。
+51
View File
@@ -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 <目录>` 观察)。
+153
View File
@@ -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 兜底 |
+101
View File
@@ -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 仍为二期(接口已预留)。
+72
View File
@@ -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 <JWT>;401 自动 refresh 重试一次。
- WS:原生 WebSocket,连接 /ws?token=<JWT>;心跳由服务端协议统一(10s/25s)。
- 分页约定:{ list, total, page, pageSize };错误码约定:业务码 + message,前端 Message 提示。
+299
View File
@@ -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<br/>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池<br/>芝麻/快代理/922S5等"]
REG -->|国外平台| POOL2["海外住宅IP池<br/>BrightData/IPRoyal等"]
POOL1 --> BIND["一账号绑定一IP<br/>长期固定不轮换"]
POOL2 --> BIND
BIND --> CTX["浏览器Context级注入<br/>非全局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["取码→推控制台+企微<br/>码过期自动刷新"]
DET -->|APP确认| CF["推送手机确认提醒<br/>Agent轮询登录态"]
DET -->|短信/邮箱| SMS["控制台弹输入框<br/>用户输入→回传回填"]
DET -->|滑块/点选| CAP["默认截图推人工<br/>低风控平台本地尝试"]
QR --> OK{"完成?"}
CF --> OK
SMS --> OK
CAP --> OK
OK -->|是| CONT["Agent继续发布流程"]
OK -->|否/超时| PEND["任务挂起→人工队列<br/>一键重发/放弃"]
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 天。
+600
View File
@@ -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池<br/>芝麻/快代理/922S5等"]
REG -->|国外平台| POOL2["海外住宅IP池<br/>BrightData/IPRoyal等"]
POOL1 --> BIND["一账号绑定一IP<br/>长期固定不轮换"]
POOL2 --> BIND
BIND --> CTX["浏览器Context级注入<br/>(非全局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://<Mac局域网IP>:<端口>`(用 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["取码→推控制台+企微<br/>码过期自动刷新"]
DET -->|APP确认| CF["推送手机确认提醒<br/>Agent轮询登录态"]
DET -->|短信/邮箱| SMS["控制台弹输入框<br/>用户输入→回传回填<br/>错可重输2次"]
DET -->|滑块/点选| CAP["默认截图推人工<br/>低风控平台本地尝试"]
QR --> OK{"完成?"}
CF --> OK
SMS --> OK
CAP --> OK
OK -->|是| CONT["Agent继续发布流程"]
OK -->|否/超时| PEND["任务挂起→人工队列<br/>一键重发/放弃"]
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
+334
View File
@@ -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
验收: 代理测试连通性; 日志可导出。
+173
View File
@@ -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. 单人版是否一期提供(隐藏审核流)。
+37
View File
@@ -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。
+234
View File
@@ -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 即够;凭据/挑战类数据才端到端加密。
+47
View File
@@ -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 打包)
+60
View File
@@ -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)。**
+3
View File
@@ -0,0 +1,3 @@
module everypublish/shared
go 1.23
+138
View File
@@ -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"`
}
+56
View File
@@ -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 <ws://host:port | http://host:port> [次数]");
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 查串行往返/服务端处理");
}