Files
xiaolajiaoyyds 6f71df4849 Initial open-source release
多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。

Features:
- 用户名/密码注册登录(首个用户自动管理员)
- JWT 认证 + bcrypt 密码加密
- 积分系统与多用户管理
- HuggingFace Space 弹性 Worker 节点管理
- Cloudflare Worker 负载均衡
- 实时日志推送(SSE/WebSocket)
- 管理后台(用户管理、系统设置、代理池、卡池)

Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
2026-03-22 03:44:42 +08:00

174 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tempmail
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/rs/zerolog/log"
)
const (
cooldownRateLimit = 6 * time.Hour // 429 每日限额耗尽,长冷却
cooldownAuthFail = 15 * time.Minute // 401/403 认证失败/额度耗尽,冷却 15 分钟
cooldownError = 30 * time.Second // 其他临时错误冷却时间
)
// MultiProvider 多邮箱服务商自动切换
// 当主 provider 返回 429 或错误时,自动尝试下一个可用 provider
type MultiProvider struct {
providers []EmailProvider
cooldowns map[string]time.Time // provider name → 冷却截止时间
mu sync.RWMutex
}
// NewMultiProvider 根据 worker config 构建多 provider 实例
// 读取配置:
// - email_provider_priority: 逗号分隔的优先级列表(如 "yydsmail")
// - yydsmail_base_url: YYDS Mail API 地址(默认 "")
// - yydsmail_api_key: YYDS Mail API Key
func NewMultiProvider(cfg map[string]string) *MultiProvider {
priority := cfg["email_provider_priority"]
if priority == "" {
log.Warn().Msg("未配置 email_provider_priority,不会启用任何邮箱 provider")
}
// 构建可用 provider 映射(仅构建在 priority 列表中出现的 provider)
available := make(map[string]EmailProvider)
prioritySet := make(map[string]bool)
for _, name := range strings.Split(priority, ",") {
prioritySet[strings.TrimSpace(name)] = true
}
// YYDS Mail(自建临时邮箱服务)
if prioritySet["yydsmail"] {
yydsURL := cfg["yydsmail_base_url"]
if yydsURL == "" {
yydsURL = ""
}
yydsKey := cfg["yydsmail_api_key"]
if yydsKey != "" {
available["yydsmail"] = NewYYDSMailProvider(yydsURL, yydsKey)
} else {
log.Warn().Msg("yydsmail 在优先级列表中但未配置 yydsmail_api_key")
}
}
// 按优先级排列(严格模式:仅使用优先级列表中的 provider)
var providers []EmailProvider
for _, name := range strings.Split(priority, ",") {
name = strings.TrimSpace(name)
if p, ok := available[name]; ok {
providers = append(providers, p)
}
}
// 按成功率加权排序(优先用亲和度高的 provider,加随机抖动防止锁死)
weightedShuffle(providers)
if len(providers) == 0 {
log.Warn().Msg("未配置任何邮箱 provider,请检查系统设置")
}
return &MultiProvider{
providers: providers,
cooldowns: make(map[string]time.Time),
}
}
// isAvailable 检查 provider 是否可用(未在冷却中)
func (mp *MultiProvider) isAvailable(name string) bool {
mp.mu.RLock()
defer mp.mu.RUnlock()
if until, ok := mp.cooldowns[name]; ok {
return time.Now().After(until)
}
return true
}
// markCooldown 标记 provider 进入冷却期(duration 区分 429 和普通错误)
func (mp *MultiProvider) markCooldown(name string, duration time.Duration) {
mp.mu.Lock()
defer mp.mu.Unlock()
mp.cooldowns[name] = time.Now().Add(duration)
log.Warn().Str("provider", name).Dur("cooldown", duration).
Msg("邮箱服务进入冷却期,切换到下一个 provider")
}
// GenerateEmail 尝试所有可用 provider 创建邮箱
func (mp *MultiProvider) GenerateEmail(ctx context.Context) (string, map[string]string, error) {
var errs []string
for _, p := range mp.providers {
if !mp.isAvailable(p.Name()) {
continue
}
addr, meta, err := p.GenerateEmail(ctx)
if err == nil {
log.Debug().Str("provider", p.Name()).Str("email", addr).Msg("邮箱创建成功")
return addr, meta, nil
}
// 429 → 长冷却
if errors.Is(err, ErrRateLimited) {
mp.markCooldown(p.Name(), cooldownRateLimit)
errs = append(errs, fmt.Sprintf("%s: 429 限速", p.Name()))
continue
}
// 401/403 → 中冷却,key 无效不会自愈
if errors.Is(err, ErrAuthFailed) {
mp.markCooldown(p.Name(), cooldownAuthFail)
errs = append(errs, fmt.Sprintf("%s: 认证失败", p.Name()))
log.Warn().Str("provider", p.Name()).Err(err).Msg("邮箱服务认证失败,冷却 15 分钟")
continue
}
// 其他临时错误 → 短冷却(30 秒)
mp.markCooldown(p.Name(), cooldownError)
errs = append(errs, fmt.Sprintf("%s: %s", p.Name(), err.Error()))
log.Warn().Str("provider", p.Name()).Err(err).Msg("邮箱创建失败,进入冷却并尝试下一个")
}
return "", nil, fmt.Errorf("所有邮箱服务均不可用: %s", strings.Join(errs, "; "))
}
// FetchVerificationCode 使用创建邮箱时的同一 provider 获取验证码
func (mp *MultiProvider) FetchVerificationCode(ctx context.Context, addr string, meta map[string]string, maxAttempts int, interval time.Duration) (string, error) {
providerName := meta["provider"]
for _, p := range mp.providers {
if p.Name() == providerName {
return p.FetchVerificationCode(ctx, addr, meta, maxAttempts, interval)
}
}
return "", fmt.Errorf("未找到邮箱 provider: %s", providerName)
}
// DeleteEmail 使用创建邮箱时的同一 provider 删除邮箱
func (mp *MultiProvider) DeleteEmail(ctx context.Context, addr string, meta map[string]string) error {
providerName := meta["provider"]
for _, p := range mp.providers {
if p.Name() == providerName {
return p.DeleteEmail(ctx, addr, meta)
}
}
return nil // provider 找不到,静默返回
}
// ProviderCount 返回可用 provider 数量
func (mp *MultiProvider) ProviderCount() int {
return len(mp.providers)
}
// ProviderNames 返回所有 provider 名称(按优先级排列)
func (mp *MultiProvider) ProviderNames() []string {
names := make([]string, len(mp.providers))
for i, p := range mp.providers {
names[i] = p.Name()
}
return names
}