+ From dc49e979125ed33cc0f20f3f7c6fdaa5b622236b Mon Sep 17 00:00:00 2001 From: Yun <995116474@qq.com> Date: Wed, 20 Nov 2024 19:42:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=A4=9A=E4=B8=AA=E9=80=9A?= =?UTF-8?q?=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aliyun/aliyun.go | 250 +++++++++++++++++++++++++ aliyun/aliyun_test.go | 82 +++++++++ aliyun/assets/zh_hant.html | 160 ++++++++++++++++ aliyun/log/2024-09-25_info.log | 11 ++ aliyun/log/2024-09-27_error.log | 2 + aliyun/log/2024-09-27_info.log | 9 + aliyun/log/2024-09-29_error.log | 3 + aliyun/log/2024-09-29_info.log | 7 + aliyun/log/2024-09-30_info.log | 4 + aws/aws.go | 82 +++++++++ aws/aws_test.go | 52 ++++++ aws/log/2024-09-27_info.log | 3 + aws/log/2024-09-30_info.log | 3 + go.mod | 57 +++++- go.sum | 286 +++++++++++++++++++++++++++++ html.go | 2 +- html_test.go | 3 +- interfaces/interfaces.go | 96 ++++++++++ mailgun/log/2024-09-30_error.log | 2 + mailgun/log/2024-09-30_info.log | 2 + mailgun/mailgun.go | 46 +++++ mailgun/mailgun_test.go | 45 +++++ mailx.go | 205 ++++----------------- old/asset/hhh.txt | 1 + old/asset/余额宝.png | Bin 0 -> 39128 bytes old/html.go | 77 ++++++++ old/html_test.go | 60 ++++++ old/mailx.go | 202 ++++++++++++++++++++ mailx_test.go => old/mailx_test.go | 22 +-- smtp/log/2024-09-27_info.log | 3 + smtp/log/2024-09-30_info.log | 2 + smtp/smtp.go | 177 ++++++++++++++++++ smtp/smtp_test.go | 96 ++++++++++ 33 files changed, 1861 insertions(+), 191 deletions(-) create mode 100644 aliyun/aliyun.go create mode 100644 aliyun/aliyun_test.go create mode 100644 aliyun/assets/zh_hant.html create mode 100644 aliyun/log/2024-09-25_info.log create mode 100644 aliyun/log/2024-09-27_error.log create mode 100644 aliyun/log/2024-09-27_info.log create mode 100644 aliyun/log/2024-09-29_error.log create mode 100644 aliyun/log/2024-09-29_info.log create mode 100644 aliyun/log/2024-09-30_info.log create mode 100644 aws/aws.go create mode 100644 aws/aws_test.go create mode 100644 aws/log/2024-09-27_info.log create mode 100644 aws/log/2024-09-30_info.log create mode 100644 interfaces/interfaces.go create mode 100644 mailgun/log/2024-09-30_error.log create mode 100644 mailgun/log/2024-09-30_info.log create mode 100644 mailgun/mailgun.go create mode 100644 mailgun/mailgun_test.go create mode 100644 old/asset/hhh.txt create mode 100644 old/asset/余额宝.png create mode 100644 old/html.go create mode 100644 old/html_test.go create mode 100644 old/mailx.go rename mailx_test.go => old/mailx_test.go (75%) create mode 100644 smtp/log/2024-09-27_info.log create mode 100644 smtp/log/2024-09-30_info.log create mode 100644 smtp/smtp.go create mode 100644 smtp/smtp_test.go diff --git a/aliyun/aliyun.go b/aliyun/aliyun.go new file mode 100644 index 0000000..52d406b --- /dev/null +++ b/aliyun/aliyun.go @@ -0,0 +1,250 @@ +package aliyun + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "code.yun.ink/pkg/mailx/interfaces" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + dm20151123 "github.com/alibabacloud-go/dm-20151123/v2/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/yuninks/loggerx" +) + +type Aliyun struct { + interfaces.DefaultEmail + client *dm20151123.Client + params *interfaces.EmialConfigDataAliyun + logger loggerx.LoggerInterface +} + +func (l *Aliyun) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) { + l.logger.Infof(ctx, "params:%+v", params) + if params.Aliyun == nil { + return nil, errors.New("not aliyun") + } + l.logger.Infof(ctx, "params:%+v", params.Aliyun) + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html。 + config := &openapi.Config{ + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + AccessKeyId: tea.String(params.Aliyun.AccessId), + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + AccessKeySecret: tea.String(params.Aliyun.AccessKey), + } + if params.Aliyun.Endpoint == "" { + params.Aliyun.Endpoint = "dm.aliyuncs.com" + } + + // Endpoint 请参考 https://api.aliyun.com/product/Dm + config.Endpoint = tea.String(params.Aliyun.Endpoint) + + result, err := dm20151123.NewClient(config) + if err != nil { + return nil, err + } + + return &Aliyun{ + client: result, + params: params.Aliyun, + logger: logger, + }, nil +} + +func (l *Aliyun) Send(ctx context.Context, params interfaces.Message) error { + if l.client == nil { + return errors.New("client no init") + } + if len(params.To) > 100 { + return errors.New("最多 100 个地址") + } + if l.params.AccountName == "" { + return errors.New("AccountName 必填") + } + + toAddress := strings.Join(params.To, ",") + + singleSendMailRequest := &dm20151123.SingleSendMailRequest{} + + singleSendMailRequest.AccountName = tea.String(l.params.AccountName) + singleSendMailRequest.ToAddress = tea.String(toAddress) // 目标地址,多个 email 地址可以用逗号分隔,最多 100 个地址(支持邮件组)。 + singleSendMailRequest.Subject = tea.String(params.Subject) + singleSendMailRequest.HtmlBody = tea.String(params.Body) + singleSendMailRequest.AddressType = tea.Int32(0) // 地址类型。取值:0:为随机账号1:为发信地址 + + if params.ReplyTo != "" { + singleSendMailRequest.ReplyToAddress = tea.Bool(false) + singleSendMailRequest.ReplyAddress = tea.String(params.ReplyTo) + } else { + singleSendMailRequest.ReplyToAddress = tea.Bool(true) + } + + runtime := &util.RuntimeOptions{} + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + // 复制代码运行请自行打印 API 的返回值 + resp, err := l.client.SingleSendMailWithOptions(singleSendMailRequest, runtime) + by, _ := json.Marshal(resp) + fmt.Printf("resp:%+v err:%+v", string(by), err) + l.logger.Infof(ctx, "resp:%+v err:%+v", resp, err) + + if err != nil { + return err + } + return nil + }() + + if tryErr != nil { + l.logger.Errorf(ctx, "err:%+v", tryErr) + return tryErr + + // var error = &tea.SDKError{} + // if _t, ok := tryErr.(*tea.SDKError); ok { + // error = _t + // } else { + // error.Message = tea.String(tryErr.Error()) + // } + // // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // // 错误 message + // fmt.Println(tea.StringValue(error.Message)) + // // 诊断地址 + // var data interface{} + // d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data))) + // d.Decode(&data) + // if m, ok := data.(map[string]interface{}); ok { + // recommend, _ := m["Recommend"] + // fmt.Println("recommend", recommend) + // } + // _, _err := util.AssertAsString(error.Message) + // if _err != nil { + // return _err + // } + } + + return nil // 实现具体的 Aliyun 发送方法 + // 如:return aliyunSDK.SendMail(params) +} + +// 同步状态 +func (l *Aliyun) SyncStatus(ctx context.Context) (resp []interfaces.EmailSendRecord, err error) { + + start := "" + + // 一次同步一天的数据 + for { + list, next, err := l.getSendStatus(ctx, start) + l.logger.Infof(ctx, "list:%+v next:%+v err:%+v", list, next, err) + if err != nil { + return nil, err + } + + for _, val := range list { + + t, _ := time.ParseInLocation("2006-01-02T15:04Z", tea.StringValue(val.LastUpdateTime), time.Local) + + // 0:成功 2:无效地址 3:垃圾邮件 4:失败 + record := interfaces.EmailSendRecord{ + AccountName: tea.StringValue(val.AccountName), + UpdateTime: t.UnixMilli(), + ToUser: tea.StringValue(val.ToAddress), + Subject: tea.StringValue(val.Subject), + ErrorMessage: tea.StringValue(val.Message), + } + + switch tea.Int32Value(val.Status) { + case 0: + record.Status = interfaces.EmailSendStatusSuccess + case 2: + record.Status = interfaces.EmailSendStatusInvalidAddress + case 3: + record.Status = interfaces.EmailSendStatusSpam + case 4: + record.Status = interfaces.EmailSendStatusFailed + default: + record.Status = interfaces.EmailSendStatusUnknown + } + + resp = append(resp, record) + } + if next == nil || len(*next) == 0 { + break + } + start = *next + } + + return resp, nil + +} + +func (l *Aliyun) getSendStatus(ctx context.Context, start string) (list []*dm20151123.SenderStatisticsDetailByParamResponseBodyDataMailDetail, nextStart *string, err error) { + now := time.Now().Local() + senderStatisticsDetailByParamRequest := &dm20151123.SenderStatisticsDetailByParamRequest{ + StartTime: tea.String(now.AddDate(0, 0, -1).Format("2006-01-02 15:04")), + EndTime: tea.String(now.Format("2006-01-02 15:04")), + Length: tea.Int32(100), + } + if start != "" { + senderStatisticsDetailByParamRequest.NextStart = tea.String(start) + } + runtime := &util.RuntimeOptions{} + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + // 复制代码运行请自行打印 API 的返回值 + resp, _err := l.client.SenderStatisticsDetailByParamWithOptions(senderStatisticsDetailByParamRequest, runtime) + if _err != nil { + l.logger.Errorf(ctx, "resp:%+v err:%+v", resp, _err) + return _err + } + + if resp == nil || resp.Body == nil || resp.Body.Data == nil { + return errors.New("resp.Body.Data is nil") + } + + list = resp.Body.Data.MailDetail + nextStart = resp.Body.NextStart + + return nil + }() + + if tryErr != nil { + l.logger.Errorf(ctx, "err:%+v", tryErr) + return nil, nil, tryErr + + // var error = &tea.SDKError{} + // if _t, ok := tryErr.(*tea.SDKError); ok { + // error = _t + // } else { + // error.Message = tea.String(tryErr.Error()) + // } + // // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // // 错误 message + // fmt.Println(tea.StringValue(error.Message)) + // // 诊断地址 + // var data interface{} + // d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data))) + // d.Decode(&data) + // if m, ok := data.(map[string]interface{}); ok { + // recommend, _ := m["Recommend"] + // fmt.Println("recommend:", recommend) + // } + // _, _err := util.AssertAsString(error.Message) + // if _err != nil { + // l.logger.Errorf(ctx, "resp:%+v err:%+v", error.Message, _err) + // return nil, nil, _err + // } + } + return list, nextStart, nil +} diff --git a/aliyun/aliyun_test.go b/aliyun/aliyun_test.go new file mode 100644 index 0000000..d273444 --- /dev/null +++ b/aliyun/aliyun_test.go @@ -0,0 +1,82 @@ +package aliyun_test + +import ( + "context" + "fmt" + "os" + "testing" + + "code.yun.ink/pkg/mailx/aliyun" + "code.yun.ink/pkg/mailx/interfaces" + "github.com/yuninks/loggerx" +) + +func TestSend(t *testing.T) { + aliyun := &aliyun.Aliyun{} + ctx := context.Background() + + logger := loggerx.NewLogger(ctx) + + ali, err := aliyun.InitEmail(ctx, interfaces.EmailConfigData{ + Aliyun: &interfaces.EmialConfigDataAliyun{ + AccessId: "LTAI5tEQ8L8fmDir8udD3CFr", + AccessKey: "llg9M1U56s2SW5PuerlKPvTB1xYhn0", + Endpoint: "dm.aliyuncs.com", + AccountName: "test@email.aisz.org", //"test@email.aisz.org", + ReplyAddress: "287852692@qq.com", + }, + }, logger) + if err != nil { + t.Fatal(err) + } + + by, err := os.ReadFile("../../../static/email/msg/zh_Hant.html") + if err != nil { + t.Fatal(err) + } + + fmt.Println(string(by)) + + err = ali.Send(ctx, interfaces.Message{ + To: []string{"huangxinyun@dreaminglife.cn"}, + Subject: "测试主题", + Body: string(by), + }) + if err != nil { + t.Fatal("resp err", err) + } + + t.Log("send success") +} + +// func TestSyncStatus(t *testing.T) { +// aliyun := &aliyun.Aliyun{} +// ctx := context.Background() + +// global.Logger = loggerx.NewLogger(ctx) + +// ali, err := aliyun.InitEmail(ctx, interfaces.EmailConfigData{ +// Aliyun: &interfaces.EmialConfigDataAliyun{ +// AccessId: "LTAI5tEQ8L8fmDir8udD3CFr", +// AccessKey: "llg9M1U56s2SW5PuerlKPvTB1xYhn0", +// Endpoint: "dm.aliyuncs.com", +// AccountName: "test@email.aisz.org", +// ReplyAddress: "287852692@qq.com", +// }, +// }) +// if err != nil { +// t.Fatal(err) +// } + +// list, err := ali.SyncStatus(ctx) +// if err != nil { +// t.Fatal(err) +// } + +// _ = list + +// // global.Logger.Infof(ctx, "status: %v", list) + +// t.Log("status:", list) + +// } diff --git a/aliyun/assets/zh_hant.html b/aliyun/assets/zh_hant.html new file mode 100644 index 0000000..4d56564 --- /dev/null +++ b/aliyun/assets/zh_hant.html @@ -0,0 +1,160 @@ + + +
+ + +
+
+ qj@qzB&%+<
zj5drB{8+AX*OtpxgO_#Gm{2xeA43#(CF;Ihft*LK! 6fYfz=4I3kj;p%LJOH!E4uZC;$hp7B)A@Wu{
zgDro(6YC$m9Gh;RNf(`DI;+#C9fRx%2cwYgSq-+1z%-5O2qytlLl#H@%Ie}#+-Skr
z3M9)Q0%VY$AV5zQh(PIAeHkESDe1nskLH99R@TjWEj;^ID7-ESC68VK$H#LJmfMz<
zv|~x0yQXDPzwFNoB&j!I45S-?!p=Hc$0B&rzC+DBFQf2@>ySQs7`Cz1meag9(9CP>
zori^?fH@=}X#v$b1)yyi!$bJDk=8S|8Mc=x2xr3nbQRrDG_EhIF8vK$56po7pSL3P
z;Y+~k*MXFefi2XUH{gB$A82^_UR2z7J}NFb3)SO~U@X}WElh6QEOfblwS3cQ&KObg
zWJs?147qLs$=AU%XgLKV6+Uwx1F)AB*UUy$Ln~P@aS*ml9gMW=N278sv+i$Rg+FgS
z!m?^|wf<(45v!ZV0BK}FX@hB5NCFwLkjAn$SHV}B4rl6@Xj%Ohnm?b9s@I-E_KUY;
z<3pF=OG5bBdB4MV3`sWV>dE*)3}1LW1f)E%6Y2 n0SE1^ke?r5O
zXVLP`Jh(qu0pGWbGuM3rqM|8gC^|KoZ1@`efcboNMme(_oO^88a7!<~$k(@((q
zDTH$ZE$G;RW*|x)%c7WZUm4@*N}g{$rEvu=T1F)+N?QD8m>z)~(l&
nDkOkL4=_7-IQ7E(6{IV2;^H
Q3+zLUpv7XFDs4=ig=!s4iQXInL?E&2`mChrFkj?!1gK
zCM{;(8aOw7h}zE=plIpi$b8{eq(5>w((aswj2lKHbM_Eq6VBY}N24ebYgUZ!hbqQl
z)l3X(k~=Xr>ws#4Srbt$RYx@>F!rPzbnnfYxRYw3yEoF`MtU?c<5SAP5Wo@njMNxW
z?B7P*UnBR?z(hq4SJJbmYe)A*)wqEuop>l?;-io?^8}<{a4J%-8i^D(veXAJMEbKg
zB4^%%D0}N~X!vFYJSm?eT(BPD%57%2m`3QRw?h4krgNJxT0I`D(TEfp8SBV;U^QF%
zFIjdQNcBhlTtEg0q>ljFvf_$
zx>o~J^w=ZPI-_>wMEHUMHcW;eMtWYhuKTy!uC4Sw!L|3NI+FOG6s=eL+qKP36kVb_
zis&a^b{oj;4y@f`zrV-Y$
+
+ huang
xin
\r\n"
+ body += "" + message.Body + "\r\n"
+ // body += "--" + boundary + "--\r\n\r\n"
+ buffer.WriteString(body)
+
+ for _, value := range message.Attachment {
+ newBuf := bytes.NewBuffer(nil)
+ err := m.writeFile(newBuf, value.Name)
+ if err != nil {
+ fmt.Println("file err:", err)
+ continue
+ }
+
+ f_name := path.Base(value.Name)
+ attachment := "--" + boundary + "\r\n"
+ attachment += "Content-Transfer-Encoding:base64\r\n"
+ attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
+ attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
+ // attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
+ buffer.WriteString(attachment)
+
+ buffer.WriteString(newBuf.String())
+
+ buffer.WriteString("\r\n")
+ }
+
+ if message.Form == "" {
+ message.Form = m.user
+ }
+
+ imgBuf := bytes.NewBuffer(nil)
+ err := m.writeFile(imgBuf, "./asset/余额宝.png")
+ if err != nil {
+ return fmt.Errorf("file err: %w", err)
+ }
+ f_name:= "f_name"
+ attachment := "--" + boundary + "\r\n"
+ attachment += "Content-Transfer-Encoding:base64\r\n"
+ attachment += "Content-ID:myimage \r\n"
+ attachment += "Content-Disposition:inline;filename=" + f_name + ".png \r\n"
+ attachment += "Content-Type:image/png \r\n"
+ buffer.WriteString(attachment)
+
+ buffer.WriteString(imgBuf.String())
+
+ buffer.WriteString("\r\n--" + boundary + "--\r\n")
+
+ b := buffer.Bytes()
+ err = smtp.SendMail(m.host+":"+m.port, m.auth, message.Form, message.To, b)
+ fmt.Println("发送结束:", err)
+ fmt.Println(string(b))
+ return err
+}
+
+
+
+// 格式化header
+func (m *Mailx) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
+ header := ""
+ // header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
+ for key, value := range Header {
+ if value != "" {
+ header += key + ": " + value + "\r\n"
+ }
+ }
+ header += "\r\n"
+ buffer.WriteString(header)
+ return header
+}
+
+// 格式化文件
+func (m *Mailx) writeFile(buffer *bytes.Buffer, fileName string) error {
+ file, err := os.ReadFile(fileName)
+ if err != nil {
+ return err
+ }
+ payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
+ base64.StdEncoding.Encode(payload, file)
+ buffer.WriteString("\r\n")
+ for index, line := 0, len(payload); index < line; index++ {
+ buffer.WriteByte(payload[index])
+ if (index+1)%76 == 0 {
+ buffer.WriteString("\r\n")
+ }
+ }
+ return nil
+}
diff --git a/mailx_test.go b/old/mailx_test.go
similarity index 75%
rename from mailx_test.go
rename to old/mailx_test.go
index a03f175..00d6d06 100644
--- a/mailx_test.go
+++ b/old/mailx_test.go
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
- "code.yun.ink/pkg/mailx"
+ mailx "code.yun.ink/pkg/mailx/old"
)
func TestMail(t *testing.T) {
@@ -12,17 +12,17 @@ func TestMail(t *testing.T) {
msg := mailx.Message{
// Form: "support@email.blueoceanpay.com",
- To: []string{"yun@blueoceanpay.com", "995116474@qq.com"},
- Cc: []string{"287852692@qq.com"},
- Bcc: []string{"1362716835@qq.com"},
- Subject: "test mail",
- Body: "测试文件",
+ To: []string{"huangxinyun520@gmail.com", "995116474@qq.com"},
+ // Cc: []string{"287852692@qq.com"},
+ // Bcc: []string{"1362716835@qq.com"},
+ Subject: "test mail",
+ Body: "dasdsadsasda
dasdsadsadsa",
Attachment: []mailx.Attachment{
- // {
- // Name: "/code/statistic/out.xlsx",
- // ContentType: "",
- // WithFile: true,
- // },
+ {
+ Name: "asset/hhh.txt",
+ ContentType: "",
+ WithFile: true,
+ },
// {
// Name: "/code/statistic/origin.xlsx",
// ContentType: "",
diff --git a/smtp/log/2024-09-27_info.log b/smtp/log/2024-09-27_info.log
new file mode 100644
index 0000000..90505f7
--- /dev/null
+++ b/smtp/log/2024-09-27_info.log
@@ -0,0 +1,3 @@
+
+[info]{"time":"2024-09-27 18:32:44.687712","file":"/smtp.go:29","func":"InitEmail","gid":"70","content":["params:{Smtp:0xc00013a910 Aws:\u003cnil\u003e Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
+[info]{"time":"2024-09-27 18:54:34.654603","file":"/smtp.go:29","func":"InitEmail","gid":"43","content":["params:{Smtp:0xc0004780f0 Aws:\u003cnil\u003e Aliyun:\u003cnil\u003e Mailgun:\u003cnil\u003e}"]}
\ No newline at end of file
diff --git a/smtp/log/2024-09-30_info.log b/smtp/log/2024-09-30_info.log
new file mode 100644
index 0000000..b53e55b
--- /dev/null
+++ b/smtp/log/2024-09-30_info.log
@@ -0,0 +1,2 @@
+
+[info]{"time":"2024-09-30 11:31:05.402488","file":"/smtp.go:33","func":"InitEmail","gid":"54","content":["params:\u0026{Username:test@email.aisz.org Password:FDuSoJKiC9tNSDIG6DFh Host:smtpdm.aliyun.com Port:80 ReplyTo:}"]}
\ No newline at end of file
diff --git a/smtp/smtp.go b/smtp/smtp.go
new file mode 100644
index 0000000..cc8f985
--- /dev/null
+++ b/smtp/smtp.go
@@ -0,0 +1,177 @@
+package smtp
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "net/smtp"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "code.yun.ink/pkg/mailx/interfaces"
+ "github.com/yuninks/loggerx"
+)
+
+// 邮件发送的封装
+// 1. 支持文本
+// 2. 支持文件
+
+type Smtp struct {
+ interfaces.DefaultEmail
+ params *interfaces.EmailConfigDataSmtp
+ auth smtp.Auth
+ logger loggerx.LoggerInterface
+}
+
+func (l *Smtp) InitEmail(ctx context.Context, params interfaces.EmailConfigData, logger loggerx.LoggerInterface) (interfaces.Email, error) {
+
+ if params.Smtp == nil {
+ return nil, errors.New("not smtp")
+ }
+ l.logger.Infof(ctx, "params:%+v", params.Smtp)
+
+ m := &Smtp{
+ params: params.Smtp,
+ }
+ m.auth = smtp.PlainAuth("", params.Smtp.Username, params.Smtp.Password, params.Smtp.Host)
+ return m, nil
+}
+
+func (l *Smtp) Send(ctx context.Context, message interfaces.Message) error {
+ if l.params == nil {
+ return errors.New("not init")
+ }
+ // .Auth()
+ buffer := bytes.NewBuffer(nil)
+ boundary := "YunBoundaryYun"
+
+ Header := make(map[string]string)
+ // Header["From"] = "BOP<" + message.Form + ">"
+
+ if message.Form != "" {
+ Header["From"] = message.Form
+ } else {
+ Header["From"] = l.params.Username
+ }
+
+ if len(message.To) > 0 {
+ str := ""
+ for _, val := range message.To {
+ name := ""
+ s := strings.Split(val, "@")
+ if len(s) > 0 {
+ name = s[0]
+ }
+ str = str + "," + name + "<" + val + ">"
+ }
+ Header["To"] = strings.Trim(str, ",")
+ // Header["To"] = strings.Join(message.To, ",")
+ }
+ if len(message.Cc) > 0 {
+ str := ""
+ for _, val := range message.Cc {
+ name := ""
+ s := strings.Split(val, "@")
+ if len(s) > 0 {
+ name = s[0]
+ }
+ str = str + "," + name + "<" + val + ">"
+ }
+ Header["Cc"] = strings.Trim(str, ",")
+ // Header["Cc"] = strings.Join(message.Cc, ",")
+ }
+ if len(message.Bcc) > 0 {
+ str := ""
+ for _, val := range message.Bcc {
+ name := ""
+ s := strings.Split(val, "@")
+ if len(s) > 0 {
+ name = s[0]
+ }
+ str = str + "," + name + "<" + val + ">"
+ }
+ Header["Bcc"] = strings.Trim(str, ",")
+ // Header["Bcc"] = strings.Join(message.Bcc, ",")
+ }
+
+ Header["Subject"] = message.Subject
+ Header["Content-Type"] = "multipart/mixed; charset=UTF-8; boundary=" + boundary
+ Header["Date"] = time.Now().String()
+ Header["Reply-To"] = message.ReplyTo
+
+ Header["X-Priority"] = "3"
+ l.writeHeader(buffer, Header)
+
+ body := "--" + boundary + "\r\n"
+ // body += "Content-Type: text/plain; charset=UTF-8 \r\n"
+ body += "Content-Type: text/html;charset=utf-8\r\n"
+ body += "Content-Transfer-Encoding:quoted-printable\r\n\r\n"
+ // body += "huang
xin
\r\n"
+
+ // body += "" + message.Body + "\r\n"
+
+ body += message.Body + "\r\n"
+
+ // body += "--" + boundary + "--\r\n\r\n"
+ buffer.WriteString(body)
+
+ for _, value := range message.Attachment {
+ newBuf := bytes.NewBuffer(nil)
+ err := l.writeFile(newBuf, value.Content)
+ if err != nil {
+ fmt.Println("file err:", err)
+ continue
+ }
+
+ f_name := path.Base(value.Content)
+ attachment := "--" + boundary + "\r\n"
+ attachment += "Content-Transfer-Encoding:base64\r\n"
+ attachment += "Content-Disposition:attachment;filename=" + f_name + "\r\n"
+ attachment += "Content-Type: application/octet-stream;charset=utf-8;name=" + f_name + "\r\n"
+ // attachment += "Contment-Type:" + message.attachment.contentType + ";name=\"" + message.attachment.name + "\"\r\n"
+ buffer.WriteString(attachment)
+
+ buffer.WriteString(newBuf.String())
+ }
+
+ buffer.WriteString("\r\n--" + boundary + "--\r\n")
+ b := buffer.Bytes()
+ err := smtp.SendMail(l.params.Host+":"+l.params.Port, l.auth, l.params.Username, message.To, b)
+ return err
+}
+
+// 格式化header
+func (l *Smtp) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
+ header := ""
+ // header := "Content-Type: multipart/mixed;charset=UTF-8;boundary=\"YunBoundaryYun\" \r\n"
+ for key, value := range Header {
+ if value != "" {
+ header += key + ": " + value + "\r\n"
+ }
+ }
+ header += "\r\n"
+ buffer.WriteString(header)
+ return header
+}
+
+// 格式化文件
+func (l *Smtp) writeFile(buffer *bytes.Buffer, fileName string) error {
+ file, err := os.ReadFile(fileName)
+ if err != nil {
+ return err
+ }
+ payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
+ base64.StdEncoding.Encode(payload, file)
+ buffer.WriteString("\r\n")
+ for index, line := 0, len(payload); index < line; index++ {
+ buffer.WriteByte(payload[index])
+ if (index+1)%76 == 0 {
+ buffer.WriteString("\r\n")
+ }
+ }
+ return nil
+}
diff --git a/smtp/smtp_test.go b/smtp/smtp_test.go
new file mode 100644
index 0000000..110c26d
--- /dev/null
+++ b/smtp/smtp_test.go
@@ -0,0 +1,96 @@
+package smtp_test
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "testing"
+
+ "code.yun.ink/pkg/mailx/interfaces"
+ "code.yun.ink/pkg/mailx/smtp"
+ "github.com/yuninks/loggerx"
+)
+
+func TestMail(t *testing.T) {
+ sm := smtp.Smtp{}
+ ctx := context.Background()
+
+ logger := loggerx.NewLogger(ctx)
+
+ ini, err := sm.InitEmail(ctx, interfaces.EmailConfigData{
+ Smtp: &interfaces.EmailConfigDataSmtp{
+ Username: "test@email.aisz.org",
+ Password: "FDuSoJKiC9tNSDIG6DFh",
+ ReplyTo: "",
+ Host: "smtpdm.aliyun.com",
+ Port: "80",
+ },
+ }, logger)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ req, err := http.Get("https://baidu.com")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer req.Body.Close()
+
+ by, err := io.ReadAll(req.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ msg := interfaces.Message{
+ To: []string{"995116474@qq.com"},
+ Cc: []string{"287852692@qq.com"},
+ Bcc: []string{"1362716835@qq.com"},
+ ReplyTo: "huangxinyun@dreaminglife.cn",
+ Subject: "test mail",
+ Body: string(by),
+ Attachment: []interfaces.MessageAttachment{
+ // {
+ // Name: "/code/statistic/out.xlsx",
+ // ContentType: "",
+ // WithFile: true,
+ // },
+ // {
+ // Name: "/code/statistic/origin.xlsx",
+ // ContentType: "",
+ // WithFile: true,
+ // },
+ // {
+ // Name: "/code/statistic/out2.xlsx",
+ // ContentType: "",
+ // WithFile: true,
+ // },
+ },
+ }
+
+ err = ini.Send(ctx, msg)
+ fmt.Println(err)
+}
+
+// func TestQQ(t *testing.T) {
+
+// // 发件人邮箱
+// from := "995116474@qq.com"
+// // 授权码,而非密码
+// authCode := "xxxxxxxxxxxxxxxxxxxxxx"
+// // 收件人邮箱,可以是多个收件人
+// to := []string{"yun@yun.ink"}
+// // 邮件服务器信息
+// smtpHost := "smtp.qq.com"
+// smtpPort := "587" // 或使用465,根据你的SMTP服务器要求设置
+
+// mail := mailx.NewMailx(from, authCode, smtpHost, smtpPort)
+
+// msg := mailx.Message{
+// To: to,
+// Subject: "test mail",
+// Body: "测试",
+// }
+// err := mail.Send(msg)
+// fmt.Println(err)
+// }