feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.

This commit is contained in:
2026-01-30 07:40:35 +08:00
commit f4448bbef2
106 changed files with 19282 additions and 0 deletions

View File

@@ -0,0 +1,433 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"sync/atomic"
"time"
"codex-pool/internal/auth"
"codex-pool/internal/config"
"codex-pool/internal/invite"
"codex-pool/internal/mail"
"codex-pool/internal/register"
)
type Account struct {
Account string `json:"account"`
Password string `json:"password"`
Token string `json:"token"`
}
type MemberAccount struct {
Email string
Password string
Success bool
}
const (
MembersPerTeam = 4 // 每个 team 注册的成员数
NumTeams = 2 // 并发运行的 team 数量
)
// ANSI 颜色码
const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorMagenta = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
ColorBold = "\033[1m"
)
// Team 颜色
var teamColors = []string{
ColorCyan, // Team 1
ColorMagenta, // Team 2
ColorYellow, // Team 3
ColorBlue, // Team 4
}
// TeamLogger 带颜色的Team日志
type TeamLogger struct {
prefix string
color string
mu sync.Mutex
}
func NewTeamLogger(teamIdx int) *TeamLogger {
color := teamColors[teamIdx%len(teamColors)]
return &TeamLogger{
prefix: fmt.Sprintf("[Team %d]", teamIdx+1),
color: color,
}
}
func (l *TeamLogger) Log(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s%s%s %s\n", l.color, l.prefix, ColorReset, msg)
}
func (l *TeamLogger) Success(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s%s%s %s✓%s %s\n", l.color, l.prefix, ColorReset, ColorGreen, ColorReset, msg)
}
func (l *TeamLogger) Error(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s%s%s %s✗%s %s\n", l.color, l.prefix, ColorReset, ColorRed, ColorReset, msg)
}
func (l *TeamLogger) Info(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s%s%s %s→%s %s\n", l.color, l.prefix, ColorReset, ColorYellow, ColorReset, msg)
}
// Highlight 整行绿色高亮(用于重要成功信息)
func (l *TeamLogger) Highlight(format string, args ...interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
fmt.Printf("%s%s %s✓ %s%s\n", ColorGreen, l.prefix, ColorBold, msg, ColorReset)
}
func main() {
fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset)
fmt.Printf("%s Multi-Team Concurrent Test (Chromedp)%s\n", ColorBold, ColorReset)
fmt.Printf(" - %d Teams running concurrently\n", NumTeams)
fmt.Printf(" - %d Members per team\n", MembersPerTeam)
fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset)
fmt.Println()
// 加载配置
configPath := config.FindPath()
cfg, err := config.Load(configPath)
if err != nil {
fmt.Printf("%s[Error]%s Failed to load config: %v\n", ColorRed, ColorReset, err)
os.Exit(1)
}
// 初始化邮箱服务
if len(cfg.MailServices) > 0 {
mail.Init(cfg.MailServices)
}
// 加载账号
accountsFile := "accounts-3-20260130-052841.json"
data, err := os.ReadFile(accountsFile)
if err != nil {
fmt.Printf("%s[Error]%s Failed to read accounts file: %v\n", ColorRed, ColorReset, err)
os.Exit(1)
}
var accounts []Account
if err := json.Unmarshal(data, &accounts); err != nil {
fmt.Printf("%s[Error]%s Failed to parse accounts file: %v\n", ColorRed, ColorReset, err)
os.Exit(1)
}
if len(accounts) < NumTeams {
fmt.Printf("%s[Error]%s Need at least %d owner accounts, got %d\n", ColorRed, ColorReset, NumTeams, len(accounts))
os.Exit(1)
}
proxy := cfg.DefaultProxy
if proxy == "" {
proxy = "http://127.0.0.1:7890"
}
fmt.Printf("[Proxy] %s\n", proxy)
fmt.Println()
// 显示 Owner 列表
fmt.Printf("%s========================================%s\n", ColorBold, ColorReset)
fmt.Printf("%s[Owners]%s\n", ColorBold, ColorReset)
fmt.Printf("%s========================================%s\n", ColorBold, ColorReset)
for i := 0; i < NumTeams; i++ {
color := teamColors[i%len(teamColors)]
fmt.Printf(" %sTeam %d:%s %s\n", color, i+1, ColorReset, accounts[i].Account)
}
fmt.Println()
// 并发运行多个 Team
var wg sync.WaitGroup
var totalRegistered int32
var totalS2A int32
startTime := time.Now()
for teamIdx := 0; teamIdx < NumTeams; teamIdx++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
registered, s2a := runTeam(idx, accounts[idx], cfg, proxy)
atomic.AddInt32(&totalRegistered, int32(registered))
atomic.AddInt32(&totalS2A, int32(s2a))
}(teamIdx)
}
wg.Wait()
totalDuration := time.Since(startTime)
// 总结
fmt.Println()
fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset)
fmt.Printf("%s All Teams Complete%s\n", ColorBold, ColorReset)
fmt.Printf("%s=================================================================%s\n", ColorBold, ColorReset)
fmt.Printf(" Total Registered: %s%d/%d%s\n", ColorGreen, totalRegistered, NumTeams*MembersPerTeam, ColorReset)
fmt.Printf(" Total Added to S2A: %s%d%s\n", ColorGreen, totalS2A, ColorReset)
fmt.Printf(" Total Duration: %v\n", totalDuration)
fmt.Printf("%s=================================================================%s\n", ColorBold, ColorReset)
}
// runTeam 运行单个 Team 的流程
func runTeam(teamIdx int, owner Account, cfg *config.Config, proxy string) (registered, s2a int) {
log := NewTeamLogger(teamIdx)
log.Log("Starting with owner: %s", owner.Account)
// Step 1: 获取 Team ID
log.Info("Fetching Team ID...")
inviter := invite.NewWithProxy(owner.Token, proxy)
teamID, err := inviter.GetAccountID()
if err != nil {
log.Error("Failed to get Team ID: %v", err)
return 0, 0
}
log.Success("Team ID: %s", teamID)
// Step 2: 生成成员邮箱
log.Info("Generating %d member emails...", MembersPerTeam)
children := make([]MemberAccount, MembersPerTeam)
for i := 0; i < MembersPerTeam; i++ {
children[i].Email = mail.GenerateEmail()
children[i].Password = register.GeneratePassword()
log.Log("[Member %d] Email: %s", i+1, children[i].Email)
}
// 批量发送邀请
log.Info("Sending invites...")
inviteEmails := make([]string, MembersPerTeam)
for i, c := range children {
inviteEmails[i] = c.Email
}
if err := inviter.SendInvites(inviteEmails); err != nil {
log.Error("Failed to send invites: %v", err)
return 0, 0
}
log.Success("Sent %d invite(s)", len(inviteEmails))
// Step 3: 并发注册成员
log.Info("Starting member registration...")
var memberWg sync.WaitGroup
var successCount int32
memberMutex := sync.Mutex{}
for i := range children {
memberWg.Add(1)
go func(memberIdx int) {
defer memberWg.Done()
memberMutex.Lock()
email := children[memberIdx].Email
password := children[memberIdx].Password
memberMutex.Unlock()
name := register.GenerateName()
birthdate := register.GenerateBirthdate()
// 最多重试3次
for attempt := 0; attempt < 3; attempt++ {
if attempt > 0 {
email = mail.GenerateEmail()
password = register.GeneratePassword()
log.Log("[Member %d] Retry %d - New email: %s", memberIdx+1, attempt, email)
// 发送新邀请
if err := inviter.SendInvites([]string{email}); err != nil {
log.Error("[Member %d] Failed to send retry invite: %v", memberIdx+1, err)
continue
}
log.Success("[Member %d] Sent retry invite", memberIdx+1)
}
// 详细注册流程
if err := registerMemberDetailed(log, memberIdx+1, email, password, name, birthdate, proxy); err != nil {
if strings.Contains(err.Error(), "验证码") {
log.Error("[Member %d] OTP timeout, will retry...", memberIdx+1)
continue
}
log.Error("[Member %d] Registration failed: %v", memberIdx+1, err)
return
}
// 成功
memberMutex.Lock()
children[memberIdx].Email = email
children[memberIdx].Password = password
children[memberIdx].Success = true
memberMutex.Unlock()
atomic.AddInt32(&successCount, 1)
log.Success("[Member %d] Registration complete!", memberIdx+1)
return
}
log.Error("[Member %d] Failed after 3 retries", memberIdx+1)
}(i)
}
memberWg.Wait()
registered = int(successCount)
log.Success("Registration phase complete: %d/%d", registered, MembersPerTeam)
// 收集成功的成员
var registeredChildren []MemberAccount
for _, c := range children {
if c.Success {
registeredChildren = append(registeredChildren, c)
}
}
if len(registeredChildren) == 0 {
log.Error("No members registered")
return registered, 0
}
// Step 4: 串行入库
log.Info("Starting S2A authorization...")
for i, child := range registeredChildren {
log.Log("[Member %d] Getting S2A auth URL...", i+1)
s2aResp, err := auth.GenerateS2AAuthURL(cfg.S2AApiBase, cfg.S2AAdminKey, cfg.ProxyID)
if err != nil {
log.Error("[Member %d] Auth URL failed: %v", i+1, err)
continue
}
log.Success("[Member %d] Got auth URL", i+1)
log.Log("[Member %d] Running browser automation (Chromedp)...", i+1)
code, err := auth.CompleteWithChromedp(s2aResp.Data.AuthURL, child.Email, child.Password, teamID, true, proxy)
if err != nil {
log.Error("[Member %d] Browser auth failed: %v", i+1, err)
continue
}
log.Success("[Member %d] Browser auth complete", i+1)
log.Log("[Member %d] Submitting to S2A...", i+1)
result, err := auth.SubmitS2AOAuth(
cfg.S2AApiBase,
cfg.S2AAdminKey,
s2aResp.Data.SessionID,
code,
child.Email,
cfg.Concurrency,
cfg.Priority,
cfg.GroupIDs,
cfg.ProxyID,
)
if err != nil {
log.Error("[Member %d] S2A submit failed: %v", i+1, err)
continue
}
log.Highlight("[Member %d] Added to S2A! ID=%d, Status=%s", i+1, result.Data.ID, result.Data.Status)
s2a++
time.Sleep(500 * time.Millisecond)
}
log.Success("Team complete: %d registered, %d in S2A", registered, s2a)
return registered, s2a
}
// registerMemberDetailed 详细的注册流程,带日志
func registerMemberDetailed(log *TeamLogger, memberNum int, email, password, name, birthdate, proxy string) error {
prefix := fmt.Sprintf("[Member %d]", memberNum)
log.Log("%s Creating TLS client...", prefix)
reg, err := register.New(proxy)
if err != nil {
return err
}
log.Log("%s Initializing session...", prefix)
if err := reg.InitSession(); err != nil {
return fmt.Errorf("初始化失败: %v", err)
}
log.Success("%s Session initialized", prefix)
log.Log("%s Getting authorize URL...", prefix)
if err := reg.GetAuthorizeURL(email); err != nil {
return fmt.Errorf("获取授权URL失败: %v", err)
}
log.Success("%s Got authorize URL", prefix)
log.Log("%s Starting authorize flow...", prefix)
if err := reg.StartAuthorize(); err != nil {
return fmt.Errorf("启动授权失败: %v", err)
}
log.Success("%s Authorize flow started", prefix)
log.Log("%s Registering account...", prefix)
if err := reg.Register(email, password); err != nil {
return fmt.Errorf("注册失败: %v", err)
}
log.Success("%s Account registered", prefix)
log.Log("%s Sending verification email...", prefix)
if err := reg.SendVerificationEmail(); err != nil {
return fmt.Errorf("发送邮件失败: %v", err)
}
log.Success("%s Verification email sent", prefix)
log.Log("%s Waiting for OTP code (5s timeout)...", prefix)
otpCode, err := mail.GetVerificationCode(email, 5*time.Second)
if err != nil {
log.Log("%s OTP not received in 5s, waiting 15s more...", prefix)
otpCode, err = mail.GetVerificationCode(email, 15*time.Second)
if err != nil {
return fmt.Errorf("验证码获取超时")
}
}
log.Success("%s Got OTP: %s", prefix, otpCode)
log.Log("%s Validating OTP...", prefix)
if err := reg.ValidateOTP(otpCode); err != nil {
return fmt.Errorf("OTP验证失败: %v", err)
}
log.Success("%s OTP validated", prefix)
log.Log("%s Creating account (name=%s, birthdate=%s)...", prefix, name, birthdate)
if err := reg.CreateAccount(name, birthdate); err != nil {
return fmt.Errorf("创建账户失败: %v", err)
}
log.Success("%s Account created", prefix)
log.Log("%s Getting session token...", prefix)
_ = reg.GetSessionToken()
if reg.AccessToken != "" {
log.Success("%s Got access token: %s...", prefix, truncate(reg.AccessToken, 30))
}
return nil
}
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen]
}