Skip to content

Commit c484b6c

Browse files
alilesterawenxuwan
andauthored
feat: Implement the Email component based on Aliyun DM (#971)
Co-authored-by: wenxuwan <[email protected]>
1 parent 8e16fb5 commit c484b6c

File tree

11 files changed

+468
-1
lines changed

11 files changed

+468
-1
lines changed

cmd/layotto/main.go

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525

2626
"mosn.io/layotto/components/cryption"
2727

28+
"mosn.io/layotto/components/email"
29+
2830
"mosn.io/layotto/pkg/grpc/lifecycle"
2931

3032
"mosn.io/layotto/components/oss"
@@ -43,6 +45,8 @@ import (
4345
aws_cryption "mosn.io/layotto/components/cryption/aws"
4446
aliyun_file "mosn.io/layotto/components/file/aliyun"
4547

48+
aliyun_email "mosn.io/layotto/components/email/aliyun"
49+
4650
"github.com/dapr/components-contrib/secretstores"
4751
"github.com/dapr/components-contrib/secretstores/aws/parameterstore"
4852
"github.com/dapr/components-contrib/secretstores/aws/secretmanager"
@@ -311,6 +315,11 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
311315
cryption.NewFactory("aliyun.kms", aliyun_cryption.NewCryption),
312316
cryption.NewFactory("aws.kms", aws_cryption.NewCryption),
313317
),
318+
319+
// Email
320+
runtime.WithEmailServiceFactory(
321+
email.NewFactory("aliyun.email", aliyun_email.NewAliyunEmail),
322+
),
314323
// PubSub
315324
runtime.WithPubSubFactory(
316325
pubsub.NewFactory("redis", func() dapr_comp_pubsub.PubSub {

components/email/aliyun/email.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package aliyun
18+
19+
import (
20+
"context"
21+
"strings"
22+
23+
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
24+
dm20151123 "github.com/alibabacloud-go/dm-20151123/v2/client"
25+
"github.com/alibabacloud-go/tea/tea"
26+
27+
"mosn.io/layotto/components/email"
28+
)
29+
30+
type AliyunEmail struct {
31+
client *dm20151123.Client
32+
}
33+
34+
func NewAliyunEmail() email.EmailService {
35+
return &AliyunEmail{}
36+
}
37+
38+
var _ email.EmailService = (*AliyunEmail)(nil)
39+
40+
func (a *AliyunEmail) Init(ctx context.Context, conf *email.Config) error {
41+
accessKeyID := conf.Metadata[email.ClientKey]
42+
accessKeySecret := conf.Metadata[email.ClientSecret]
43+
endpoint := conf.Metadata[email.Endpoint]
44+
config := &openapi.Config{
45+
// accessKey ID
46+
AccessKeyId: tea.String(accessKeyID),
47+
// accessKey Secret
48+
AccessKeySecret: tea.String(accessKeySecret),
49+
// endpoint, ref https://api.aliyun.com/product/Dm
50+
Endpoint: tea.String(endpoint),
51+
}
52+
53+
client, err := dm20151123.NewClient(config)
54+
if err != nil {
55+
return err
56+
}
57+
a.client = client
58+
return nil
59+
}
60+
61+
// SendEmail .
62+
func (a *AliyunEmail) SendEmail(ctx context.Context, req *email.SendEmailRequest) (*email.SendEmailResponse, error) {
63+
if !a.checkSendRequest(req) {
64+
return nil, email.ErrInvalid
65+
}
66+
67+
// AccountName is the email send from
68+
accountName := req.Address.From
69+
// ToAddress is target addresses the email send to
70+
toAddress := strings.Join(req.Address.To, ",")
71+
72+
sendMailRequest := &dm20151123.SingleSendMailRequest{}
73+
sendMailRequest.
74+
SetAccountName(accountName).
75+
// AddressType = 1: use the email send from
76+
// ref https://help.aliyun.com/document_detail/29444.html
77+
SetAddressType(1).
78+
// ReplyToAddress = false: the email no need to reply
79+
// ref https://help.aliyun.com/document_detail/29444.html
80+
SetReplyToAddress(false).
81+
SetSubject(req.Subject).
82+
SetToAddress(toAddress).
83+
SetTextBody(req.Content.Text)
84+
85+
resp, err := a.client.SingleSendMail(sendMailRequest)
86+
if err != nil {
87+
return nil, err
88+
}
89+
return &email.SendEmailResponse{
90+
RequestId: *resp.Body.RequestId,
91+
}, nil
92+
}
93+
94+
func (a *AliyunEmail) checkSendRequest(r *email.SendEmailRequest) bool {
95+
// make sure content not empty
96+
if r.Content == nil || r.Content.Text == "" {
97+
return false
98+
}
99+
if info := r.Address; info == nil || info.From == "" || len(info.To) == 0 {
100+
return false
101+
}
102+
return true
103+
}
104+
105+
// SendEmailWithTemplate .
106+
// template must have been applied in aliyun console, and there need the template name
107+
// receivers must have been filled in aliyun console, and there need the receivers list name
108+
func (a *AliyunEmail) SendEmailWithTemplate(ctx context.Context, req *email.SendEmailWithTemplateRequest) (*email.SendEmailWithTemplateResponse, error) {
109+
if !a.checkSendWithTemplateRequest(req) {
110+
return nil, email.ErrInvalid
111+
}
112+
113+
// AccountName is the email send from
114+
accountName := req.Address.From
115+
// ReceiversName is the name of the recipient list that is created in advance and uploaded with recipients.
116+
// Only take the element with index zero
117+
receiversName := req.Address.To[0]
118+
119+
sendMailWithTemplateRequest := &dm20151123.BatchSendMailRequest{}
120+
sendMailWithTemplateRequest.
121+
SetAccountName(accountName).
122+
// AddressType = 1: use the email send from
123+
// ref https://help.aliyun.com/document_detail/29444.html
124+
SetAddressType(1).
125+
SetReceiversName(receiversName).
126+
SetTemplateName(req.Template.TemplateId)
127+
128+
resp, err := a.client.BatchSendMail(sendMailWithTemplateRequest)
129+
if err != nil {
130+
return nil, err
131+
}
132+
return &email.SendEmailWithTemplateResponse{
133+
RequestId: *resp.Body.RequestId,
134+
}, nil
135+
}
136+
137+
func (a *AliyunEmail) checkSendWithTemplateRequest(r *email.SendEmailWithTemplateRequest) bool {
138+
// make sure template exist
139+
if r.Template == nil || r.Template.TemplateId == "" {
140+
return false
141+
}
142+
if info := r.Address; info == nil || info.From == "" || len(info.To) == 0 {
143+
return false
144+
}
145+
return true
146+
}

components/email/aliyun/email_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package aliyun
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
25+
"mosn.io/layotto/components/email"
26+
)
27+
28+
func TestInit(t *testing.T) {
29+
a := &AliyunEmail{}
30+
conf := &email.Config{
31+
Metadata: map[string]string{
32+
email.ClientKey: "aki",
33+
email.ClientSecret: "aks",
34+
email.Endpoint: "endpoint",
35+
},
36+
}
37+
err := a.Init(context.TODO(), conf)
38+
assert.Nil(t, err)
39+
}
40+
41+
func TestSendEmail(t *testing.T) {
42+
a := &AliyunEmail{}
43+
conf := &email.Config{
44+
Metadata: map[string]string{
45+
email.ClientKey: "aki",
46+
email.ClientSecret: "aks",
47+
email.Endpoint: "endpoint",
48+
},
49+
}
50+
err := a.Init(context.TODO(), conf)
51+
assert.Nil(t, err)
52+
53+
request := &email.SendEmailRequest{
54+
Subject: "a_subject",
55+
Address: &email.EmailAddress{
56+
From: "email_send_from",
57+
To: []string{"email_send_to"},
58+
},
59+
Content: &email.Content{Text: "some words"},
60+
}
61+
_, err = a.SendEmail(context.TODO(), request)
62+
assert.Error(t, err)
63+
}
64+
65+
func TestSendEmailWithTemplate(t *testing.T) {
66+
a := &AliyunEmail{}
67+
conf := &email.Config{
68+
Metadata: map[string]string{
69+
email.ClientKey: "aki",
70+
email.ClientSecret: "aks",
71+
email.Endpoint: "endpoint",
72+
},
73+
}
74+
err := a.Init(context.TODO(), conf)
75+
assert.NoError(t, err)
76+
77+
request := &email.SendEmailWithTemplateRequest{
78+
Template: &email.EmailTemplate{
79+
TemplateId: "a_template",
80+
},
81+
Address: &email.EmailAddress{
82+
From: "email_send_from",
83+
To: []string{"receivers_name"},
84+
},
85+
}
86+
_, err = a.SendEmailWithTemplate(context.TODO(), request)
87+
assert.Error(t, err)
88+
}

components/email/errors.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package email
18+
19+
import "errors"
20+
21+
var (
22+
ErrInvalid = errors.New("invalid argument")
23+
)

components/email/meta.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package email
18+
19+
const (
20+
ClientKey = "accessKeyID"
21+
ClientSecret = "accessKeySecret"
22+
Endpoint = "endpoint"
23+
)

components/go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.18
55
require (
66
github.com/DATA-DOG/go-sqlmock v1.5.0
77
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
8+
github.com/alibabacloud-go/dm-20151123/v2 v2.0.1
89
github.com/alibabacloud-go/kms-20160120/v3 v3.0.2
910
github.com/alibabacloud-go/tea v1.1.19
1011
github.com/alicebob/miniredis/v2 v2.16.0

components/go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,14 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
141141
github.com/alibaba/sentinel-golang v1.0.3/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
142142
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
143143
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
144+
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
144145
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
145146
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4=
146147
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
147148
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
148149
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
150+
github.com/alibabacloud-go/dm-20151123/v2 v2.0.1 h1:wEIEI4vvk7vtiJmXEUnom+ylEBfy107WQjaPNyQc7E4=
151+
github.com/alibabacloud-go/dm-20151123/v2 v2.0.1/go.mod h1:q9cd++SgWfT9U5kdBbRRlvzrr0HOKayLxfe9s0NVZhQ=
149152
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
150153
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
151154
github.com/alibabacloud-go/kms-20160120/v3 v3.0.2 h1:kxtotOKKdQuO2UlOrOsevBioZfdfV4MYGu1jJ0Rctlk=

0 commit comments

Comments
 (0)