Files
MeBox/internal/service/notify_email.go
T

148 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package service — Email(SMTP) 通知 Provider。
package service
import (
"context"
"crypto/tls"
"fmt"
"net/mail"
"net/smtp"
"strconv"
"strings"
)
// EmailProvider 通过 SMTP 发送邮件通知。
type EmailProvider struct{}
// Send 通过 SMTP 发送邮件。
func (p *EmailProvider) Send(ctx context.Context, cfg map[string]string, event NotifyEvent) error {
smtpHost := cfg["smtp_host"]
smtpPortStr := cfg["smtp_port"]
username := cfg["username"]
password := cfg["password"]
from := cfg["from"]
to := cfg["to"]
tlsStr := cfg["tls"]
if smtpHost == "" || smtpPortStr == "" || username == "" || from == "" || to == "" {
return fmt.Errorf("email: smtp_host, smtp_port, username, from, and to are required")
}
smtpPort, err := strconv.Atoi(smtpPortStr)
if err != nil {
return fmt.Errorf("email: invalid smtp_port: %s", smtpPortStr)
}
useTLS := true
if tlsStr == "false" || tlsStr == "0" || tlsStr == "no" {
useTLS = false
}
// 构建邮件内容
subject := fmt.Sprintf("[MediaStationGo] %s", event.Title)
body := event.Message
if len(event.Data) > 0 {
body += "\n\n---\n详细信息:\n"
for k, v := range event.Data {
body += fmt.Sprintf(" %s: %v\n", k, v)
}
}
recipients := strings.Split(to, ",")
for i, r := range recipients {
recipients[i] = strings.TrimSpace(r)
}
// 构建邮件
fromAddr := mail.Address{Name: "MediaStationGo", Address: from}
toAddrs := make([]mail.Address, 0, len(recipients))
for _, r := range recipients {
toAddrs = append(toAddrs, mail.Address{Address: r})
}
msg := "From: " + fromAddr.String() + "\r\n"
msg += "To: "
for i, addr := range toAddrs {
if i > 0 {
msg += ", "
}
msg += addr.String()
}
msg += "\r\n"
msg += "Subject: " + subject + "\r\n"
msg += "MIME-Version: 1.0\r\n"
msg += "Content-Type: text/plain; charset=\"utf-8\"\r\n"
msg += "Content-Transfer-Encoding: base64\r\n"
msg += "\r\n"
msg += body
addr := fmt.Sprintf("%s:%d", smtpHost, smtpPort)
auth := smtp.PlainAuth("", username, password, smtpHost)
if useTLS {
// 使用 TLS 连接
tlsConfig := &tls.Config{
ServerName: smtpHost,
MinVersion: tls.VersionTLS12,
}
conn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
return fmt.Errorf("email tls dial: %w", err)
}
client, err := smtp.NewClient(conn, smtpHost)
if err != nil {
return fmt.Errorf("email smtp client: %w", err)
}
defer client.Close()
if err = client.Auth(auth); err != nil {
return fmt.Errorf("email auth: %w", err)
}
if err = client.Mail(from); err != nil {
return fmt.Errorf("email mail from: %w", err)
}
for _, r := range recipients {
if err = client.Rcpt(r); err != nil {
return fmt.Errorf("email rcpt to: %w", err)
}
}
w, err := client.Data()
if err != nil {
return fmt.Errorf("email data: %w", err)
}
if _, err = w.Write([]byte(msg)); err != nil {
return fmt.Errorf("email write: %w", err)
}
if err = w.Close(); err != nil {
return fmt.Errorf("email close: %w", err)
}
return client.Quit()
}
// 不使用 TLS(STARTTLS 或明文)
return smtp.SendMail(addr, auth, from, recipients, []byte(msg))
}
// ValidateConfig 验证 Email 配置。
func (p *EmailProvider) ValidateConfig(cfg map[string]string) error {
if cfg["smtp_host"] == "" {
return fmt.Errorf("email: smtp_host is required")
}
if cfg["smtp_port"] == "" {
return fmt.Errorf("email: smtp_port is required")
}
if cfg["username"] == "" {
return fmt.Errorf("email: username is required")
}
if cfg["from"] == "" {
return fmt.Errorf("email: from is required")
}
if cfg["to"] == "" {
return fmt.Errorf("email: to is required")
}
if _, err := strconv.Atoi(cfg["smtp_port"]); err != nil {
return fmt.Errorf("email: invalid smtp_port")
}
return nil
}