feat: Implement browser-based OAuth authentication using chromedp and rod, and add a team processing API.

This commit is contained in:
2026-02-02 05:05:07 +08:00
parent ee5b69af13
commit 4cd9f2b2b7
3 changed files with 66 additions and 122 deletions

View File

@@ -575,10 +575,9 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
}
// 发送邀请
logger.Info(fmt.Sprintf("%s [发送邀请] %s", memberLogPrefix, currentEmail), currentEmail, "team")
if err := inviter.SendInvites([]string{currentEmail}); err != nil {
errStr := err.Error()
logger.Error(fmt.Sprintf("%s [邀请失败] %v", memberLogPrefix, err), currentEmail, "team")
logger.Error(fmt.Sprintf("%s 邀请失败: %v", memberLogPrefix, err), currentEmail, "team")
// 检测 Team 已达邀请上限401 或 maximum number of seats
if strings.Contains(errStr, "401") || strings.Contains(errStr, "maximum number of seats") {
@@ -587,18 +586,14 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
}
continue
}
logger.Info(fmt.Sprintf("%s [邀请成功]", memberLogPrefix), currentEmail, "team")
// 再次检查是否应该停止(邀请期间其他 goroutine 可能已标记)
if isTeamExhausted() {
return false
}
// 创建注册日志记录器
regLogger := NewRegisterLogger(memberLogPrefix, currentEmail)
// 注册
_, err := registerWithTimeoutLogged(currentEmail, currentPassword, name, birthdate, req.Proxy, regLogger)
_, err := registerWithTimeout(currentEmail, currentPassword, name, birthdate, req.Proxy)
if err != nil {
logger.Error(fmt.Sprintf("%s [注册失败] %v", memberLogPrefix, err), currentEmail, "team")
continue
@@ -736,25 +731,23 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
logger.Warning(fmt.Sprintf("%s 入库重试 (第%d次)", memberLogPrefix, attempt+1), memberChild.Email, "team")
}
// 创建日志回调
// 创建日志回调(只输出关键日志)
authLogger := auth.NewAuthLogger(memberChild.Email, logPrefix, memberIdx+1, func(entry auth.AuthLogEntry) {
stepName := auth.StepName(entry.Step)
// 只输出错误和关键步骤
if entry.IsError {
logger.Error(fmt.Sprintf("%s [%s] %s (%.1fs)", memberLogPrefix, stepName, entry.Message, entry.Duration.Seconds()), memberChild.Email, "team")
} else {
logger.Info(fmt.Sprintf("%s [%s] %s", memberLogPrefix, stepName, entry.Message), memberChild.Email, "team")
logger.Error(fmt.Sprintf("%s %s", memberLogPrefix, entry.Message), memberChild.Email, "team")
} else if entry.Step == auth.StepComplete || entry.Step == auth.StepConsent || entry.Step == auth.StepSelectWorkspace {
logger.Info(fmt.Sprintf("%s %s", memberLogPrefix, entry.Message), memberChild.Email, "team")
}
})
// 获取授权 URL
logger.Info(fmt.Sprintf("%s 获取 S2A 授权 URL...", memberLogPrefix), memberChild.Email, "team")
s2aResp, err := auth.GenerateS2AAuthURL(config.Global.S2AApiBase, config.Global.S2AAdminKey, config.Global.ProxyID)
if err != nil {
lastError = fmt.Sprintf("获取授权URL失败: %v", err)
logger.Error(fmt.Sprintf("%s %s", memberLogPrefix, lastError), memberChild.Email, "team")
continue
}
logger.Info(fmt.Sprintf("%s 授权 URL 获取成功, SessionID: %s", memberLogPrefix, s2aResp.Data.SessionID[:8]+"..."), memberChild.Email, "team")
// 根据配置选择浏览器自动化
var code string
@@ -765,13 +758,11 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
}
if err != nil {
lastError = fmt.Sprintf("浏览器授权失败: %v", err)
logger.Error(fmt.Sprintf("%s %s (耗时: %.1fs)", memberLogPrefix, lastError, authLogger.TotalDuration().Seconds()), memberChild.Email, "team")
logger.Error(fmt.Sprintf("%s %s", memberLogPrefix, lastError), memberChild.Email, "team")
continue
}
logger.Info(fmt.Sprintf("%s 浏览器授权成功, 授权码: %s... (耗时: %.1fs)", memberLogPrefix, code[:8], authLogger.TotalDuration().Seconds()), memberChild.Email, "team")
// 提交到 S2A
logger.Info(fmt.Sprintf("%s 正在提交到 S2A...", memberLogPrefix), memberChild.Email, "team")
_, err = auth.SubmitS2AOAuth(
config.Global.S2AApiBase,
config.Global.S2AAdminKey,
@@ -826,8 +817,7 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
if req.IncludeOwner && teamProcessState.Running {
ownerLogPrefix := fmt.Sprintf("%s [母号 ]", logPrefix)
ownerStartTime := time.Now()
logger.Info(fmt.Sprintf("%s ════════ 开始母号入库 ════════", logPrefix), owner.Email, "team")
logger.Info(fmt.Sprintf("%s 开始入库 | 邮箱: %s", ownerLogPrefix, owner.Email), owner.Email, "team")
logger.Info(fmt.Sprintf("%s 开始母号入库...", ownerLogPrefix), owner.Email, "team")
var ownerSuccess bool
var lastError string
@@ -836,24 +826,21 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
logger.Warning(fmt.Sprintf("%s 入库重试 (第%d次)", ownerLogPrefix, attempt+1), owner.Email, "team")
}
// 创建日志回调
// 创建日志回调(只输出关键日志)
authLogger := auth.NewAuthLogger(owner.Email, logPrefix, 0, func(entry auth.AuthLogEntry) {
stepName := auth.StepName(entry.Step)
if entry.IsError {
logger.Error(fmt.Sprintf("%s [%s] %s (%.1fs)", ownerLogPrefix, stepName, entry.Message, entry.Duration.Seconds()), owner.Email, "team")
} else {
logger.Info(fmt.Sprintf("%s [%s] %s", ownerLogPrefix, stepName, entry.Message), owner.Email, "team")
logger.Error(fmt.Sprintf("%s %s", ownerLogPrefix, entry.Message), owner.Email, "team")
} else if entry.Step == auth.StepComplete || entry.Step == auth.StepConsent || entry.Step == auth.StepSelectWorkspace {
logger.Info(fmt.Sprintf("%s %s", ownerLogPrefix, entry.Message), owner.Email, "team")
}
})
logger.Info(fmt.Sprintf("%s 获取 S2A 授权 URL...", ownerLogPrefix), owner.Email, "team")
s2aResp, err := auth.GenerateS2AAuthURL(config.Global.S2AApiBase, config.Global.S2AAdminKey, config.Global.ProxyID)
if err != nil {
lastError = fmt.Sprintf("获取授权URL失败: %v", err)
logger.Error(fmt.Sprintf("%s %s", ownerLogPrefix, lastError), owner.Email, "team")
continue
}
logger.Info(fmt.Sprintf("%s 授权 URL 获取成功, SessionID: %s", ownerLogPrefix, s2aResp.Data.SessionID[:8]+"..."), owner.Email, "team")
var code string
if req.BrowserType == "rod" {
@@ -863,12 +850,11 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
}
if err != nil {
lastError = fmt.Sprintf("浏览器授权失败: %v", err)
logger.Error(fmt.Sprintf("%s %s (耗时: %.1fs)", ownerLogPrefix, lastError, authLogger.TotalDuration().Seconds()), owner.Email, "team")
logger.Error(fmt.Sprintf("%s %s", ownerLogPrefix, lastError), owner.Email, "team")
continue
}
logger.Info(fmt.Sprintf("%s 浏览器授权成功, 授权码: %s... (耗时: %.1fs)", ownerLogPrefix, code[:8], authLogger.TotalDuration().Seconds()), owner.Email, "team")
logger.Info(fmt.Sprintf("%s 正在提交到 S2A...", ownerLogPrefix), owner.Email, "team")
// 提交到 S2A
_, err = auth.SubmitS2AOAuth(
config.Global.S2AApiBase,
config.Global.S2AAdminKey,
@@ -896,7 +882,6 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
if !ownerSuccess {
result.Errors = append(result.Errors, fmt.Sprintf("母号入库失败: %s", lastError))
}
logger.Info(fmt.Sprintf("%s ════════ 母号入库完成 ════════", logPrefix), owner.Email, "team")
}
result.DurationMs = time.Since(startTime).Milliseconds()
@@ -909,97 +894,50 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
return result
}
// RegisterLogger 注册日志记录器
type RegisterLogger struct {
logPrefix string
email string
startTime time.Time
}
// NewRegisterLogger 创建注册日志记录器
func NewRegisterLogger(logPrefix, email string) *RegisterLogger {
return &RegisterLogger{
logPrefix: logPrefix,
email: email,
startTime: time.Now(),
}
}
// LogStep 记录步骤
func (l *RegisterLogger) LogStep(step string) {
logger.Info(fmt.Sprintf("%s [%s]", l.logPrefix, step), l.email, "team")
}
// LogStepDone 记录步骤完成
func (l *RegisterLogger) LogStepDone(step string, duration time.Duration) {
logger.Info(fmt.Sprintf("%s [%s] 完成 (%.1fs)", l.logPrefix, step, duration.Seconds()), l.email, "team")
}
// registerWithTimeout 带超时的注册
func registerWithTimeout(email, password, name, birthdate, proxy string) (*register.ChatGPTReg, error) {
return registerWithTimeoutLogged(email, password, name, birthdate, proxy, nil)
}
// registerWithTimeoutLogged 带超时和日志的注册
func registerWithTimeoutLogged(email, password, name, birthdate, proxy string, regLogger *RegisterLogger) (*register.ChatGPTReg, error) {
logStep := func(step string) {
if regLogger != nil {
regLogger.LogStep(step)
}
}
reg, err := register.New(proxy)
if err != nil {
return nil, err
}
logStep("初始化会话")
if err := reg.InitSession(); err != nil {
return nil, fmt.Errorf("初始化失败: %v", err)
}
logStep("获取授权URL")
if err := reg.GetAuthorizeURL(email); err != nil {
return nil, fmt.Errorf("获取授权URL失败: %v", err)
}
logStep("启动授权")
if err := reg.StartAuthorize(); err != nil {
return nil, fmt.Errorf("启动授权失败: %v", err)
}
logStep("提交注册信息")
if err := reg.Register(email, password); err != nil {
return nil, fmt.Errorf("注册失败: %v", err)
}
logStep("发送验证邮件")
if err := reg.SendVerificationEmail(); err != nil {
return nil, fmt.Errorf("发送邮件失败: %v", err)
}
// 短超时获取验证码
logStep("等待验证码 (5s)")
otpCode, err := mail.GetVerificationCode(email, 5*time.Second)
if err != nil {
logStep("等待验证码 (15s)")
otpCode, err = mail.GetVerificationCode(email, 15*time.Second)
if err != nil {
return nil, fmt.Errorf("验证码获取超时")
}
}
logStep("验证OTP")
if err := reg.ValidateOTP(otpCode); err != nil {
return nil, fmt.Errorf("OTP验证失败: %v", err)
}
logStep("创建账户")
if err := reg.CreateAccount(name, birthdate); err != nil {
return nil, fmt.Errorf("创建账户失败: %v", err)
}
logStep("获取会话令牌")
_ = reg.GetSessionToken()
return reg, nil
}