package logic import ( "bytes" "fmt" "fusenapi/utils/check" "fusenapi/utils/fstpl" "html/template" "log" "net/smtp" "sync" "time" "github.com/zeromicro/go-zero/core/logx" ) var EmailTaskResendTime = time.Second * 30 var TimeLimit *check.TimeLimit[string] var EmailManager *EmailSender var tpls *template.Template func init() { var err error tpls = fstpl.AutoParseTplFiles() if err != nil { log.Fatal(err) } TimeLimit = check.NewTimelimit[string](EmailTaskResendTime) // Initialize the email manager EmailManager = &EmailSender{ EmailTasks: make(chan *EmailFormat, 10), Auth: smtp.PlainAuth( "fusen support", "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 { TemplateName string // 模板名字 UniqueKey string // 用于处理唯一的任务,重发都会被利用到 TargetEmail string // 发送的目标email CompanyName string // fs公司名 ConfirmationLink string // fs确认连接 SenderName string // 发送人 SenderTitle string // 发送标题 Extend map[string]string } func (eformat *EmailFormat) Vaild() error { // 1. 检查模板名称 if tpls.Lookup(eformat.TemplateName) == nil { return fmt.Errorf("%s template name is not found", eformat.TemplateName) } // 2. 检查公司名称 if eformat.CompanyName == "" { return fmt.Errorf("company name cannot be empty") } // 3. 检查确认链接 if eformat.ConfirmationLink == "" { return fmt.Errorf("confirmation link cannot be empty") } // 4. 检查发送人名称 if eformat.SenderName == "" { return fmt.Errorf("sender name cannot be empty") } // 5. 检查发送人头衔 if eformat.SenderTitle == "" { return fmt.Errorf("sender title cannot be empty") } // 6. 检查目标邮箱 if eformat.TargetEmail == "" { return fmt.Errorf("target email cannot be empty") } // 7. 检查唯一键 if eformat.UniqueKey == "" { return fmt.Errorf("unique key cannot be empty") } // 所有校验通过 return nil } // 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 } err := emailformat.Vaild() if err != nil { logx.Error(err) 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.TemplateName, emailformat.CompanyName, emailformat.ConfirmationLink, emailformat.SenderName, emailformat.SenderTitle, emailformat.Extend, ) 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(templateName, companyName, confirmationLink, senderName, senderTitle string, extend map[string]string) []byte { data := map[string]string{ "CompanyName": companyName, "ConfirmationLink": confirmationLink, "SenderName": senderName, "SenderTitle": senderTitle, } for k, v := range extend { data[k] = v } var result bytes.Buffer // result.Write([]byte("MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n")) err := tpls.ExecuteTemplate(&result, templateName, data) if err != nil { log.Fatal(err) } return result.Bytes() }