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() } } const emailTemplate = `Subject: Your {{.CompanyName}} Account Confirmation Dear Thank you for creating an account with {{.CompanyName}}. We're excited to have you on board! Before we get started, we just need to confirm that this is the right email address. Please confirm your email address by clicking on the link below: {{.ConfirmationLink}} Once you've confirmed, you can get started with {{.CompanyName}}. If you have any questions, feel free to reply to this email. We're here to help! If you did not create an account with us, please ignore this email. Thanks, {{.SenderName}} {{.SenderTitle}} {{.CompanyName}} ` func RenderEmailTemplate(companyName, confirmationLink, senderName, senderTitle string) []byte { tmpl, err := template.New("email").Parse(emailTemplate) 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() }