初始版本,工具基础类

This commit is contained in:
2025-11-30 13:02:34 +08:00
commit ea4e2e305d
37 changed files with 7480 additions and 0 deletions

306
email/email.go Normal file
View File

@@ -0,0 +1,306 @@
package email
import (
"bytes"
"crypto/tls"
"fmt"
"net"
"net/smtp"
"time"
"github.com/go-common/config"
)
// Email 邮件发送器
type Email struct {
config *config.EmailConfig
}
// NewEmail 创建邮件发送器
func NewEmail(cfg *config.EmailConfig) (*Email, error) {
if cfg == nil {
return nil, fmt.Errorf("email config is nil")
}
if cfg.Host == "" {
return nil, fmt.Errorf("email host is required")
}
if cfg.Username == "" {
return nil, fmt.Errorf("email username is required")
}
if cfg.Password == "" {
return nil, fmt.Errorf("email password is required")
}
// 设置默认值
if cfg.Port == 0 {
cfg.Port = 587
}
if cfg.From == "" {
cfg.From = cfg.Username
}
if cfg.Timeout == 0 {
cfg.Timeout = 30
}
return &Email{
config: cfg,
}, nil
}
// Message 邮件消息
type Message struct {
// To 收件人列表
To []string
// Cc 抄送列表(可选)
Cc []string
// Bcc 密送列表(可选)
Bcc []string
// Subject 主题
Subject string
// Body 正文(纯文本)
Body string
// HTMLBody HTML正文可选如果设置了会优先使用
HTMLBody string
// Attachments 附件列表(可选)
Attachments []Attachment
}
// Attachment 附件
type Attachment struct {
// Filename 文件名
Filename string
// Content 文件内容
Content []byte
// ContentType 文件类型application/pdf
ContentType string
}
// SendRaw 发送原始邮件内容
// recipients: 收件人列表To、Cc、Bcc的合并列表
// body: 完整的邮件内容MIME格式由外部构建
func (e *Email) SendRaw(recipients []string, body []byte) error {
if len(recipients) == 0 {
return fmt.Errorf("recipients are required")
}
if len(body) == 0 {
return fmt.Errorf("email body is required")
}
// 连接SMTP服务器
addr := fmt.Sprintf("%s:%d", e.config.Host, e.config.Port)
auth := smtp.PlainAuth("", e.config.Username, e.config.Password, e.config.Host)
// 创建连接
conn, err := net.DialTimeout("tcp", addr, time.Duration(e.config.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %w", err)
}
defer conn.Close()
// 创建SMTP客户端
client, err := smtp.NewClient(conn, e.config.Host)
if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err)
}
defer client.Close()
// TLS/SSL处理
if e.config.UseSSL {
// SSL模式端口通常是465
tlsConfig := &tls.Config{
ServerName: e.config.Host,
}
if err := client.StartTLS(tlsConfig); err != nil {
return fmt.Errorf("failed to start TLS: %w", err)
}
} else if e.config.UseTLS {
// TLS模式STARTTLS端口通常是587
tlsConfig := &tls.Config{
ServerName: e.config.Host,
}
if err := client.StartTLS(tlsConfig); err != nil {
return fmt.Errorf("failed to start TLS: %w", err)
}
}
// 认证
if err := client.Auth(auth); err != nil {
return fmt.Errorf("failed to authenticate: %w", err)
}
// 设置发件人
if err := client.Mail(e.config.From); err != nil {
return fmt.Errorf("failed to set sender: %w", err)
}
// 设置收件人
for _, to := range recipients {
if err := client.Rcpt(to); err != nil {
return fmt.Errorf("failed to set recipient %s: %w", to, err)
}
}
// 发送邮件内容
writer, err := client.Data()
if err != nil {
return fmt.Errorf("failed to get data writer: %w", err)
}
_, err = writer.Write(body)
if err != nil {
writer.Close()
return fmt.Errorf("failed to write email body: %w", err)
}
err = writer.Close()
if err != nil {
return fmt.Errorf("failed to close writer: %w", err)
}
// 退出
if err := client.Quit(); err != nil {
return fmt.Errorf("failed to quit: %w", err)
}
return nil
}
// Send 发送邮件使用Message结构内部会构建邮件内容
// 注意如果需要完全控制邮件内容请使用SendRaw方法
func (e *Email) Send(msg *Message) error {
if msg == nil {
return fmt.Errorf("message is nil")
}
if len(msg.To) == 0 {
return fmt.Errorf("recipients are required")
}
if msg.Subject == "" {
return fmt.Errorf("subject is required")
}
if msg.Body == "" && msg.HTMLBody == "" {
return fmt.Errorf("body or HTMLBody is required")
}
// 构建邮件内容
emailBody, err := e.buildEmailBody(msg)
if err != nil {
return fmt.Errorf("failed to build email body: %w", err)
}
// 合并收件人列表
recipients := append(msg.To, msg.Cc...)
recipients = append(recipients, msg.Bcc...)
// 使用SendRaw发送
return e.SendRaw(recipients, emailBody)
}
// buildEmailBody 构建邮件内容
func (e *Email) buildEmailBody(msg *Message) ([]byte, error) {
var buf bytes.Buffer
// 邮件头
from := e.config.From
if e.config.FromName != "" {
from = fmt.Sprintf("%s <%s>", e.config.FromName, e.config.From)
}
buf.WriteString(fmt.Sprintf("From: %s\r\n", from))
// 收件人
buf.WriteString(fmt.Sprintf("To: %s\r\n", joinEmails(msg.To)))
// 抄送
if len(msg.Cc) > 0 {
buf.WriteString(fmt.Sprintf("Cc: %s\r\n", joinEmails(msg.Cc)))
}
// 主题
buf.WriteString(fmt.Sprintf("Subject: %s\r\n", msg.Subject))
// 内容类型
if msg.HTMLBody != "" {
// 多部分邮件HTML + 纯文本)
boundary := "----=_Part_" + fmt.Sprint(time.Now().UnixNano())
buf.WriteString("MIME-Version: 1.0\r\n")
buf.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=\"%s\"\r\n", boundary))
buf.WriteString("\r\n")
// 纯文本部分
buf.WriteString("--" + boundary + "\r\n")
buf.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
buf.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
buf.WriteString("\r\n")
buf.WriteString(msg.Body)
buf.WriteString("\r\n")
// HTML部分
buf.WriteString("--" + boundary + "\r\n")
buf.WriteString("Content-Type: text/html; charset=UTF-8\r\n")
buf.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
buf.WriteString("\r\n")
buf.WriteString(msg.HTMLBody)
buf.WriteString("\r\n")
buf.WriteString("--" + boundary + "--\r\n")
} else {
// 纯文本邮件
buf.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
buf.WriteString("Content-Transfer-Encoding: quoted-printable\r\n")
buf.WriteString("\r\n")
buf.WriteString(msg.Body)
buf.WriteString("\r\n")
}
return buf.Bytes(), nil
}
// joinEmails 连接邮箱地址
func joinEmails(emails []string) string {
if len(emails) == 0 {
return ""
}
result := emails[0]
for i := 1; i < len(emails); i++ {
result += ", " + emails[i]
}
return result
}
// SendSimple 发送简单邮件(便捷方法)
// to: 收件人
// subject: 主题
// body: 正文
func (e *Email) SendSimple(to []string, subject, body string) error {
return e.Send(&Message{
To: to,
Subject: subject,
Body: body,
})
}
// SendHTML 发送HTML邮件便捷方法
// to: 收件人
// subject: 主题
// htmlBody: HTML正文
func (e *Email) SendHTML(to []string, subject, htmlBody string) error {
return e.Send(&Message{
To: to,
Subject: subject,
HTMLBody: htmlBody,
})
}