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) }