feat: Implement batch team owner processing with dedicated upload, configuration, and monitoring pages and backend services.

This commit is contained in:
2026-01-30 18:59:03 +08:00
parent 165c6d69b9
commit 6f18740215
7 changed files with 348 additions and 46 deletions

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
@@ -17,15 +18,18 @@ import (
"codex-pool/internal/register"
)
// TeamOwner 团队母号信息
type TeamOwner struct {
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"token"`
AccountID string `json:"account_id"` // 已存储的 account_id如有则直接使用
}
// TeamProcessRequest 团队处理请求
type TeamProcessRequest struct {
// Owner 账号列表
Owners []struct {
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"token"`
AccountID string `json:"account_id"` // 已存储的 account_id如有则直接使用
} `json:"owners"`
Owners []TeamOwner `json:"owners"`
// 配置
MembersPerTeam int `json:"members_per_team"` // 每个 Team 的成员数
ConcurrentTeams int `json:"concurrent_teams"` // 并发 Team 数量
@@ -33,6 +37,7 @@ type TeamProcessRequest struct {
Headless bool `json:"headless"` // 是否无头模式
Proxy string `json:"proxy"` // 代理设置
IncludeOwner bool `json:"include_owner"` // 母号也入库到 S2A
ProcessCount int `json:"process_count"` // 处理数量0表示全部
}
// TeamProcessResult 团队处理结果
@@ -91,12 +96,7 @@ func HandleTeamProcess(w http.ResponseWriter, r *http.Request) {
}
// 转换为请求格式(包含已存储的 account_id
for _, o := range pendingOwners {
req.Owners = append(req.Owners, struct {
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"token"`
AccountID string `json:"account_id"`
}{
req.Owners = append(req.Owners, TeamOwner{
Email: o.Email,
Password: o.Password,
Token: o.Token,
@@ -106,6 +106,12 @@ func HandleTeamProcess(w http.ResponseWriter, r *http.Request) {
logger.Info(fmt.Sprintf("从数据库加载 %d 个待处理母号", len(req.Owners)), "", "team")
}
// 根据 ProcessCount 限制处理数量
if req.ProcessCount > 0 && req.ProcessCount < len(req.Owners) {
req.Owners = req.Owners[:req.ProcessCount]
logger.Info(fmt.Sprintf("限制处理数量: %d 个母号", req.ProcessCount), "", "team")
}
if req.MembersPerTeam <= 0 {
req.MembersPerTeam = 4
}
@@ -330,7 +336,28 @@ func processSingleTeam(idx int, req TeamProcessRequest) TeamProcessResult {
}
result.TeamID = teamID
// Step 2: 并发注册成员
// Step 2: 测试邀请功能(检测 Team 是否被封禁)
testEmail := mail.GenerateEmail()
if err := inviter.SendInvites([]string{testEmail}); err != nil {
// 邀请失败,可能是 Team 被封禁
errStr := err.Error()
if strings.Contains(errStr, "403") || strings.Contains(errStr, "forbidden") ||
strings.Contains(errStr, "banned") || strings.Contains(errStr, "suspended") ||
strings.Contains(errStr, "deactivated") {
// Team 被封禁,标记为 invalid
logger.Error(fmt.Sprintf("%s Team 被封禁,标记为无效: %v", logPrefix, err), owner.Email, "team")
if database.Instance != nil {
database.Instance.MarkOwnerAsInvalid(owner.Email)
}
result.Errors = append(result.Errors, "Team 被封禁")
result.DurationMs = time.Since(startTime).Milliseconds()
return result
}
// 其他邀请错误,继续尝试
logger.Warning(fmt.Sprintf("%s 首次邀请失败,继续尝试: %v", logPrefix, err), owner.Email, "team")
}
// Step 3: 并发注册成员
// 每个成员:邀请 → 注册失败重试1次
// Team 有4次额外补救机会
type MemberAccount struct {