265 lines
6.9 KiB
Go
265 lines
6.9 KiB
Go
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 // 处理的任务时间
|
|
}
|
|
|
|
// ProcessEmailTasks 是 EmailSender 结构体的方法,用于处理邮件任务。
|
|
func (m *EmailSender) ProcessEmailTasks() {
|
|
for {
|
|
// 从 EmailTasks 通道中接收邮件任务
|
|
emailformat, ok := <-m.EmailTasks
|
|
if !ok {
|
|
log.Println("Email task channel closed")
|
|
break
|
|
}
|
|
|
|
// 验证邮件格式是否合法
|
|
err := emailformat.Vaild()
|
|
if err != nil {
|
|
logx.Error(err)
|
|
continue
|
|
}
|
|
|
|
// 加锁,避免并发修改 emailSending 字典
|
|
m.lock.Lock()
|
|
|
|
// 检查 emailSending 字典中是否已存在相同的任务
|
|
_, isSending := m.emailSending[emailformat.UniqueKey]
|
|
if isSending {
|
|
m.lock.Unlock()
|
|
continue
|
|
}
|
|
|
|
// 将邮件任务添加到 emailSending 字典中
|
|
m.emailSending[emailformat.UniqueKey] = &EmailTask{
|
|
Email: emailformat,
|
|
SendTime: time.Now().UTC(),
|
|
}
|
|
m.lock.Unlock()
|
|
|
|
// 获取一个信号量,限制同时发送的邮件任务数量
|
|
m.semaphore <- struct{}{}
|
|
|
|
go func() {
|
|
defer func() { <-m.semaphore }() // 释放一个信号量
|
|
|
|
// 渲染邮件模板内容
|
|
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 是 EmailSender 结构体的方法,用于重发邮件。
|
|
func (m *EmailSender) Resend(uniqueKey string, content []byte) {
|
|
// 等待一段时间后重发邮件,避免频繁重发
|
|
time.Sleep(m.ResendTimeLimit)
|
|
|
|
m.lock.Lock()
|
|
defer m.lock.Unlock()
|
|
|
|
// 检查邮件任务是否仍存在并且未成功发送
|
|
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 {
|
|
// 从 emailSending 字典中删除已成功发送的邮件任务
|
|
delete(m.emailSending, uniqueKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ClearExpiredTasks 是 EmailSender 结构体的方法,用于清除过期的邮件任务。
|
|
func (m *EmailSender) ClearExpiredTasks() {
|
|
// 每分钟触发一次清除操作
|
|
ticker := time.NewTicker(time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
<-ticker.C
|
|
|
|
m.lock.Lock()
|
|
// 遍历 emailSending 字典,删除发送时间超过一定限制的过期任务
|
|
for email, task := range m.emailSending {
|
|
if task.SendTime.Add(m.ResendTimeLimit).Before(time.Now().UTC()) {
|
|
delete(m.emailSending, email)
|
|
}
|
|
}
|
|
m.lock.Unlock()
|
|
}
|
|
}
|
|
|
|
// RenderEmailTemplate 是一个渲染邮件模板的函数,根据给定的参数生成邮件内容。
|
|
// 参数:
|
|
// - templateName: 邮件模板的名称
|
|
// - companyName: 公司名称
|
|
// - confirmationLink: 确认链接
|
|
// - senderName: 发件人姓名
|
|
// - senderTitle: 发件人职务
|
|
// - extend: 扩展字段,包含其他自定义键值对的映射
|
|
// 返回值:
|
|
// - []byte: 渲染后的邮件内容(以字节切片形式返回)
|
|
|
|
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
|
|
|
|
// 使用给定的数据映射执行邮件模板渲染
|
|
err := tpls.ExecuteTemplate(&result, templateName, data)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// 将渲染后的邮件内容转换为字节切片并返回
|
|
return result.Bytes()
|
|
}
|