# GPT-Plus Web 管理面板方案 ## 一、技术选型 | 层 | 选型 | 理由 | |---|------|------| | Backend | Go + Gin | 与现有 Go 代码无缝集成,Gin 轻量高性能 | | Frontend | Vue 3 + Vite + Element Plus | 中后台组件库成熟,中文友好 | | Database | SQLite + GORM | 零部署依赖,单文件数据库,适合单机场景 | | Auth | JWT + bcrypt | .env 设置管理员密码,JWT 签发 token | | 部署 | 单二进制 + embed 前端 | `go:embed` 嵌入前端 dist,一个二进制即运行 | ## 二、目录结构 ``` gpt-plus/ ├── cmd/ │ └── gptplus/ │ └── main.go # 入口:启动 web server ├── config/ │ └── config.go # 保留,Web 化后从 DB 读取 ├── internal/ │ ├── db/ │ │ ├── db.go # GORM 初始化 + 自动迁移 │ │ ├── models.go # 数据模型 │ │ └── query.go # 查询/聚合方法 │ ├── handler/ │ │ ├── auth.go # POST /api/login │ │ ├── config.go # GET/PUT /api/config │ │ ├── task.go # 任务 CRUD + 控制 │ │ ├── account.go # 账号列表/搜索/导出/检查 │ │ └── middleware.go # JWT 鉴权中间件 │ ├── task/ │ │ ├── manager.go # 任务管理器(创建/运行/停止) │ │ ├── runner.go # 单任务执行器(调用现有 runOnce) │ │ └── types.go # 任务状态/事件类型 │ └── service/ │ ├── account_svc.go # 账号业务逻辑 │ └── export_svc.go # 导出(单文件/ZIP 打包) ├── pkg/ # 现有核心逻辑不动 │ ├── auth/ │ ├── chatgpt/ │ ├── stripe/ │ ├── httpclient/ │ ├── provider/ │ │ ├── card/ │ │ ├── email/ │ │ └── proxy/ │ ├── captcha/ │ └── storage/ # 保留文件导出,DB 为主存储 ├── web/ │ └── frontend/ # Vue 3 SPA │ ├── src/ │ │ ├── views/ │ │ │ ├── Login.vue │ │ │ ├── Dashboard.vue # 概览(统计卡片) │ │ │ ├── Config.vue # 配置管理 │ │ │ ├── Tasks.vue # 任务列表 │ │ │ ├── TaskDetail.vue # 任务详情 + 实时进度 │ │ │ ├── Accounts.vue # 账号面板 │ │ │ └── AccountDetail.vue # 母号详情(含小号列表) │ │ ├── api/ # axios 封装 │ │ ├── router/ │ │ ├── stores/ # Pinia 状态管理 │ │ └── components/ │ ├── package.json │ └── vite.config.ts ├── .env # ADMIN_PASSWORD=xxx, JWT_SECRET=xxx ├── gptplus.db # SQLite 数据库文件 (运行时生成) └── go.mod ``` ## 三、数据模型 ### 3.1 系统配置表 `configs` ```go type SystemConfig struct { ID uint `gorm:"primaryKey"` Key string `gorm:"uniqueIndex;size:100"` // e.g. "proxy.url", "email.provider" Value string `gorm:"type:text"` Group string `gorm:"size:50;index"` // "proxy", "email", "card", "stripe", etc. Label string `gorm:"size:100"` // 中文显示名 Type string `gorm:"size:20"` // "string", "int", "bool", "password", "textarea" UpdatedAt time.Time } ``` 配置组 (Group): - `proxy` — 代理模式(直连/固定代理/B2Proxy动态代理)、B2Proxy API 地址、代理区域(国家代码,如 US/JP/KR)、协议(socks5/http)、会话时长等 - `email` — 邮箱网关域名、API Key - `card` — 卡片默认绑定上限(max_binds)、默认地址信息、开卡 API 密钥 - `stripe` — build_hash, tag_version, fingerprint_dir - `captcha` — 验证码提供商、API Key - `account` — 密码长度、locale - `team` — 开关、workspace 前缀、座位数、优惠券、邀请数 > 邮箱不再作为配置项,去掉 MailGateway,全量使用 Outlook。邮箱通过 mailboxes 表管理。 ### 3.2 邮箱表 `mailboxes` ```go type Mailbox struct { ID uint `gorm:"primaryKey"` Email string `gorm:"size:200;uniqueIndex"` Password string `gorm:"size:200"` ClientID string `gorm:"size:200"` // Outlook OAuth client_id RefreshToken string `gorm:"type:text"` // Outlook OAuth refresh_token // 使用状态 Status string `gorm:"size:20;index;default:available"` // available / in_use / used / used_member / failed / disabled UsedByAccountID *uint `gorm:"index"` UsedForRole string `gorm:"size:20"` // "owner" / "member" UsedAt *time.Time TaskID string `gorm:"size:36;index"` CreatedAt time.Time `gorm:"index"` UpdatedAt time.Time } ``` **邮箱生命周期**: ``` available → in_use → used (主号) / used_member (小号) / failed (可回收) → disabled (管理员禁用) failed → available (回收重用) ``` **适配**: 新增 `DBEmailProvider`,不再用内存索引,完全 DB 状态驱动。 **API**: 导入/回收/禁用/统计,邮箱面板可视化管理。 ### 3.3 注册任务表 `tasks` ```go type Task struct { ID string `gorm:"primaryKey;size:36"` // UUID Type string `gorm:"size:20;index"` // "plus", "team", "both" TotalCount int // 本轮要注册多少个 DoneCount int // 已完成(成功+失败) SuccessCount int // 成功数 FailCount int // 失败数 Status string `gorm:"size:20;index"` // pending/running/stopping/stopped/completed Config string `gorm:"type:text"` // 快照:任务创建时的配置 JSON CreatedAt time.Time `gorm:"index"` StartedAt *time.Time StoppedAt *time.Time } ``` 状态流转: ``` pending → running → completed → stopping → stopped (graceful stop) ``` ### 3.3 任务日志表 `task_logs` ```go type TaskLog struct { ID uint `gorm:"primaryKey"` TaskID string `gorm:"size:36;index"` Index int // 第几个账号 (1-based) Email string `gorm:"size:200"` Status string `gorm:"size:20"` // success/failed/skipped Plan string `gorm:"size:20"` // plus/team/free Error string `gorm:"type:text"` Duration int // 耗时(秒) CreatedAt time.Time } ``` ### 3.4 卡密表 `card_codes` + 卡片表 `cards` 两层管理:卡密(兑换码)→ 卡片(已开卡)。同一时间只有一张卡 active。 **CardCode**: unused → redeeming → redeemed(关联card) / failed **Card**: available → active(全局唯一) → exhausted / rejected / disabled 自动切换:active失效 → 找available → 无则兑换卡密 → 无则报错 API: `/api/cards` + `/api/card-codes` (CRUD + 激活 + 兑换 + 统计) UI: 两个Tab(卡片+卡密),顶部高亮当前激活卡 详见 Obsidian 完整文档。 ### 3.5 账号表 `accounts` ```go type Account struct { ID uint `gorm:"primaryKey"` TaskID string `gorm:"size:36;index"` // 来源任务 Email string `gorm:"size:200;uniqueIndex"` Password string `gorm:"size:100"` Plan string `gorm:"size:20;index"` // "plus", "team_owner", "team_member" ParentID *uint `gorm:"index"` // team_member → 指向 team_owner Parent *Account `gorm:"foreignKey:ParentID"` SubAccounts []Account `gorm:"foreignKey:ParentID"` // team_owner 的小号列表 // Auth tokens AccessToken string `gorm:"type:text"` RefreshToken string `gorm:"type:text"` IDToken string `gorm:"type:text"` AccountID string `gorm:"size:100"` DeviceID string `gorm:"size:100"` UserID string `gorm:"size:100"` // Team specific TeamWorkspaceID string `gorm:"size:100"` WorkspaceToken string `gorm:"type:text"` // team workspace-scoped token // Status Status string `gorm:"size:20;index;default:active"` // active/banned/unknown StatusCheckedAt *time.Time Note string `gorm:"type:text"` // 用户备注 CreatedAt time.Time `gorm:"index"` UpdatedAt time.Time } ``` ## 四、API 设计 ### 4.1 认证 ``` POST /api/login { password: "xxx" } → { token: "jwt..." } ``` 所有 `/api/*` 路由需 `Authorization: Bearer ` 头(中间件校验)。 ### 4.2 配置管理 ``` GET /api/config → { groups: { proxy: [...], email: [...], ... } } PUT /api/config { items: [ {key, value}, ... ] } GET /api/config/test-connection → 测试代理/邮箱/卡片连通性 ``` ### 4.3 任务管理 ``` POST /api/tasks { type: "plus", count: 10 } → { id: "uuid", status: "pending" } GET /api/tasks → [ { id, type, total, done, success, fail, status, created_at } ] GET /api/tasks/:id → { ...task, logs: [...] } POST /api/tasks/:id/start → 启动任务 POST /api/tasks/:id/stop → 优雅停止(完成当前轮后停止) DELETE /api/tasks/:id → 删除已完成/已停止的任务 ``` **实时进度**: 前端轮询 `GET /api/tasks/:id`(2s 间隔),返回最新 done/success/fail 计数 + 最新日志。 ### 4.4 账号管理 ``` GET /api/accounts ?plan=plus|team_owner&search=email&page=1&size=20 GET /api/accounts/:id → 账号详情(含小号列表) POST /api/accounts/check { ids: [1,2,3] } → 批量检查状态 PUT /api/accounts/:id/note { note: "xxx" } → 更新备注 POST /api/accounts/export { ids: [1,2,3], note: "导出备注" } → 文件下载 ``` **账号列表规则**: - 默认只显示 `plan = plus` 和 `plan = team_owner` - `team_member` 不在列表中显示,归属于其 `parent` (team_owner) - 搜索可以搜到 team_member(显示其所属母号) **导出规则**: - 导出 Plus 账号 → 单个 `{email}.auth.json` - 导出 Team 母号 → 母号 `.auth.json` + 所有小号 `.auth.json` - 导出文件数 > 1 → 自动打包为 `.zip` - 导出文件名: `export_{备注}_{timestamp}.zip` 或 `{email}.auth.json` ### 4.5 Dashboard ``` GET /api/dashboard → { total_accounts, plus_count, team_count, active_tasks, recent_tasks: [...], today_registrations, success_rate } ``` ## 五、任务执行引擎 ### 5.1 TaskManager ```go type TaskManager struct { mu sync.Mutex running map[string]*TaskRunner // taskID → runner db *gorm.DB } func (m *TaskManager) Start(taskID string) error func (m *TaskManager) Stop(taskID string) error // 设置 stopping flag func (m *TaskManager) GetStatus(taskID string) *TaskStatus ``` ### 5.2 TaskRunner ```go type TaskRunner struct { task *db.Task config *config.Config // 从任务快照还原 cancel context.CancelFunc stopping atomic.Bool // graceful stop signal } func (r *TaskRunner) Run(ctx context.Context) { for i := 0; i < r.task.TotalCount; i++ { if r.stopping.Load() { break // 当前轮完成后停止 } result := runOnce(ctx, i, ...) // 调用现有核心逻辑 r.saveResult(result) // 写入 DB r.updateProgress() // 更新任务计数 } } ``` ### 5.3 停止逻辑 用户点击"停止" → `POST /api/tasks/:id/stop`: 1. 设置 `task.Status = "stopping"` (DB) 2. 设置 `runner.stopping = true` (内存) 3. 当前正在执行的 `runOnce()` 会跑完 4. 下一轮循环检测到 `stopping` → break 5. 更新 `task.Status = "stopped"` (DB) ## 六、前端页面 ### 6.0 全局布局架构 ``` ┌─────────────────────────────────────────────────────┐ │ Navbar [Logo GPT-Plus] [配置] [任务] [账号] [■ 任务进度浮窗] [退出] │ ├─────────────────────────────────────────────────────┤ │ │ │ │ │ (当前页面内容) │ │ │ └─────────────────────────────────────────────────────┘ ``` **核心设计: 全局任务进度浮窗 (TaskProgressWidget)** 右上角常驻一个**任务进度迷你组件**,行为规则: - 有 running/stopping 状态的任务时**始终显示**,跨路由不消失 - 登录页**不显示**(layout 不包含 navbar) - 无活跃任务时**隐藏**或显示为灰色空状态 - 点击浮窗可**展开**查看详情或**跳转**到任务详情页 **浮窗 UI**: ``` ┌──────────────────────────┐ │ ● 任务进行中 3/10 │ ← 收起态:一行摘要 + 进度条 │ ████████░░░░ 30% │ │ [展开 ▼] │ ├──────────────────────────┤ ← 展开态:显示详细信息 │ 类型: Plus │ │ 成功: 2 失败: 1 │ │ 当前: user_xxx@outlook.. │ │ [查看详情] [停止任务] │ └──────────────────────────┘ ``` **实现方式**: - `Pinia` 全局 store: `useTaskStore()` — 管理活跃任务状态 - store 内置定时轮询(3s 间隔),**独立于路由组件生命周期** - 登录成功后启动轮询,退出登录时停止 - 组件: `TaskProgressWidget.vue` 挂在 `AppLayout.vue` 的 navbar 中 ``` src/ ├── layouts/ │ ├── AppLayout.vue # 已登录布局:navbar + sidebar + router-view │ └── BlankLayout.vue # 登录页布局:无 navbar ├── components/ │ └── TaskProgressWidget.vue # 全局任务进度浮窗 ├── stores/ │ └── taskStore.ts # 全局任务状态 + 轮询逻辑 ``` **路由结构**: ```typescript const routes = [ { path: '/login', component: BlankLayout, // 无 navbar,无浮窗 children: [{ path: '', component: Login }] }, { path: '/', component: AppLayout, // 有 navbar + TaskProgressWidget meta: { requiresAuth: true }, children: [ { path: '', component: Dashboard }, { path: 'config', component: Config }, { path: 'tasks', component: Tasks }, { path: 'tasks/:id', component: TaskDetail }, { path: 'accounts', component: Accounts }, { path: 'accounts/:id', component: AccountDetail }, ] } ] ``` ### 6.1 登录页 (BlankLayout) - 单密码输入框 + 登录按钮 - JWT 存 localStorage - 登录成功 → 跳转 Dashboard + 启动 taskStore 轮询 ### 6.2 Dashboard (首页) - 统计卡片: 总账号数、Plus 数、Team 数、今日注册数 - 活跃任务列表(简要) - 最近注册记录 ### 6.3 配置页 - 分组表单 (Tabs: 代理 | 邮箱 | 卡片 | Stripe | 验证码 | 账号 | Team) - 每组内用 Element Plus 表单组件 - 密码类型用 `el-input type="password"` - 底部"保存"按钮 + 连通性测试按钮 ### 6.4 任务页 - 顶部: "新建任务" 按钮 → 弹窗 (选类型 + 输入数量) - 任务列表表格: ID | 类型 | 进度 (3/10) | 成功/失败 | 状态 | 操作 - 状态标签: pending=灰, running=蓝(动画), stopping=黄, stopped=橙, completed=绿 - 操作: 启动 | 停止 | 查看详情 | 删除 - 点击任务 → 任务详情页 ### 6.5 任务详情页 - 进度条 + 计数器 (成功/失败/总数) - 实时日志表格: 序号 | 邮箱 | 状态 | 类型 | 耗时 | 错误信息 - 2 秒轮询刷新(与全局 store 复用数据) ### 6.6 账号面板 - 筛选栏: 类型下拉(全部/Plus/Team母号) | 搜索框(邮箱) | 状态筛选(全部/正常/封禁) - 表格: 勾选框 | 邮箱 | 类型 | 状态 | 小号数 | 备注 | 创建时间 | 操作 - 操作: 查看详情 | 编辑备注 | 检查状态 | 导出 - 批量操作栏: 勾选后 → 批量导出 | 批量检查状态 - 导出弹窗: 输入备注 → 确认导出 → 浏览器下载 ### 6.7 账号详情页 (Team 母号) - 母号信息卡片 - 小号列表表格 (最多 4 个): 邮箱 | 状态 | Token 概要 ## 七、.env 配置 ```env # 管理员密码 (必填) ADMIN_PASSWORD=your_secure_password # JWT 密钥 (可选,默认随机生成) JWT_SECRET=random_secret_key # 服务端口 (默认 8080) PORT=8080 # 数据库路径 (默认 ./gptplus.db) DB_PATH=./gptplus.db ``` ## 八、构建 & 部署 ### 开发模式 ```bash # 前端 cd web/frontend && npm run dev # 后端 (代理前端) go run ./cmd/gptplus/ --dev ``` ### 生产构建 ```bash # 1. 构建前端 cd web/frontend && npm run build # 2. 构建后端 (embed 前端 dist) CGO_ENABLED=1 go build -o gptplus ./cmd/gptplus/ # 注: SQLite 需要 CGO # 3. 部署 scp gptplus server:/root/gptplus/ scp .env server:/root/gptplus/ ssh server 'cd /root/gptplus && ./gptplus' ``` ### 单二进制方案 ```go //go:embed web/frontend/dist var frontendFS embed.FS // Gin 中挂载 router.StaticFS("/", http.FS(frontendFS)) ``` ## 九、迁移计划 ### Phase 1: 基础框架 (Day 1-2) - [ ] 初始化 SQLite + GORM + 数据模型 - [ ] Gin 路由 + JWT 中间件 - [ ] 配置 CRUD API - [ ] Vue 3 项目初始化 + 登录页 + 配置页 ### Phase 2: 任务引擎 (Day 2-3) - [ ] TaskManager + TaskRunner - [ ] 重构 runOnce() 解耦:接受 config 参数而非全局状态 - [ ] 任务 API + 前端任务页 ### Phase 3: 账号面板 (Day 3-4) - [ ] Account 存储从文件 → DB - [ ] 搜索/筛选/分页 API - [ ] 导出(JSON/ZIP) - [ ] 状态检查 (调用 ChatGPT session API) - [ ] 前端账号面板 ### Phase 4: 打磨 (Day 4-5) - [ ] Dashboard 统计 - [ ] embed 前端 + 单二进制构建 - [ ] 错误处理 + 日志 - [ ] 部署到服务器 ## 十、改动范围 | 动作 | 路径 | 说明 | |------|------|------| | 新增 | `internal/db/` | GORM 模型 + 查询 | | 新增 | `internal/handler/` | Gin HTTP handlers | | 新增 | `internal/task/` | 任务管理引擎 | | 新增 | `internal/service/` | 导出/检查业务逻辑 | | 新增 | `web/frontend/` | Vue 3 SPA | | 重写 | `cmd/gptplus/main.go` | CLI → Web Server | | 修改 | `config/config.go` | 支持从 DB 加载 | | 修改 | `pkg/storage/json.go` | 同时写 DB + 文件 | | 新增 | `.env` | 管理员密码 + JWT 密钥 | | 保留 | `pkg/*` 全部 | 核心业务逻辑不动 |