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:
455
backend/internal/mail/service.go
Normal file
455
backend/internal/mail/service.go
Normal file
@@ -0,0 +1,455 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"codex-pool/internal/config"
|
||||
)
|
||||
|
||||
// 默认邮箱配置
|
||||
var defaultMailServices = []config.MailServiceConfig{
|
||||
{
|
||||
Name: "esyteam",
|
||||
APIBase: "https://mail.esyteam.edu.kg",
|
||||
APIToken: "005d6f3e-5312-4c37-8125-e1f71243e1ba",
|
||||
Domain: "esyteam.edu.kg",
|
||||
EmailPath: "/api/public/emailList",
|
||||
AddUserAPI: "/api/public/addUser",
|
||||
},
|
||||
}
|
||||
|
||||
// 全局变量
|
||||
var (
|
||||
currentMailServices []config.MailServiceConfig
|
||||
mailServicesMutex sync.RWMutex
|
||||
currentServiceIndex int
|
||||
)
|
||||
|
||||
func init() {
|
||||
currentMailServices = defaultMailServices
|
||||
}
|
||||
|
||||
// Init 初始化邮箱服务配置
|
||||
func Init(services []config.MailServiceConfig) {
|
||||
mailServicesMutex.Lock()
|
||||
defer mailServicesMutex.Unlock()
|
||||
|
||||
if len(services) > 0 {
|
||||
for i := range services {
|
||||
if services[i].EmailPath == "" {
|
||||
services[i].EmailPath = "/api/public/emailList"
|
||||
}
|
||||
if services[i].AddUserAPI == "" {
|
||||
services[i].AddUserAPI = "/api/public/addUser"
|
||||
}
|
||||
if services[i].Name == "" {
|
||||
services[i].Name = fmt.Sprintf("mail-service-%d", i+1)
|
||||
}
|
||||
}
|
||||
currentMailServices = services
|
||||
fmt.Printf("[邮箱] 已加载 %d 个邮箱服务配置:\n", len(services))
|
||||
for _, s := range services {
|
||||
fmt.Printf(" - %s (%s) @ %s\n", s.Name, s.Domain, s.APIBase)
|
||||
}
|
||||
} else {
|
||||
currentMailServices = defaultMailServices
|
||||
fmt.Println("[邮箱] 使用默认邮箱服务配置")
|
||||
}
|
||||
currentServiceIndex = 0
|
||||
}
|
||||
|
||||
// GetServices 获取当前邮箱服务配置
|
||||
func GetServices() []config.MailServiceConfig {
|
||||
mailServicesMutex.RLock()
|
||||
defer mailServicesMutex.RUnlock()
|
||||
return currentMailServices
|
||||
}
|
||||
|
||||
// GetNextService 轮询获取下一个邮箱服务
|
||||
func GetNextService() config.MailServiceConfig {
|
||||
mailServicesMutex.Lock()
|
||||
defer mailServicesMutex.Unlock()
|
||||
|
||||
if len(currentMailServices) == 0 {
|
||||
return defaultMailServices[0]
|
||||
}
|
||||
|
||||
service := currentMailServices[currentServiceIndex]
|
||||
currentServiceIndex = (currentServiceIndex + 1) % len(currentMailServices)
|
||||
return service
|
||||
}
|
||||
|
||||
// GetRandomService 随机获取一个邮箱服务
|
||||
func GetRandomService() config.MailServiceConfig {
|
||||
mailServicesMutex.RLock()
|
||||
defer mailServicesMutex.RUnlock()
|
||||
|
||||
if len(currentMailServices) == 0 {
|
||||
return defaultMailServices[0]
|
||||
}
|
||||
|
||||
return currentMailServices[rand.Intn(len(currentMailServices))]
|
||||
}
|
||||
|
||||
// GetServiceByDomain 根据域名获取对应的邮箱服务
|
||||
func GetServiceByDomain(domain string) *config.MailServiceConfig {
|
||||
mailServicesMutex.RLock()
|
||||
defer mailServicesMutex.RUnlock()
|
||||
|
||||
for _, s := range currentMailServices {
|
||||
if s.Domain == domain || strings.HasSuffix(domain, "."+s.Domain) {
|
||||
return &s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ==================== 邮件结构 ====================
|
||||
|
||||
// EmailListRequest 邮件列表请求
|
||||
type EmailListRequest struct {
|
||||
ToEmail string `json:"toEmail"`
|
||||
TimeSort string `json:"timeSort"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// EmailListResponse 邮件列表响应
|
||||
type EmailListResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data []EmailItem `json:"data"`
|
||||
}
|
||||
|
||||
// EmailItem 邮件项
|
||||
type EmailItem struct {
|
||||
Content string `json:"content"`
|
||||
Text string `json:"text"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
// AddUserRequest 创建用户请求
|
||||
type AddUserRequest struct {
|
||||
List []AddUserItem `json:"list"`
|
||||
}
|
||||
|
||||
// AddUserItem 用户项
|
||||
type AddUserItem struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// AddUserResponse 创建用户响应
|
||||
type AddUserResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ==================== 邮箱生成 ====================
|
||||
|
||||
// GenerateEmail 生成随机邮箱并在邮件系统中创建
|
||||
func GenerateEmail() string {
|
||||
return GenerateEmailWithService(GetNextService())
|
||||
}
|
||||
|
||||
// GenerateEmailWithService 使用指定服务生成随机邮箱
|
||||
func GenerateEmailWithService(service config.MailServiceConfig) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, 10)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
email := string(b) + "@" + service.Domain
|
||||
|
||||
if err := CreateMailboxWithService(email, service); err != nil {
|
||||
fmt.Printf(" [!] 创建邮箱失败 (%s): %v (继续尝试)\n", service.Name, err)
|
||||
}
|
||||
|
||||
return email
|
||||
}
|
||||
|
||||
// CreateMailbox 在邮件系统中创建邮箱
|
||||
func CreateMailbox(email string) error {
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("无效的邮箱地址: %s", email)
|
||||
}
|
||||
|
||||
domain := parts[1]
|
||||
service := GetServiceByDomain(domain)
|
||||
if service == nil {
|
||||
services := GetServices()
|
||||
if len(services) > 0 {
|
||||
service = &services[0]
|
||||
} else {
|
||||
return fmt.Errorf("没有可用的邮箱服务")
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMailboxWithService(email, *service)
|
||||
}
|
||||
|
||||
// CreateMailboxWithService 使用指定服务在邮件系统中创建邮箱
|
||||
func CreateMailboxWithService(email string, service config.MailServiceConfig) error {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) == 2 {
|
||||
domain := parts[1]
|
||||
if strings.HasSuffix(domain, "."+service.Domain) {
|
||||
email = parts[0] + "@" + service.Domain
|
||||
}
|
||||
}
|
||||
|
||||
payload := AddUserRequest{
|
||||
List: []AddUserItem{
|
||||
{Email: email, Password: GeneratePassword()},
|
||||
},
|
||||
}
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
|
||||
req, err := http.NewRequest("POST", service.APIBase+service.AddUserAPI, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", service.APIToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result AddUserResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Code != 200 {
|
||||
if strings.Contains(result.Message, "exist") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("API 错误: %s", result.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GeneratePassword 生成随机密码
|
||||
func GeneratePassword() string {
|
||||
const (
|
||||
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
lower = "abcdefghijklmnopqrstuvwxyz"
|
||||
digits = "0123456789"
|
||||
special = "#$%@!"
|
||||
)
|
||||
|
||||
password := make([]byte, 12)
|
||||
password[0] = upper[rand.Intn(len(upper))]
|
||||
password[1] = lower[rand.Intn(len(lower))]
|
||||
password[10] = digits[rand.Intn(len(digits))]
|
||||
password[11] = special[rand.Intn(len(special))]
|
||||
|
||||
charset := upper + lower
|
||||
for i := 2; i < 10; i++ {
|
||||
password[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
|
||||
return string(password)
|
||||
}
|
||||
|
||||
// ==================== 邮件客户端 ====================
|
||||
|
||||
// Client 邮件客户端
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
service *config.MailServiceConfig
|
||||
}
|
||||
|
||||
// NewClient 创建邮件客户端
|
||||
func NewClient() *Client {
|
||||
services := GetServices()
|
||||
var service *config.MailServiceConfig
|
||||
if len(services) > 0 {
|
||||
service = &services[0]
|
||||
} else {
|
||||
service = &defaultMailServices[0]
|
||||
}
|
||||
return &Client{
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithService 创建指定服务的邮件客户端
|
||||
func NewClientWithService(service config.MailServiceConfig) *Client {
|
||||
return &Client{
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
service: &service,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientForEmail 根据邮箱地址创建对应的邮件客户端
|
||||
func NewClientForEmail(email string) *Client {
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) == 2 {
|
||||
if service := GetServiceByDomain(parts[1]); service != nil {
|
||||
return NewClientWithService(*service)
|
||||
}
|
||||
}
|
||||
return NewClient()
|
||||
}
|
||||
|
||||
// GetEmails 获取邮件列表
|
||||
func (m *Client) GetEmails(email string, size int) ([]EmailItem, error) {
|
||||
service := m.service
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) == 2 {
|
||||
if s := GetServiceByDomain(parts[1]); s != nil {
|
||||
service = s
|
||||
}
|
||||
}
|
||||
|
||||
url := service.APIBase + service.EmailPath
|
||||
|
||||
payload := EmailListRequest{
|
||||
ToEmail: email,
|
||||
TimeSort: "desc",
|
||||
Size: size,
|
||||
}
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Authorization", service.APIToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := m.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result EmailListResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 200 {
|
||||
return nil, fmt.Errorf("API 错误: %d", result.Code)
|
||||
}
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
// WaitForCode 等待验证码邮件
|
||||
func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error) {
|
||||
start := time.Now()
|
||||
codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
|
||||
|
||||
for time.Since(start) < timeout {
|
||||
emails, err := m.GetEmails(email, 10)
|
||||
if err == nil {
|
||||
for _, mail := range emails {
|
||||
subject := strings.ToLower(mail.Subject)
|
||||
// 匹配多种可能的验证码邮件主题
|
||||
isCodeEmail := strings.Contains(subject, "code") ||
|
||||
strings.Contains(subject, "verify") ||
|
||||
strings.Contains(subject, "verification") ||
|
||||
strings.Contains(subject, "openai") ||
|
||||
strings.Contains(subject, "confirm")
|
||||
|
||||
if !isCodeEmail {
|
||||
continue
|
||||
}
|
||||
|
||||
content := mail.Content
|
||||
if content == "" {
|
||||
content = mail.Text
|
||||
}
|
||||
matches := codeRegex.FindStringSubmatch(content)
|
||||
if len(matches) >= 2 {
|
||||
return matches[1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("验证码获取超时")
|
||||
}
|
||||
|
||||
// WaitForInviteLink 等待邀请邮件并提取链接
|
||||
func (m *Client) WaitForInviteLink(email string, timeout time.Duration) (string, error) {
|
||||
start := time.Now()
|
||||
|
||||
for time.Since(start) < timeout {
|
||||
emails, err := m.GetEmails(email, 10)
|
||||
if err == nil {
|
||||
for _, mail := range emails {
|
||||
content := mail.Content
|
||||
if content == "" {
|
||||
content = mail.Text
|
||||
}
|
||||
|
||||
if strings.Contains(mail.Subject, "invite") ||
|
||||
strings.Contains(mail.Subject, "Team") ||
|
||||
strings.Contains(mail.Subject, "ChatGPT") ||
|
||||
strings.Contains(content, "invite") {
|
||||
|
||||
link := extractInviteLink(content)
|
||||
if link != "" {
|
||||
return link, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("等待邀请邮件超时")
|
||||
}
|
||||
|
||||
// extractInviteLink 从邮件内容提取邀请链接
|
||||
func extractInviteLink(content string) string {
|
||||
patterns := []string{
|
||||
`https://chatgpt\.com/invite/[^\s"'<>]+`,
|
||||
`https://chat\.openai\.com/invite/[^\s"'<>]+`,
|
||||
`https://chatgpt\.com/[^\s"'<>]*accept[^\s"'<>]*`,
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindString(content)
|
||||
if match != "" {
|
||||
match = strings.ReplaceAll(match, "&", "&")
|
||||
return match
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ==================== 便捷函数 ====================
|
||||
|
||||
// WaitForInviteEmail 等待邀请邮件
|
||||
func WaitForInviteEmail(email string, timeout time.Duration) (string, error) {
|
||||
client := NewClientForEmail(email)
|
||||
return client.WaitForInviteLink(email, timeout)
|
||||
}
|
||||
|
||||
// GetVerificationCode 获取验证码
|
||||
func GetVerificationCode(email string, timeout time.Duration) (string, error) {
|
||||
client := NewClientForEmail(email)
|
||||
return client.WaitForCode(email, timeout)
|
||||
}
|
||||
Reference in New Issue
Block a user