270 lines
7.6 KiB
Go
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)
|
|
}
|