From a6ba78c92c8a55a8a62b0a3f76eef480f47cab6b Mon Sep 17 00:00:00 2001 From: 4566704 <4566704@qq.com> Date: Sun, 20 Sep 2026 12:29:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(mail):=20=E8=87=AA=20go-hua=20=E8=BF=81?= =?UTF-8?q?=E5=85=A5=20SMTP=20=E9=82=AE=E4=BB=B6=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SendToMail 支持收/抄/密送与回复地址、HTML 或文本正文 - 附测试、examples/mail 例程与包 README(注明不支持 465 加密端口) --- examples/mail/main.go | 37 +++++++++++++++++++++++++++++++++++++ mail/README.md | 34 ++++++++++++++++++++++++++++++++++ mail/mail.go | 40 ++++++++++++++++++++++++++++++++++++++++ mail/mail_test.go | 22 ++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 examples/mail/main.go create mode 100644 mail/README.md create mode 100644 mail/mail.go create mode 100644 mail/mail_test.go diff --git a/examples/mail/main.go b/examples/mail/main.go new file mode 100644 index 0000000..b58cf74 --- /dev/null +++ b/examples/mail/main.go @@ -0,0 +1,37 @@ +// mail 包示例:SMTP 发送邮件(SendToMail)与收件人列表合并(MergeSlice) +package main + +import ( + "fmt" + + "git.zeroonesoft.cn/golib/zogo/mail" +) + +func main() { + // MergeSlice:合并收件人列表(to + cc + bcc 一次性提交给 SMTP) + merged := mail.MergeSlice([]string{"a@test.com", "b@test.com"}, []string{"c@test.com"}) + fmt.Println("合并收件人:", merged) + + // SendToMail 参数说明: + // user SMTP 账号 + // password SMTP 密码/授权码 + // host SMTP 服务器,如 "smtp.example.com:25" + // from 发件人地址 + // subject 邮件主题 + // body 邮件正文 + // mailType "html" 为 HTML 邮件,其他值为纯文本 + // replyToAddress 回复地址 + // to / cc / bcc 收件人 / 抄送 / 密送列表 + // + // 填入真实 SMTP 账号后调用即可发送: + // + // err := mail.SendToMail( + // "user@example.com", "password", "smtp.example.com:25", + // "user@example.com", "测试邮件", "

hello

", "html", + // "reply@example.com", + // []string{"to@example.com"}, []string{"cc@example.com"}, []string{}, + // ) + // if err != nil { + // fmt.Println("发送失败:", err) + // } +} diff --git a/mail/README.md b/mail/README.md new file mode 100644 index 0000000..d1c7736 --- /dev/null +++ b/mail/README.md @@ -0,0 +1,34 @@ +# mail + +SMTP 邮件发送封装:单函数 `SendToMail` 支持收件人/抄送/密送/回复地址, +`mailType` 选择 HTML 或纯文本正文。 + +> 迁移自 go-hua/mail,代码保持原样。 + +## 用法 + +```go +import "git.zeroonesoft.cn/golib/zogo/mail" + +err := mail.SendToMail( + "user@example.com", // SMTP 用户名 + "password-or-auth-code", // 密码/授权码 + "smtp.example.com:25", // SMTP 服务器(host:port) + "user@example.com", // 发件人 + "告警通知", // 主题 + "CPU 超限", // 正文 + "html", // "html" 或其他(纯文本) + "reply@example.com", // Reply-To + []string{"a@example.com"}, // 收件人 + []string{"b@example.com"}, // 抄送 + nil, // 密送 +) +``` + +完整可运行例程:[examples/mail/main.go](../examples/mail/main.go) + +## 注意 + +- 不支持 SSL/TLS 端口(465),走明文 SMTP 或 STARTTLS 由服务器协商;现在多数 + 公网邮箱要求授权码 + 加密端口,此包更适合内网 SMTP 中继。 +- 邮件头未做 RFC 2047 编码,中文主题请确认服务器兼容(内网中继一般没问题)。 diff --git a/mail/mail.go b/mail/mail.go new file mode 100644 index 0000000..a1023ad --- /dev/null +++ b/mail/mail.go @@ -0,0 +1,40 @@ +// Package mail 封装 SMTP 邮件发送。 +package mail + +import ( + "net/smtp" + "strings" +) + +// SendToMail 通过 SMTP 发送邮件 +// user/password 为 SMTP 账号与密码(或授权码);host 如 "smtp.example.com:25" +// mailType 为 "html" 时正文按 HTML 发送,其他值按纯文本发送 +// to/cc/bcc 分别为收件人/抄送/密送列表 +func SendToMail(user, password, host, from, subject, body, mailType, replyToAddress string, to, cc, bcc []string) error { + hp := strings.Split(host, ":") + auth := smtp.PlainAuth("", user, password, hp[0]) + var contentType string + if mailType == "html" { + contentType = "Content-Type: text/" + mailType + "; charset=UTF-8" + } else { + contentType = "Content-Type: text/plain" + "; charset=UTF-8" + } + + ccAddress := strings.Join(cc, ";") + bccAddress := strings.Join(bcc, ";") + toAddress := strings.Join(to, ";") + msg := []byte("To: " + toAddress + "\r\nFrom: " + from + "\r\nSubject: " + subject + "\r\nReply-To: " + replyToAddress + "\r\nCc: " + ccAddress + "\r\nBcc: " + bccAddress + "\r\n" + contentType + "\r\n\r\n" + body) + + sendTo := MergeSlice(to, cc) + sendTo = MergeSlice(sendTo, bcc) + err := smtp.SendMail(host, auth, user, sendTo, msg) + return err +} + +// MergeSlice 合并两个字符串切片(用于合并收件人列表) +func MergeSlice(s1 []string, s2 []string) []string { + slice := make([]string, len(s1)+len(s2)) + copy(slice, s1) + copy(slice[len(s1):], s2) + return slice +} diff --git a/mail/mail_test.go b/mail/mail_test.go new file mode 100644 index 0000000..110eb2c --- /dev/null +++ b/mail/mail_test.go @@ -0,0 +1,22 @@ +package mail + +import "testing" + +func TestMergeSlice(t *testing.T) { + got := MergeSlice([]string{"a@t.com", "b@t.com"}, []string{"c@t.com"}) + if len(got) != 3 { + t.Fatalf("MergeSlice 长度 = %d, want 3", len(got)) + } + want := map[string]bool{"a@t.com": true, "b@t.com": true, "c@t.com": true} + for _, s := range got { + if !want[s] { + t.Errorf("MergeSlice 结果包含意外元素 %q", s) + } + } +} + +func TestMergeSliceEmpty(t *testing.T) { + if got := MergeSlice(nil, nil); len(got) != 0 { + t.Errorf("空输入 MergeSlice = %v, want 空", got) + } +}