Initial sanitized code sync

This commit is contained in:
zqq61
2026-03-15 20:48:19 +08:00
commit 17ee51ba04
126 changed files with 22546 additions and 0 deletions

538
docs/web-panel-plan.md Normal file
View File

@@ -0,0 +1,538 @@
# 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 <jwt>` 头(中间件校验)。
### 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] [配置] [任务] [账号] [■ 任务进度浮窗] [退出] │
├─────────────────────────────────────────────────────┤
│ │
│ <router-view /> │
│ (当前页面内容) │
│ │
└─────────────────────────────────────────────────────┘
```
**核心设计: 全局任务进度浮窗 (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/*` 全部 | 核心业务逻辑不动 |