From 1f07fae1c2cd4950ef2a1aabd02640d75ca4b3e7 Mon Sep 17 00:00:00 2001 From: chengqian Date: Mon, 31 Aug 2026 15:59:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(email):=E4=BF=AE=E5=A4=8D=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/logic/common/email.go | 23 +++++++++++++++++-- internal/logic/common/settings_registry.go | 2 ++ internal/logic/monitor/alert_notify.go | 17 +++++++------- .../src/views/system/settings/EmailTab.vue | 3 +++ 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/internal/logic/common/email.go b/internal/logic/common/email.go index 454d9a3..1a9f7d7 100644 --- a/internal/logic/common/email.go +++ b/internal/logic/common/email.go @@ -4,11 +4,13 @@ import ( "bytes" "context" "crypto/tls" - "github.com/qianfree/team-api/internal/dao" "html/template" + "net/mail" "strings" "time" + "github.com/qianfree/team-api/internal/dao" + "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" @@ -23,6 +25,7 @@ type EmailConfig struct { Username string Password string From string + FromName string // 发件人显示名,收件人邮件列表里看到的名字;为空时只显示邮箱地址 UseTLS bool } @@ -121,7 +124,17 @@ func (s *BasicEmailSender) SendTemplate(ctx context.Context, to, templateCode st // sendWithRetry performs the actual SMTP send. func (s *BasicEmailSender) sendWithRetry(ctx context.Context, msg *EmailMessage, attempt int) error { m := gomail.NewMessage() - m.SetHeader("From", s.config.From) + // 发件人显示名:SetAddressHeader 会按 RFC 5322 拼成 "aifree" , + // 名称含中文时自动做 RFC 2047 编码;FromName 为空时退化为纯地址。 + // From 若已配成 "名称 <地址>" 形式,先拆出地址避免二次包装。 + fromAddr, fromName := s.config.From, s.config.FromName + if parsed, err := mail.ParseAddress(fromAddr); err == nil { + fromAddr = parsed.Address + if fromName == "" { + fromName = parsed.Name + } + } + m.SetAddressHeader("From", fromAddr, fromName) m.SetHeader("To", msg.To) m.SetHeader("Subject", msg.Subject) @@ -205,6 +218,7 @@ func EmailConfigFromOptions(ctx context.Context) (*EmailConfig, error) { Username: Config().GetString(ctx, "email_smtp_username"), Password: Config().GetString(ctx, "email_smtp_password"), From: Config().GetString(ctx, "email_smtp_from"), + FromName: Config().GetString(ctx, "email_smtp_from_name"), UseTLS: Config().GetBool(ctx, "email_smtp_tls"), } @@ -212,6 +226,11 @@ func EmailConfigFromOptions(ctx context.Context) (*EmailConfig, error) { return nil, gerror.New("email SMTP config not set (email_smtp_host, email_smtp_from)") } + // 未单独配置发件人名称时回落到站点名称,避免收件人只看到邮箱地址的本地部分 + if cfg.FromName == "" { + cfg.FromName = Config().GetString(ctx, "site_name") + } + if cfg.Port == 0 { cfg.Port = 587 } diff --git a/internal/logic/common/settings_registry.go b/internal/logic/common/settings_registry.go index 128f1bc..ccd25c1 100644 --- a/internal/logic/common/settings_registry.go +++ b/internal/logic/common/settings_registry.go @@ -107,6 +107,8 @@ var Registry = []SettingDef{ Label: "SMTP 密码", Sensitive: true}, {Key: "email_smtp_from", Type: SettingTypeString, Default: "", Category: "email", Label: "发件人地址", Description: "格式: noreply@example.com"}, + {Key: "email_smtp_from_name", Type: SettingTypeString, Default: "", Category: "email", + Label: "发件人名称", Description: "收件人看到的发件人显示名,如 aifree;留空则使用站点名称"}, {Key: "email_smtp_tls", Type: SettingTypeBool, Default: "true", Category: "email", Label: "启用 TLS", Description: "587/465 端口建议开启"}, diff --git a/internal/logic/monitor/alert_notify.go b/internal/logic/monitor/alert_notify.go index ed384c4..9525647 100644 --- a/internal/logic/monitor/alert_notify.go +++ b/internal/logic/monitor/alert_notify.go @@ -95,15 +95,14 @@ func dispatchAlertNotifications(ctx context.Context, rule map[string]any, eventI // sendAlertEmailToAdmins sends alert email to specified admin users. func sendAlertEmailToAdmins(ctx context.Context, adminIDs []int64, subject, body string) { - // Create email sender from config - sender := common.NewEmailSender(&common.EmailConfig{ - Host: g.Cfg().MustGet(ctx, "email.smtp.host").String(), - Port: g.Cfg().MustGet(ctx, "email.smtp.port").Int(), - Username: g.Cfg().MustGet(ctx, "email.smtp.username").String(), - Password: g.Cfg().MustGet(ctx, "email.smtp.password").String(), - From: g.Cfg().MustGet(ctx, "email.smtp.from").String(), - UseTLS: g.Cfg().MustGet(ctx, "email.smtp.port").Int() == 587 || g.Cfg().MustGet(ctx, "email.smtp.port").Int() == 465, - }) + // 与其余邮件链路统一走设置注册表(管理后台「邮件配置」), + // 而非 config.yaml 的 email.smtp.*(该配置段不存在,会读出空值导致告警邮件必然发送失败) + cfg, err := common.EmailConfigFromOptions(ctx) + if err != nil { + g.Log().Warningf(ctx, "send alert email: %v", err) + return + } + sender := common.NewEmailSender(cfg) for _, adminID := range adminIDs { type adminUser struct { diff --git a/web/admin/src/views/system/settings/EmailTab.vue b/web/admin/src/views/system/settings/EmailTab.vue index 18461e7..a47ba5c 100644 --- a/web/admin/src/views/system/settings/EmailTab.vue +++ b/web/admin/src/views/system/settings/EmailTab.vue @@ -37,6 +37,9 @@ const values = useFormValues() + + +
启用 TLS 587/465 端口建议开启