package logic import ( "bytes" "fusenapi/utils/check" "log" "net/smtp" "sync" "text/template" "time" "github.com/zeromicro/go-zero/core/logx" ) var EmailTaskResendTime = time.Second * 30 var TimeLimit *check.TimeLimit[string] var EmailManager *EmailSender func init() { TimeLimit = check.NewTimelimit[string](EmailTaskResendTime) // Initialize the email manager EmailManager = &EmailSender{ EmailTasks: make(chan *EmailFormat, 10), Auth: smtp.PlainAuth( "", "support@fusenpack.com", "wfbjpdgvaozjvwah", "smtp.gmail.com", ), FromEmail: "support@fusenpack.com", emailSending: make(map[string]*EmailTask, 10), ResendTimeLimit: EmailTaskResendTime, semaphore: make(chan struct{}, 100), // Initialize semaphore with a capacity of 10 } // Start processing email tasks go EmailManager.ProcessEmailTasks() // Start clearing expired tasks go EmailManager.ClearExpiredTasks() } type EmailFormat struct { UniqueKey string // 用于处理唯一的任务,重发都会被利用到 TargetEmail string // 发送的目标email CompanyName string // fs公司名 ConfirmationLink string // fs确认连接 SenderName string // 发送人 SenderTitle string // 发送标题 } // EmailSender type EmailSender struct { lock sync.Mutex EmailTasks chan *EmailFormat Auth smtp.Auth FromEmail string ResendTimeLimit time.Duration emailSending map[string]*EmailTask semaphore chan struct{} } // EmailTask type EmailTask struct { Email *EmailFormat // email SendTime time.Time // 处理的任务时间 } func (m *EmailSender) ProcessEmailTasks() { for { emailformat, ok := <-m.EmailTasks if !ok { log.Println("Email task channel closed") break } if emailformat.UniqueKey == "" { logx.Error("email UniqueKey must be exists") continue } m.lock.Lock() _, isSending := m.emailSending[emailformat.UniqueKey] if isSending { m.lock.Unlock() continue } m.emailSending[emailformat.UniqueKey] = &EmailTask{ Email: emailformat, SendTime: time.Now().UTC(), } m.lock.Unlock() // Acquire a token m.semaphore <- struct{}{} go func() { defer func() { <-m.semaphore }() // Release a token content := RenderEmailTemplate(emailformat.CompanyName, emailformat.ConfirmationLink, emailformat.SenderName, emailformat.SenderTitle) err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailformat.TargetEmail}, content) if err != nil { log.Printf("Failed to send email to %s: %v\n", emailformat, err) m.Resend(emailformat.UniqueKey, content) } }() } } // Resend 重发邮件 func (m *EmailSender) Resend(uniqueKey string, content []byte) { time.Sleep(m.ResendTimeLimit) m.lock.Lock() defer m.lock.Unlock() // Check if the email task still exists and has not been sent successfully if task, ok := m.emailSending[uniqueKey]; ok && task.SendTime.Add(m.ResendTimeLimit).After(time.Now().UTC()) { err := smtp.SendMail(task.Email.TargetEmail, m.Auth, m.FromEmail, []string{task.Email.TargetEmail}, content) if err != nil { log.Printf("Failed to resend email to %s: %v\n", task.Email.TargetEmail, err) } else { delete(m.emailSending, uniqueKey) } } } // ClearExpiredTasks 清除过期的邮件任务 func (m *EmailSender) ClearExpiredTasks() { ticker := time.NewTicker(time.Minute) defer ticker.Stop() for { <-ticker.C m.lock.Lock() for email, task := range m.emailSending { if task.SendTime.Add(m.ResendTimeLimit).Before(time.Now().UTC()) { delete(m.emailSending, email) } } m.lock.Unlock() } } func RenderEmailTemplate(companyName, confirmationLink, senderName, senderTitle string) []byte { tmpl, err := template.New("email").ParseFiles("../../html_template/email_register.tpl") if err != nil { log.Fatal(err) } data := map[string]string{ "CompanyName": companyName, "ConfirmationLink": confirmationLink, "SenderName": senderName, "SenderTitle": senderTitle, } var result bytes.Buffer err = tmpl.Execute(&result, data) if err != nil { log.Fatal(err) } return result.Bytes() }