go-platform/services/system_email_smtp.go
2026-04-01 15:15:21 +08:00

127 lines
3.5 KiB
Go
Raw Permalink 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 services
import (
"crypto/tls"
"fmt"
"net"
"net/smtp"
"strconv"
"strings"
"time"
)
// SMTPConfig 发送邮件所需参数(与 yz_system_email 字段对应)
type SMTPConfig struct {
FromAddress string
FromName string
Host string
Port uint
Password string
Encryption string // ssl / tls / none
Timeout uint // 秒
}
// SendTestEmailSMTP 发送一封简单测试邮件(纯文本 UTF-8
func SendTestEmailSMTP(cfg SMTPConfig, to string) error {
to = strings.TrimSpace(to)
if to == "" {
return fmt.Errorf("收件人不能为空")
}
if cfg.Host == "" || cfg.FromAddress == "" {
return fmt.Errorf("SMTP 主机或发件人不能为空")
}
if cfg.Port == 0 {
cfg.Port = 465
}
timeout := cfg.Timeout
if timeout == 0 {
timeout = 30
}
d := net.Dialer{Timeout: time.Duration(timeout) * time.Second}
addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10))
enc := strings.ToLower(strings.TrimSpace(cfg.Encryption))
if enc == "" {
enc = "ssl"
}
var client *smtp.Client
var err error
switch enc {
case "ssl":
conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12})
if derr != nil {
return fmt.Errorf("连接 SMTP 失败: %w", derr)
}
defer conn.Close()
client, err = smtp.NewClient(conn, cfg.Host)
if err != nil {
return fmt.Errorf("SMTP 握手失败: %w", err)
}
case "tls":
conn, derr := d.Dial("tcp", addr)
if derr != nil {
return fmt.Errorf("连接 SMTP 失败: %w", derr)
}
defer conn.Close()
client, err = smtp.NewClient(conn, cfg.Host)
if err != nil {
return fmt.Errorf("SMTP 握手失败: %w", err)
}
if ok, _ := client.Extension("STARTTLS"); ok {
if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil {
_ = client.Close()
return fmt.Errorf("STARTTLS 失败: %w", err)
}
}
case "none":
conn, derr := d.Dial("tcp", addr)
if derr != nil {
return fmt.Errorf("连接 SMTP 失败: %w", derr)
}
defer conn.Close()
client, err = smtp.NewClient(conn, cfg.Host)
if err != nil {
return fmt.Errorf("SMTP 握手失败: %w", err)
}
default:
return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption)
}
defer func() { _ = client.Close() }()
auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host)
if err = client.Auth(auth); err != nil {
return fmt.Errorf("SMTP 认证失败: %w", err)
}
if err = client.Mail(cfg.FromAddress); err != nil {
return fmt.Errorf("MAIL FROM 失败: %w", err)
}
if err = client.Rcpt(to); err != nil {
return fmt.Errorf("RCPT TO 失败: %w", err)
}
wc, err := client.Data()
if err != nil {
return fmt.Errorf("DATA 失败: %w", err)
}
fromName := strings.TrimSpace(cfg.FromName)
subject := "平台邮箱测试"
body := "这是一封来自管理后台「邮箱管理」的测试邮件。\r\nThis is a test email from the platform email settings.\r\n"
headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n",
formatFromHeader(fromName, cfg.FromAddress), to, subject)
if _, err = wc.Write([]byte(headers + body)); err != nil {
return fmt.Errorf("写入邮件内容失败: %w", err)
}
if err = wc.Close(); err != nil {
return fmt.Errorf("结束 DATA 失败: %w", err)
}
return client.Quit()
}
func formatFromHeader(name, addr string) string {
name = strings.TrimSpace(name)
if name == "" {
return addr
}
return fmt.Sprintf("%s <%s>", name, addr)
}