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" ) type Aliyun struct { interfaces.DefaultEmail client *dm20151123.Client // params *interfaces.EmialConfigDataAliyun // logger loggerx.LoggerInterface } func NewAliyun() *Aliyun { aliyun := &Aliyun{} aliyun.Options = interfaces.DefaultOptions() aliyun.EmailType = "Aliyun" return aliyun } func (l *Aliyun) SetOption(ctx context.Context, opt ...interfaces.Option) (interfaces.EmailInterface, error) { for _, o := range opt { o(&l.Options) } l.Options.Logger.Infof(ctx, "Aliyun:%+v", l.Options.Aliyun) if l.Options.Aliyun == nil { return nil, errors.New("not aliyun") } // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html。 config := &openapi.Config{ // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 AccessKeyId: tea.String(l.Options.Aliyun.AccessId), // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 AccessKeySecret: tea.String(l.Options.Aliyun.AccessKey), } if l.Options.Aliyun.Endpoint == "" { l.Options.Aliyun.Endpoint = "dm.aliyuncs.com" } // Endpoint 请参考 https://api.aliyun.com/product/Dm config.Endpoint = tea.String(l.Options.Aliyun.Endpoint) result, err := dm20151123.NewClient(config) if err != nil { return nil, err } return &Aliyun{ client: result, }, 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.Options.Aliyun.AccountName == "" { return errors.New("AccountName 必填") } toAddress := strings.Join(params.To, ",") singleSendMailRequest := &dm20151123.SingleSendMailRequest{} singleSendMailRequest.AccountName = tea.String(l.Options.Aliyun.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.Options.Logger.Infof(ctx, "resp:%+v err:%+v", resp, err) if err != nil { return err } return nil }() if tryErr != nil { l.Options.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.Options.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.Options.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.Options.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 }