diff --git a/.gitignore b/.gitignore index c8b3be9..8a0cbdf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,11 @@ obj/ *.blockmap *.zip builder-debug.yml -client/dist/ # agent-core 编译产物(win/darwin) -client/desktop/icons/ # 生成图标(gen-icon.js/gen-ico.js 可重生成) + +# 编译产物目录(无行内注释) +client/dist/ +client/desktop/icons/ +client/macos/release/ # 本地运行数据与密钥(绝不入库) server/data/ @@ -34,3 +37,6 @@ client/core/data/ client/desktop/release/ client/desktop/win-unpacked/ *.log + +# 第三方参考仓库(非产品代码;含嵌套 git/密钥/236MB 二进制,不入库) +references/ diff --git a/Makefile b/Makefile index 5ccc20b..75f71c1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: run build test tidy infra-up infra-down health +.PHONY: run build web-build web-test web-smoke test tidy infra-up infra-down health mac-core mac-client run: ## 本地运行服务 cd server && go run . @@ -6,8 +6,19 @@ run: ## 本地运行服务 build: ## 编译二进制 cd server && go build -o ../dist/everypublish-server . +web-build: ## 构建网页端 + cd apps/web && npm run build + +web-test: ## 网页端类型检查 + cd apps/web && npx tsc --noEmit + +web-smoke: ## 网页端 API/Browser WS smoke(需先启动 Web-only server) + cd apps/web && npm test + test: ## 单测 cd server && go test ./... + cd server && go vet ./... + cd apps/web && npx tsc --noEmit tidy: ## 整理依赖 cd server && go mod tidy @@ -20,3 +31,10 @@ infra-down: ## 停止 MySQL/Redis health: ## 健康检查 curl -s http://127.0.0.1:8090/health + +mac-core: ## 构建 macOS arm64 agent-core + @echo "macOS 客户端已暂停;该目标仅保留历史兼容,不属于 V1 验收" + cd client/core && GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags '-s -w' -o ../dist/agent-core . + +mac-client: ## 构建 macOS arm64 SwiftUI 原生 DMG + @echo "macOS 客户端已暂停;该目标不属于 V1 验收" diff --git a/README.md b/README.md index 0218f4e..aaa9999 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,72 @@ -# EveryPublish 项目总文档(唯一入口) +# EveryPublish -**一句话定位**:企业多平台官方账号的发布中台——网页控制台协作、审核、排期,客户端单点持号、自动发布。 +EveryPublish 是一个本机部署的多平台内容发布 Web 控制台:用户打开浏览器登录,在网页内完成账号、素材、任务、审核、挑战和结果管理。 -> 当前状态:开发进行中(阶段一网页端 D1)。进度跟踪见 docs/progress.md。 +> 当前交付边界:Web-only V1。macOS/Windows 客户端、Agent 配对和设备管理暂停,不进入本阶段验收。 -## 文档地图(全部文档一览) +## 当前架构 -| # | 文档 | 用途 | 主要读者 | 状态 | -|---|---|---|---|---| -| 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 | +```text +浏览器 React + TDesign + │ REST / Browser WebSocket + ▼ +Go server :8090 + ├─ MySQL / Redis + ├─ 本地素材 StorageDriver + ├─ WebExecutor(mock 联调) + └─ PlatformAdapter(真实平台逐个接入) +``` -## 阅读路径 +服务器运行在当前设备时,发布执行所需的浏览器 profile、cookie 和素材均留在该设备;不需要安装客户端或输入配对码。 -- **产品评审**:1 → 4 → 5 -- **开发开工**:2 → 3 → 5 → 7 → 6 -- **新人上手**:1 → 9 → 4 → 3 -- **向客户/上级汇报**:1(定位与范围)+ 5(排期) +## 本地启动 -## 关键决策速览 +```bash +make infra-up +make run +cd apps/web && npm run dev -- --port 3013 +``` -| 项 | 决策 | +本地联调默认使用 `EXECUTOR_MODE=mock`,不会访问真实平台。要联调已接入的 B 站 adapter,启动服务前设置 `EXECUTOR_MODE=web`;二维码扫码或 Cookie 导入成功后,任务才会走真实平台链路。没有人工扫码时,使用 adapter HTTP stub 和 mock 账号完成自动化验证,不把 mock URL 当成真实发布。 + +- Go API/Browser WS:`http://127.0.0.1:8090` +- Vite Web:`http://127.0.0.1:3013` +- 生产构建:`cd apps/web && npm run build`,设置 `STATIC_DIR=apps/web/dist` 后由 Go server 托管 + +## 网页功能 + +- 工作台:任务统计、执行器状态、挑战和最近任务。 +- 账号管理:平台账号、扫码/Cookie 登录、健康检查、重新登录。 +- 素材库:上传进度、SHA-256 去重、预览、分组和删除。 +- 发布管理:新建、草稿、审核、排期、任务详情、重试和取消。 +- 挑战/通知/审计:二维码、验证码、实时状态、历史追踪。 + +网页端不需要客户端安装、设备配对或 Agent WSS。客户端代码仅作为历史归档保留。 + +## 文档入口 + +| 文档 | 说明 | |---|---| -| 技术栈 | 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 仅存客户端本地 | -| 职责边界 | 网站=分配任务+收状态(零平台凭据);客户端=持号+登录鉴权+发布执行(凭据仅本机) | -| 红线 | 矩阵养号/搬运去重绕过/刷量/私信轰炸一律不做 | +| [docs/web-only-v1-plan.md](docs/web-only-v1-plan.md) | 当前 V1 范围、页面、执行器和验收 | +| [docs/web-only-logic-diagrams.md](docs/web-only-logic-diagrams.md) | Web-only 系统、登录、上传、执行、扫码逻辑图 | +| [docs/reference-repositories-analysis.md](docs/reference-repositories-analysis.md) | 三个 MIT 参考仓库的拉取记录和借鉴边界 | +| [docs/system-design.md](docs/system-design.md) | 当前系统设计 | +| [docs/delivery-plan.md](docs/delivery-plan.md) | 当前交付排期 | +| [docs/page-wireframes.md](docs/page-wireframes.md) | 网页 ASCII 线框 | +| [docs/api.md](docs/api.md) | REST API | +| [docs/progress.md](docs/progress.md) | 当前进度和剩余风险 | -## 目录结构 +## 参考项目 -- 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) +- `references/social-auto-upload`(MIT,dreammis,CLI/uploader/QR 参考) +- `references/social-auto-upload-web-ui`(MIT,DevilJie,Web/SSE/任务队列参考) +- `references/MediaPublishPlatform`(MIT,funfan0517,批量上传/定时/发布记录参考) -## 开发进行中(阶段一 D1-D7) +参考代码只读保存,新增实现必须保留 MIT 版权说明;当前工程不混入 Vue/Flask/SQLite 三套运行时。 -- 排期: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) +## 开发原则 + +- mock executor 只能证明业务状态机,不冒充真实平台成功。 +- 真实扫码无法执行时,状态必须停在“等待扫码”,不能自动标记 active。 +- 所有任务、挑战和素材错误必须能在页面和日志中定位。 +- 不做刷量、绕过平台风控、矩阵养号、私信轰炸等功能。 diff --git a/apps/web/package.json b/apps/web/package.json index 8126761..89f825f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,8 +11,8 @@ "build:site": "vite build --mode site", "site:preview": "npm run build && cp -r dist _site", "preview": "vite preview --mode test", - "test": "echo \"no test specified,work in process\"", - "test:coverage": "echo \"no test specified,work in process\"", + "test": "node ../../tools/web-smoke.mjs", + "test:coverage": "node ../../tools/web-smoke.mjs", "lint": "eslint ./src --ext ts,tsx", "lint:fix": "eslint ./src --ext ts,tsx --fix", "prepare": "husky install" diff --git a/apps/web/src/layouts/components/Header/HeaderIcon.tsx b/apps/web/src/layouts/components/Header/HeaderIcon.tsx index 84b49c3..9b95f00 100644 --- a/apps/web/src/layouts/components/Header/HeaderIcon.tsx +++ b/apps/web/src/layouts/components/Header/HeaderIcon.tsx @@ -1,9 +1,11 @@ -import React, { memo } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Button, Badge, Dropdown, Space } from 'tdesign-react'; +import { Button, Badge, Dropdown, Space, Tag } from 'tdesign-react'; import { Icon, MailIcon, PoweroffIcon, UserCircleIcon } from 'tdesign-icons-react'; import { useAppDispatch, useAppSelector } from 'modules/store'; import { logout, selectUser } from 'modules/user'; +import { disconnectWS, onWS } from 'utils/ws'; +import request from 'utils/request'; import Style from './HeaderIcon.module.less'; const { DropdownMenu, DropdownItem } = Dropdown; @@ -13,6 +15,26 @@ export default memo(() => { const navigate = useNavigate(); const userState = useAppSelector(selectUser); const nickname = userState.userInfo.nickname || userState.userInfo.email || '未登录'; + const [wsOnline, setWsOnline] = useState(false); + const [unread, setUnread] = useState(0); + + useEffect(() => { + const offConnection = onWS('connection.status', (payload) => setWsOnline(Boolean(payload?.online))); + const loadNotifications = async () => { + try { + const res = await request.get('/notifications'); + setUnread(Number(res.data.unread || 0)); + } catch (e) { + setUnread(0); + } + }; + loadNotifications(); + const offNotification = onWS('notification.new', loadNotifications); + return () => { + offConnection(); + offNotification(); + }; + }, []); const clickHandler = (data: any) => { if (data.value === 1) { @@ -22,13 +44,17 @@ export default memo(() => { const handleLogout = async () => { await dispatch(logout()); + disconnectWS(); navigate('/login/index'); }; return ( - - )} - {row.status === 'unbound' && ( + {canOperate && (row.status === 'unbound' || row.status === 'expired') && ( + + )} + {canOperate && row.status === 'active' && ( + + )} + {canAdmin && row.status === 'unbound' && ( remove(row)}> )} + {canAdmin && (row.status === 'active' || row.status === 'binding' || row.status === 'expired') && ( + unbind(row)}> + + + )} ), }, ]; - const deviceOptions = [ - { label: '暂不指定(稍后选择)', value: 0 }, - ...devices.map((d) => ({ label: d.name + '(在线)', value: d.id })), - ]; - return ( - + {canOperate && } @@ -206,7 +264,7 @@ export default function AccountList() { } > -
凭据只保存在客户端本机保险库,服务器零凭据;账号名称/头像由客户端绑定后自动获取。
+
当前为 Web-only 本地模式:登录挑战和平台浏览器 profile 在运行服务器的设备上完成,不需要客户端配对。
- @@ -237,17 +292,50 @@ export default function AccountList() { setBindAccount(null)} + onClose={() => { setBindAccount(null); bindAccountRef.current = null; setBindChallenge(null); }} footer={null} > -
-
📱 {bindTip || '正在发起绑定...'}
-
请在客户端设备上完成扫码/验证,完成后本窗口自动关闭。
- +
+ {bindChallenge?.qrUrl && !bindChallenge.qrUrl.startsWith('mock://') && ( + + )} +
{bindTip || '正在发起登录...'}
+
+ 二维码由当前服务器生成并轮询状态,不需要客户端配对码。 +
+ {bindChallenge?.qrUrl?.startsWith('mock://') && ( + + )} +
+ +
+ { setCredentialsTarget(null); setCredentialsValue(''); }} + confirmBtn={} + > +
+ 仅支持已接入真实适配器的平台。凭据只写入当前服务器的加密目录,不会显示在列表、日志或响应中。 +
+