Files
cloudpods/pkg/notify/sender/email.go
2023-08-08 11:36:37 +08:00

329 lines
8.1 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sender
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"strings"
"time"
gomail "gopkg.in/mail.v2"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"
api "yunion.io/x/onecloud/pkg/apis/notify"
"yunion.io/x/onecloud/pkg/notify/models"
)
type errorMap map[string]error
func (em errorMap) Error() string {
msg := make(map[string]string)
for k, e := range em {
msg[k] = e.Error()
}
return jsonutils.Marshal(msg).String()
}
type SEmailSender struct {
config map[string]api.SNotifyConfigContent
}
func (emailSender *SEmailSender) GetSenderType() string {
return api.EMAIL
}
func (emailSender *SEmailSender) Send(ctx context.Context, args api.SendParams) error {
// 初始化emaliClient
hostNmae, hostPort, userName, password := models.ConfigMap[api.EMAIL].Content.Hostname, models.ConfigMap[api.EMAIL].Content.Hostport, models.ConfigMap[api.EMAIL].Content.Username, models.ConfigMap[api.EMAIL].Content.Password
dialer := gomail.NewDialer(hostNmae, hostPort, userName, password)
// 是否支持ssl
if models.ConfigMap[api.EMAIL].Content.SslGlobal {
dialer.SSL = true
} else {
dialer.SSL = false
dialer.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
if len(args.EmailMsg.To) > 0 {
// emailMsg不为空时
sender, err := dialer.Dial()
if err != nil {
return errors.Wrap(err, "dialer.Dial")
}
retErr := errorMap{}
destMap := make(map[string]int)
for _, tos := range [][]string{
args.EmailMsg.To,
args.EmailMsg.Cc,
args.EmailMsg.Bcc,
} {
for _, to := range tos {
to = strings.ToLower(to)
if _, ok := destMap[to]; !ok {
destMap[to] = 1
}
}
}
for to := range destMap {
gmsg := gomail.NewMessage()
gmsg.SetHeader("From", models.ConfigMap[api.EMAIL].Content.SenderAddress)
gmsg.SetHeader("To", to)
gmsg.SetHeader("Subject", args.EmailMsg.Subject)
gmsg.SetBody("text/html", args.EmailMsg.Body)
for i := range args.EmailMsg.Attachments {
attach := args.EmailMsg.Attachments[i]
gmsg.Attach(attach.Filename,
gomail.SetCopyFunc(func(w io.Writer) error {
mime := attach.Mime
if len(mime) == 0 {
mime = "application/octet-stream"
}
_, err := w.Write([]byte("Content-Type: " + attach.Mime))
return errors.Wrap(err, "WriteMime")
}),
gomail.SetHeader(map[string][]string{
"Content-Disposition": {
fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", attach.Filename)),
},
}),
gomail.SetCopyFunc(func(w io.Writer) error {
contBytes, err := base64.StdEncoding.DecodeString(attach.Base64Content)
if err != nil {
return errors.Wrap(err, "base64.StdEncoding.DecodeString")
}
_, err = w.Write(contBytes)
return errors.Wrap(err, "WriteContent")
}),
)
}
errs := make([]error, 0)
for tryTime := 3; tryTime > 0; tryTime-- {
err = gomail.Send(sender, gmsg)
if err != nil {
errs = append(errs, err)
time.Sleep(time.Second * 10)
continue
}
errs = errs[0:0]
break
}
if len(errs) > 0 {
retErr[to] = errors.NewAggregate(errs)
}
}
if len(retErr) > 0 {
log.Errorf("send email error:%v", jsonutils.Marshal(retErr))
return errors.Wrap(retErr, "send email")
}
} else {
// 构造email发送请求
gmsg := gomail.NewMessage()
gmsg.SetHeader("From", models.ConfigMap[api.EMAIL].Content.SenderAddress)
gmsg.SetHeader("To", args.Receivers.Contact)
gmsg.SetHeader("Subject", args.Title)
gmsg.SetBody("text/html", args.EmailMsg.Body)
dialer.StartTLSPolicy = gomail.MandatoryStartTLS
if err := dialer.DialAndSend(gmsg); err != nil {
return errors.Wrap(err, "send email")
}
}
return nil
}
func (emailSender *SEmailSender) ValidateConfig(ctx context.Context, config api.NotifyConfig) (string, error) {
errChan := make(chan error, 1)
go func() {
dialer := gomail.NewDialer(config.Hostname, config.Hostport, config.Username, config.Password)
if config.SslGlobal {
dialer.SSL = true
} else {
dialer.SSL = false
// StartLSConfig
dialer.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
sender, err := dialer.Dial()
if err != nil {
errChan <- err
return
}
sender.Close()
errChan <- nil
}()
ticker := time.Tick(10 * time.Second)
select {
case <-ticker:
return "", errors.Error("timeout")
case err := <-errChan:
return "", err
}
}
func (emailSender *SEmailSender) ContactByMobile(ctx context.Context, mobile, domainId string) (string, error) {
return "", nil
}
func (emailSender *SEmailSender) IsPersonal() bool {
return true
}
func (emailSender *SEmailSender) IsRobot() bool {
return false
}
func (emailSender *SEmailSender) IsValid() bool {
return len(emailSender.config) > 0
}
func (emailSender *SEmailSender) IsPullType() bool {
return false
}
func (emailSender *SEmailSender) IsSystemConfigContactType() bool {
return true
}
func (emailSender *SEmailSender) GetAccessToken(ctx context.Context, key string) error {
return nil
}
func (emailSender *SEmailSender) sendMessageWithToken(ctx context.Context, uri string, method httputils.THttpMethod, header http.Header, params url.Values, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if params == nil {
params = url.Values{}
}
params.Set("access_token", models.ConfigMap[api.WORKWX].Content.AccessToken)
return sendRequest(ctx, uri, httputils.POST, nil, params, jsonutils.Marshal(body))
}
func (emailSender *SEmailSender) RegisterConfig(config models.SConfig) {
models.ConfigMap[config.Type] = config
}
func init() {
models.Register(&SEmailSender{
config: map[string]api.SNotifyConfigContent{},
})
}
/*
func SendEmail(conf *api.SEmailConfig, msg *api.SEmailMessage) error {
dialer := gomail.NewDialer(conf.Hostname, conf.Hostport, conf.Username, conf.Password)
if conf.SslGlobal {
dialer.SSL = true
} else {
dialer.SSL = false
dialer.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
sender, err := dialer.Dial()
if err != nil {
return errors.Wrap(err, "dialer.Dial")
}
retErr := errorMap{}
destMap := make(map[string]int)
for _, tos := range [][]string{
msg.To,
msg.Cc,
msg.Bcc,
} {
for _, to := range tos {
to = strings.ToLower(to)
if _, ok := destMap[to]; !ok {
destMap[to] = 1
}
}
}
for to := range destMap {
log.Debugf("send to %s %s", to, msg.Subject)
gmsg := gomail.NewMessage()
gmsg.SetHeader("From", conf.SenderAddress)
gmsg.SetHeader("To", to)
gmsg.SetHeader("Subject", msg.Subject)
gmsg.SetBody("text/html", msg.Body)
for i := range msg.Attachments {
attach := msg.Attachments[i]
gmsg.Attach(attach.Filename,
gomail.SetCopyFunc(func(w io.Writer) error {
mime := attach.Mime
if len(mime) == 0 {
mime = "application/octet-stream"
}
_, err := w.Write([]byte("Content-Type: " + attach.Mime))
return errors.Wrap(err, "WriteMime")
}),
gomail.SetCopyFunc(func(w io.Writer) error {
contBytes, err := base64.StdEncoding.DecodeString(attach.Base64Content)
if err != nil {
return errors.Wrap(err, "base64.StdEncoding.DecodeString")
}
_, err = w.Write(contBytes)
return errors.Wrap(err, "WriteContent")
}),
)
}
errs := make([]error, 0)
for tryTime := 3; tryTime > 0; tryTime-- {
err = gomail.Send(sender, gmsg)
if err != nil {
errs = append(errs, err)
time.Sleep(time.Second * 10)
continue
}
errs = errs[0:0]
break
}
if len(errs) > 0 {
retErr[to] = errors.NewAggregate(errs)
}
}
if len(retErr) > 0 {
return retErr
}
return nil
}
*/