Files

270 lines
7.6 KiB
Go

package warningnotify
import (
"context"
"encoding/json"
"html"
"mime"
"net"
"net/mail"
"net/smtp"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
func sendWarningEmail(ctx context.Context, batch Batch) error {
recipients := normalizeEmailRecipients(batch.Recipients)
if len(recipients) == 0 {
g.Log().Warningf(
ctx,
"WarningNotify email skipped, no valid recipients, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, count:%d",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
len(batch.Items),
)
return nil
}
cfg, ok := loadWarningEmailConfig(ctx)
if !ok || strings.TrimSpace(cfg.Host) == "" && strings.TrimSpace(cfg.Url) == "" {
g.Log().Infof(
ctx,
"WarningNotify email payload prepared, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d, subject:%s, body:%s",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
batch.EmailSubject,
batch.EmailBody,
)
return nil
}
if strings.TrimSpace(cfg.Url) != "" {
if err := callWarningEmailHTTP(ctx, cfg.UserCodeHttpSenderConfig, batch, recipients); err != nil {
return gerror.Wrap(err, "send warning email failed")
}
g.Log().Infof(
ctx,
"WarningNotify email sent by http, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d, subject:%s",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
batch.EmailSubject,
)
return nil
}
if cfg.Port <= 0 {
cfg.Port = 25
}
if strings.TrimSpace(cfg.From) == "" {
cfg.From = cfg.Username
}
if strings.TrimSpace(cfg.From) == "" {
return gerror.New("warning email sender is empty")
}
addr := net.JoinHostPort(cfg.Host, gconv.String(cfg.Port))
auth := smtp.Auth(nil)
if strings.TrimSpace(cfg.Username) != "" {
auth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
}
msg := buildWarningEmailMessage(cfg, batch, recipients)
err := utility.SendSMTP(ctx, utility.SMTPOptions{
Address: addr,
Host: cfg.Host,
Auth: auth,
From: cfg.From,
To: recipients,
Data: msg,
SSL: cfg.SSL,
Timeout: utility.ExternalRequestTimeout(cfg.Timeout),
})
if err != nil {
return gerror.Wrap(err, "send warning email failed")
}
g.Log().Infof(
ctx,
"WarningNotify email sent, business:%s, tenantId:%s, deptId:%d, category:%s, stage:%d, recipients:%s, count:%d, subject:%s",
batch.Business,
batch.TenantID,
batch.DeptID,
batch.Category,
batch.WarnStage,
strings.Join(recipients, ","),
len(batch.Items),
batch.EmailSubject,
)
return nil
}
func loadWarningEmailConfig(ctx context.Context) (model.UserCodeEmailSenderConfig, bool) {
var cfg model.UserCodeEmailSenderConfig
for _, key := range []string{"warning.email", "register.email"} {
v, err := g.Cfg().Get(ctx, key)
if err != nil || v.IsNil() {
continue
}
_ = v.Scan(&cfg)
if strings.TrimSpace(cfg.Host) != "" || strings.TrimSpace(cfg.Url) != "" {
return cfg, true
}
}
return cfg, false
}
func normalizeEmailRecipients(values []string) []string {
seen := make(map[string]struct{}, len(values))
recipients := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
address, err := mail.ParseAddress(value)
if err != nil || address == nil || strings.TrimSpace(address.Address) == "" {
continue
}
email := strings.ToLower(strings.TrimSpace(address.Address))
if _, ok := seen[email]; ok {
continue
}
seen[email] = struct{}{}
recipients = append(recipients, email)
}
return recipients
}
func buildWarningEmailMessage(cfg model.UserCodeEmailSenderConfig, batch Batch, recipients []string) []byte {
subject := strings.TrimSpace(batch.EmailSubject)
if subject == "" {
subject = buildEmailSubject(batch)
}
from := strings.TrimSpace(cfg.From)
if cfg.FromName != "" {
from = mime.QEncoding.Encode("UTF-8", cfg.FromName) + " <" + from + ">"
}
message := strings.Builder{}
message.WriteString("From: " + from + "\r\n")
message.WriteString("To: " + strings.Join(recipients, ", ") + "\r\n")
message.WriteString("Subject: " + mime.QEncoding.Encode("UTF-8", subject) + "\r\n")
message.WriteString("MIME-Version: 1.0\r\n")
message.WriteString("Content-Type: text/html; charset=UTF-8\r\n")
message.WriteString("\r\n")
message.WriteString(warningEmailHTMLBody(batch.EmailBody))
return []byte(message.String())
}
func warningEmailHTMLBody(body string) string {
body = strings.TrimSpace(body)
if body == "" {
body = defaultPlatformContent
}
if strings.Contains(body, warningEmailHTMLSignature) {
return warningEmailHTMLDocument(body)
}
escaped := html.EscapeString(body)
escaped = strings.ReplaceAll(escaped, "\r\n", "\n")
escaped = strings.ReplaceAll(escaped, "\n", "<br/>\n")
return warningEmailHTMLDocument(`<div style="font-family:Arial,'Microsoft YaHei',sans-serif;font-size:14px;line-height:1.8;color:#1f2937;">` +
escaped +
`</div>`)
}
func warningEmailHTMLDocument(body string) string {
return `<!doctype html><html><body>` + body + `</body></html>`
}
func callWarningEmailHTTP(ctx context.Context, cfg model.UserCodeHttpSenderConfig, batch Batch, recipients []string) error {
method := strings.ToUpper(strings.TrimSpace(cfg.Method))
if method == "" {
method = "POST"
}
successStatus := cfg.SuccessStatus
if successStatus <= 0 {
successStatus = 200
}
body := renderWarningEmailTemplate(cfg.BodyTemplate, batch, recipients)
if strings.TrimSpace(body) == "" {
bodyBytes, _ := json.Marshal(g.Map{
"subject": strings.TrimSpace(batch.EmailSubject),
"title": strings.TrimSpace(batch.EmailSubject),
"content": strings.TrimSpace(batch.EmailBody),
"body": strings.TrimSpace(batch.EmailBody),
"recipients": recipients,
"business": batch.Business,
"category": batch.Category,
"stage": batch.WarnStage,
})
body = string(bodyBytes)
}
headers := cfg.Headers
if headers == nil {
headers = map[string]string{}
}
if _, ok := headers["Content-Type"]; !ok {
headers["Content-Type"] = "application/json"
}
resp, err := g.Client().Timeout(utility.ExternalRequestTimeout(cfg.Timeout)).SetHeaderMap(headers).DoRequest(ctx, method, cfg.Url, body)
if err != nil {
return gerror.Wrap(err, "send warning email http request failed")
}
defer resp.Close()
if resp.StatusCode != successStatus {
return gerror.Newf("send warning email http request failed, status code: %d", resp.StatusCode)
}
return nil
}
func renderWarningEmailTemplate(template string, batch Batch, recipients []string) string {
template = strings.TrimSpace(template)
if template == "" {
return ""
}
replacer := strings.NewReplacer(
"{subject}", batch.EmailSubject,
"{{subject}}", batch.EmailSubject,
"{title}", batch.EmailSubject,
"{{title}}", batch.EmailSubject,
"{content}", batch.EmailBody,
"{{content}}", batch.EmailBody,
"{body}", batch.EmailBody,
"{{body}}", batch.EmailBody,
"{recipients}", strings.Join(recipients, ","),
"{{recipients}}", strings.Join(recipients, ","),
"{deptName}", "",
"{{deptName}}", "",
"{category}", batch.Category,
"{{category}}", batch.Category,
"{stage}", stageText(batch.WarnStage),
"{{stage}}", stageText(batch.WarnStage),
"{business}", batch.Business,
"{{business}}", batch.Business,
)
return replacer.Replace(template)
}