From 2525c81c24f1adfd61de8238e88c691d2558eb06 Mon Sep 17 00:00:00 2001 From: alilestera Date: Thu, 12 Oct 2023 11:29:03 +0800 Subject: [PATCH 1/5] feat: support tencentcloud SMS component --- components/go.mod | 2 + components/go.sum | 4 + components/sms/errors.go | 35 +++ components/sms/meta.go | 31 +++ .../sms/{types_generated.go => registry.go} | 30 +-- .../sms/{interface_generated.go => sms.go} | 10 +- components/sms/tencentcloud/sms.go | 203 ++++++++++++++++++ .../sms/{struct_generated.go => types.go} | 23 +- 8 files changed, 309 insertions(+), 29 deletions(-) create mode 100644 components/sms/errors.go create mode 100644 components/sms/meta.go rename components/sms/{types_generated.go => registry.go} (67%) rename components/sms/{interface_generated.go => sms.go} (88%) create mode 100644 components/sms/tencentcloud/sms.go rename components/sms/{struct_generated.go => types.go} (77%) diff --git a/components/go.mod b/components/go.mod index f4f5cb476d..8aea2cafdb 100644 --- a/components/go.mod +++ b/components/go.mod @@ -34,6 +34,8 @@ require ( github.com/pkg/errors v0.9.1 github.com/qiniu/go-sdk/v7 v7.11.1 github.com/stretchr/testify v1.8.0 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759 github.com/tencentyun/cos-go-sdk-v5 v0.7.33 github.com/valyala/fasthttp v1.40.0 go.beyondstorage.io/services/hdfs v0.3.0 diff --git a/components/go.sum b/components/go.sum index c836b6ba50..a829722da3 100644 --- a/components/go.sum +++ b/components/go.sum @@ -1201,7 +1201,11 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/supplyon/gremcos v0.1.0/go.mod h1:ZnXsXGVbGCYDFU5GLPX9HZLWfD+ZWkiPo30KUjNoOtw= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759 h1:flWgFybB3MYWFxwRO4yXbdiPT3SNwjSLuXCXsfs6kN4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759 h1:GrpwnCcbPYewCj/lvyAWEShTprW+5CVWrU+89UyjXcs= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759/go.mod h1:PEv4IxnbGqYejMUrUxawrlKHQUiAvgEXHpYP4Rfa538= github.com/tencentyun/cos-go-sdk-v5 v0.7.33 h1:5jmJU7U/1nf/7ZPDkrUL8KlF1oDUzTHsdtLNY6x0hq4= github.com/tencentyun/cos-go-sdk-v5 v0.7.33/go.mod h1:4E4+bQ2gBVJcgEC9Cufwylio4mXOct2iu05WjgEBx1o= github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= diff --git a/components/sms/errors.go b/components/sms/errors.go new file mode 100644 index 0000000000..f7f229063f --- /dev/null +++ b/components/sms/errors.go @@ -0,0 +1,35 @@ +// Copyright 2021 Layotto Authors +// 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 sms + +import ( + "errors" + "fmt" +) + +var ( + ErrClientNotInit = errors.New("error: client not init") +) + +func MissingMethodParam(method, param string) error { + return fmt.Errorf("error: sms `%s` method missing parameter `%s`", method, param) +} + +func MissingInitParam(param string) error { + return MissingMethodParam("init", param) +} + +func MissingSendSmsParam(param string) error { + return MissingMethodParam("sendSms", param) +} diff --git a/components/sms/meta.go b/components/sms/meta.go new file mode 100644 index 0000000000..27aeb6b778 --- /dev/null +++ b/components/sms/meta.go @@ -0,0 +1,31 @@ +// Copyright 2021 Layotto Authors +// 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 sms + +// metadata required to init sms client +const ( + ClientKey = "accessKeyID" + ClientSecret = "accessKeySecret" + Region = "region" +) + +// metadata required to tencentcloud sms request +const ( + SdkAppId = "SdkAppId" +) + +// metadata contained in sms response +const ( + PhoneNumber = "PhoneNumber" +) diff --git a/components/sms/types_generated.go b/components/sms/registry.go similarity index 67% rename from components/sms/types_generated.go rename to components/sms/registry.go index 1eb37a0ffd..3a31d7e091 100644 --- a/components/sms/types_generated.go +++ b/components/sms/registry.go @@ -1,5 +1,3 @@ -// Code generated by github.com/layotto/protoc-gen-p6 . - // Copyright 2021 Layotto Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,34 +14,22 @@ package sms import ( - fmt "fmt" - - info "mosn.io/layotto/components/pkg/info" - ref "mosn.io/layotto/components/ref" -) + "fmt" -const ( - serviceName = "sms" + "mosn.io/layotto/components/pkg/info" ) -// Config is the component's configuration -type Config struct { - ref.Config - Type string `json:"type"` - Metadata map[string]string `json:"metadata"` -} - type Registry interface { Register(fs ...*Factory) - Create(compType string) (SmsService, error) + Create(compType string) (Sms, error) } type Factory struct { CompType string - FactoryMethod func() SmsService + FactoryMethod func() Sms } -func NewFactory(compType string, f func() SmsService) *Factory { +func NewFactory(compType string, f func() Sms) *Factory { return &Factory{ CompType: compType, FactoryMethod: f, @@ -51,14 +37,14 @@ func NewFactory(compType string, f func() SmsService) *Factory { } type registry struct { - stores map[string]func() SmsService + stores map[string]func() Sms info *info.RuntimeInfo } func NewRegistry(info *info.RuntimeInfo) Registry { info.AddService(serviceName) return ®istry{ - stores: make(map[string]func() SmsService), + stores: make(map[string]func() Sms), info: info, } } @@ -70,7 +56,7 @@ func (r *registry) Register(fs ...*Factory) { } } -func (r *registry) Create(compType string) (SmsService, error) { +func (r *registry) Create(compType string) (Sms, error) { if f, ok := r.stores[compType]; ok { r.info.LoadComponent(serviceName, compType) return f(), nil diff --git a/components/sms/interface_generated.go b/components/sms/sms.go similarity index 88% rename from components/sms/interface_generated.go rename to components/sms/sms.go index 3c943fe36e..3e19f60166 100644 --- a/components/sms/interface_generated.go +++ b/components/sms/sms.go @@ -1,5 +1,3 @@ -// Code generated by github.com/layotto/protoc-gen-p6 . - // Copyright 2021 Layotto Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,10 +14,14 @@ package sms import ( - context "context" + "context" +) + +const ( + serviceName = "sms" ) -type SmsService interface { +type Sms interface { Init(context.Context, *Config) error SendSmsWithTemplate(context.Context, *SendSmsWithTemplateRequest) (*SendSmsWithTemplateResponse, error) diff --git a/components/sms/tencentcloud/sms.go b/components/sms/tencentcloud/sms.go new file mode 100644 index 0000000000..1096fc6008 --- /dev/null +++ b/components/sms/tencentcloud/sms.go @@ -0,0 +1,203 @@ +// Copyright 2021 Layotto Authors +// 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 tencentcloud + +import ( + "context" + "errors" + "strconv" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcsms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" + + "mosn.io/layotto/components/sms" +) + +var ( + ErrTemplateParams = errors.New("error: template parameters should be key-value pairs of the form [idx, param], with idx starting at 0 and ending at the length of parameters - 1") +) + +// SmsClient defines the methods of the tencentcloud sms client. +type SmsClient interface { + SendSmsWithContext(ctx context.Context, request *tcsms.SendSmsRequest) (response *tcsms.SendSmsResponse, err error) +} + +// InitConfig is the information required to initialize the sms client +type InitConfig struct { + SecretId string + SecretKey string + Region string +} + +// NewInitConfig create init config, which will be used in NewSmsClient. +// It checks metadata from sms.Config +// The `accessKeyID` should not be empty. +// The `accessKeySecret` should not be empty. +// The `region` should not be empty. +// Please make sure `region` value is available, +// you can refer https://cloud.tencent.com/document/api/382/52071 +func NewInitConfig(config *sms.Config) (*InitConfig, error) { + meta := config.Metadata + secretId := meta[sms.ClientKey] + if secretId == "" { + return nil, sms.MissingInitParam(sms.ClientKey) + } + secretKey := meta[sms.ClientSecret] + if secretKey == "" { + return nil, sms.MissingInitParam(sms.ClientSecret) + } + region := meta[sms.Region] + if region == "" { + return nil, sms.MissingInitParam(sms.Region) + } + + conf := &InitConfig{ + SecretId: secretId, + SecretKey: secretKey, + Region: region, + } + return conf, nil +} + +// NewSmsClient create tencentcloud sms client instance by InitConfig. +func NewSmsClient(conf *InitConfig) (SmsClient, error) { + credential := common.NewCredential(conf.SecretId, conf.SecretKey) + cpf := profile.NewClientProfile() + return tcsms.NewClient(credential, conf.Region, cpf) +} + +// NewSendSmsRequest create tencentcloud sms request struct by sms.SendSmsWithTemplateRequest. +// The PhoneNumbers should not be empty. +// The Template should not be empty. +// In Template, the TemplateParams should be key-value pairs of the form [idx, param], +// with idx starting at 0 and ending at the length of parameters - 1, +// each [idx, param] key-value pair indicates that the value of the variable at the idx position is param. +// Each parameter and its index must match the variable position of the template corresponding to TemplateId, +// you can refer https://cloud.tencent.com/document/api/382/55981 +func NewSendSmsRequest(req *sms.SendSmsWithTemplateRequest) (*tcsms.SendSmsRequest, error) { + if len(req.PhoneNumbers) == 0 { + return nil, sms.MissingSendSmsParam("phone_numbers") + } + if req.Template == nil { + return nil, sms.MissingSendSmsParam("template") + } + + n := len(req.Template.TemplateParams) + templateParams := make([]string, n) + for k, v := range req.Template.TemplateParams { + idx, err := strconv.Atoi(k) + if err != nil { + return nil, ErrTemplateParams + } + if idx < 0 || idx >= n { + return nil, ErrTemplateParams + } + templateParams[idx] = v + } + + meta := req.Metadata + request := tcsms.NewSendSmsRequest() + // required fields + request.PhoneNumberSet = common.StringPtrs(req.PhoneNumbers) + request.SmsSdkAppId = common.StringPtr(meta[sms.SdkAppId]) + request.TemplateId = common.StringPtr(req.Template.TemplateId) + // optional fields + request.SignName = common.StringPtr(req.SignName) + request.SenderId = common.StringPtr(req.SenderId) + request.TemplateParamSet = common.StringPtrs(templateParams) + return request, nil +} + +// ConvertSmsResponse convert tcsms.SendSmsResponse to sms.SendSmsWithTemplateResponse +func ConvertSmsResponse(resp *tcsms.SendSmsResponse) *sms.SendSmsWithTemplateResponse { + statusSet := resp.Response.SendStatusSet + results := make([]*sms.SendStatus, len(statusSet)) + for i, s := range statusSet { + meta := map[string]string{sms.PhoneNumber: *s.PhoneNumber} + results[i] = &sms.SendStatus{ + Code: *s.Code, + Message: *s.Message, + Metadata: meta, + } + } + smsResp := &sms.SendSmsWithTemplateResponse{ + RequestId: *resp.Response.RequestId, + Results: results, + } + return smsResp +} + +// Sms implemented sms.Sms, is used to send request to tencentcloud sms +type Sms struct { + client SmsClient +} + +// NewSms create empty sms client for tencentcloud +func NewSms() sms.Sms { + return &Sms{} +} + +// Init used to init tencentcloud sms client +// It checks metadata from sms.Config +// The `accessKeyID` should not be empty. +// The `accessKeySecret` should not be empty. +// The `region` should not be empty. +// Please make sure `region` value is available, +// you can refer https://cloud.tencent.com/document/api/382/52071 +func (s *Sms) Init(ctx context.Context, config *sms.Config) error { + conf, err := NewInitConfig(config) + if err != nil { + return err + } + client, err := NewSmsClient(conf) + if err != nil { + return err + } + s.client = client + return nil +} + +// SendSmsWithTemplate used to send sms with template to tencentcloud sms +// Before calling this method, you should make sure that the Init method is called. +// The PhoneNumbers should not be empty. +// The Template should not be empty. +// In Template, the TemplateParams should be key-value pairs of the form [idx, param], +// with idx starting at 0 and ending at the length of parameters - 1, +// each [idx, param] key-value pair indicates that the value of the variable at the idx position is param. +// Each parameter and its index must match the variable position of the template corresponding to TemplateId, +// you can refer https://cloud.tencent.com/document/api/382/55981 +func (s *Sms) SendSmsWithTemplate(ctx context.Context, request *sms.SendSmsWithTemplateRequest) (*sms.SendSmsWithTemplateResponse, error) { + client, err := s.getClient() + if err != nil { + return nil, err + } + smsRequest, err := NewSendSmsRequest(request) + if err != nil { + return nil, err + } + resp, err := client.SendSmsWithContext(ctx, smsRequest) + if err != nil { + return nil, err + } + smsResp := ConvertSmsResponse(resp) + return smsResp, nil +} + +func (s *Sms) getClient() (SmsClient, error) { + if s.client == nil { + return nil, sms.ErrClientNotInit + } + return s.client, nil +} diff --git a/components/sms/struct_generated.go b/components/sms/types.go similarity index 77% rename from components/sms/struct_generated.go rename to components/sms/types.go index b07feb9504..e8bbb53748 100644 --- a/components/sms/struct_generated.go +++ b/components/sms/types.go @@ -1,5 +1,3 @@ -// Code generated by github.com/layotto/protoc-gen-p6 . - // Copyright 2021 Layotto Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +13,17 @@ package sms +import ( + ref "mosn.io/layotto/components/ref" +) + +// Config is the component's configuration +type Config struct { + ref.Config + Type string `json:"type"` + Metadata map[string]string `json:"metadata"` +} + // SendSmsRequest is the request of the `SendSms` method. type SendSmsWithTemplateRequest struct { // Required. The saas service name @@ -45,10 +54,18 @@ type Template struct { type SendSmsWithTemplateResponse struct { // The unique requestId. RequestId string `json:"request_id,omitempty"` + // The status set of SMS + Results []*SendStatus `json:"results,omitempty"` +} + +// Status contains more information about the response +type SendStatus struct { // "OK" represents success. Code string `json:"code,omitempty"` // The error message. Message string `json:"message,omitempty"` - // The metadata returned from SMS service. + // The send status metadata returned from SMS service. + // Includes `PhoneNumber`. + // `PhoneNumber`, is the phone number SMS send to. Supported by tencentcloud. Metadata map[string]string `json:"metadata,omitempty"` } From 2e61dc3ff8911c5d613c0d0d7580a032c184c7e1 Mon Sep 17 00:00:00 2001 From: alilestera Date: Thu, 12 Oct 2023 12:06:12 +0800 Subject: [PATCH 2/5] fix: sms interface name changed from Sms to SmsService --- components/sms/registry.go | 12 ++++++------ components/sms/sms.go | 2 +- components/sms/tencentcloud/sms.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/sms/registry.go b/components/sms/registry.go index 3a31d7e091..3ce751636c 100644 --- a/components/sms/registry.go +++ b/components/sms/registry.go @@ -21,15 +21,15 @@ import ( type Registry interface { Register(fs ...*Factory) - Create(compType string) (Sms, error) + Create(compType string) (SmsService, error) } type Factory struct { CompType string - FactoryMethod func() Sms + FactoryMethod func() SmsService } -func NewFactory(compType string, f func() Sms) *Factory { +func NewFactory(compType string, f func() SmsService) *Factory { return &Factory{ CompType: compType, FactoryMethod: f, @@ -37,14 +37,14 @@ func NewFactory(compType string, f func() Sms) *Factory { } type registry struct { - stores map[string]func() Sms + stores map[string]func() SmsService info *info.RuntimeInfo } func NewRegistry(info *info.RuntimeInfo) Registry { info.AddService(serviceName) return ®istry{ - stores: make(map[string]func() Sms), + stores: make(map[string]func() SmsService), info: info, } } @@ -56,7 +56,7 @@ func (r *registry) Register(fs ...*Factory) { } } -func (r *registry) Create(compType string) (Sms, error) { +func (r *registry) Create(compType string) (SmsService, error) { if f, ok := r.stores[compType]; ok { r.info.LoadComponent(serviceName, compType) return f(), nil diff --git a/components/sms/sms.go b/components/sms/sms.go index 3e19f60166..b02a939863 100644 --- a/components/sms/sms.go +++ b/components/sms/sms.go @@ -21,7 +21,7 @@ const ( serviceName = "sms" ) -type Sms interface { +type SmsService interface { Init(context.Context, *Config) error SendSmsWithTemplate(context.Context, *SendSmsWithTemplateRequest) (*SendSmsWithTemplateResponse, error) diff --git a/components/sms/tencentcloud/sms.go b/components/sms/tencentcloud/sms.go index 1096fc6008..5cbade16d6 100644 --- a/components/sms/tencentcloud/sms.go +++ b/components/sms/tencentcloud/sms.go @@ -145,7 +145,7 @@ type Sms struct { } // NewSms create empty sms client for tencentcloud -func NewSms() sms.Sms { +func NewSms() sms.SmsService { return &Sms{} } From bcaa8cc39e15adf2a9f438a6b4be9a5b3278abb7 Mon Sep 17 00:00:00 2001 From: alilestera Date: Thu, 12 Oct 2023 23:05:48 +0800 Subject: [PATCH 3/5] feat: add mock and unit test for tencentcloud sms component --- components/sms/tencentcloud/export_test.go | 20 +++ components/sms/tencentcloud/sms_test.go | 183 +++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 components/sms/tencentcloud/export_test.go create mode 100644 components/sms/tencentcloud/sms_test.go diff --git a/components/sms/tencentcloud/export_test.go b/components/sms/tencentcloud/export_test.go new file mode 100644 index 0000000000..572f4b6272 --- /dev/null +++ b/components/sms/tencentcloud/export_test.go @@ -0,0 +1,20 @@ +// Copyright 2021 Layotto Authors +// 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 tencentcloud + +import "mosn.io/layotto/components/sms" + +func NewSmsWithClient(client SmsClient) sms.SmsService { + return &Sms{client: client} +} diff --git a/components/sms/tencentcloud/sms_test.go b/components/sms/tencentcloud/sms_test.go new file mode 100644 index 0000000000..dd9df23ca6 --- /dev/null +++ b/components/sms/tencentcloud/sms_test.go @@ -0,0 +1,183 @@ +// Copyright 2021 Layotto Authors +// 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 tencentcloud_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + tcsms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" + + "mosn.io/layotto/components/sms" + "mosn.io/layotto/components/sms/tencentcloud" +) + +type MockSmsClient struct{} + +// MockSmsClient.SendSmsWithContext returns error when the TemplateId equals `-1`, +// else returns empty response. +func (*MockSmsClient) SendSmsWithContext(ctx context.Context, request *tcsms.SendSmsRequest) (*tcsms.SendSmsResponse, error) { + if *request.TemplateId == "-1" { + return nil, errors.New("need error") + } + resp := tcsms.NewSendSmsResponse() + resp.Response = &tcsms.SendSmsResponseParams{ + SendStatusSet: []*tcsms.SendStatus{ + { + Code: common.StringPtr("code"), + Message: common.StringPtr("message"), + PhoneNumber: common.StringPtr("phoneNumber"), + }, + }, + RequestId: common.StringPtr("requestId"), + } + return resp, nil +} + +var ( + ctx = context.Background() + mockSms = tencentcloud.NewSmsWithClient(&MockSmsClient{}) +) + +var ( + confSuccess = &sms.Config{ + Metadata: map[string]string{ + sms.ClientKey: "ck", + sms.ClientSecret: "cs", + sms.Region: "region", + }, + } + confNoClientKey = &sms.Config{ + Metadata: map[string]string{ + sms.ClientSecret: "cs", + sms.Region: "region", + }, + } + confNoClientSecret = &sms.Config{ + Metadata: map[string]string{ + sms.ClientKey: "ck", + sms.Region: "region", + }, + } + confNoRegion = &sms.Config{ + Metadata: map[string]string{ + sms.ClientKey: "ck", + sms.ClientSecret: "cs", + }, + } +) + +var ( + requestSuccess = &sms.SendSmsWithTemplateRequest{ + PhoneNumbers: []string{"+0010001000100"}, + Template: &sms.Template{TemplateParams: map[string]string{"0": "param"}}, + } + requestNoPhoneNumbers = &sms.SendSmsWithTemplateRequest{ + Template: &sms.Template{TemplateParams: map[string]string{"0": "param"}}, + } + requestNoTemplate = &sms.SendSmsWithTemplateRequest{ + PhoneNumbers: []string{"+0010001000100"}, + } + requestWithWrongTemplate1 = &sms.SendSmsWithTemplateRequest{ + PhoneNumbers: []string{"+0010001000100"}, + Template: &sms.Template{TemplateParams: map[string]string{"idx": "param"}}, + } + requestWithWrongTemplate2 = &sms.SendSmsWithTemplateRequest{ + PhoneNumbers: []string{"+0010001000100"}, + Template: &sms.Template{TemplateParams: map[string]string{"-1": "param"}}, + } + requestFailed = &sms.SendSmsWithTemplateRequest{ + PhoneNumbers: []string{"+0010001000100"}, + Template: &sms.Template{ + TemplateId: "-1", + TemplateParams: map[string]string{"0": "param"}, + }, + } +) + +func testInit(t *testing.T) { + svc := tencentcloud.NewSms() + err := svc.Init(ctx, confSuccess) + assert.NoError(t, err) +} + +func testInitNoClientKey(t *testing.T) { + svc := tencentcloud.NewSms() + err := svc.Init(ctx, confNoClientKey) + assert.Error(t, err) +} + +func testInitNoClientSecret(t *testing.T) { + svc := tencentcloud.NewSms() + err := svc.Init(ctx, confNoClientSecret) + assert.Error(t, err) +} + +func testInitNoRegion(t *testing.T) { + svc := tencentcloud.NewSms() + err := svc.Init(ctx, confNoRegion) + assert.Error(t, err) +} + +func TestSms_Init(t *testing.T) { + t.Run("TestInit", testInit) + t.Run("TestInitNoClientKey", testInitNoClientKey) + t.Run("TestInitNoClientSecret", testInitNoClientSecret) + t.Run("TestInitNoRegion", testInitNoRegion) +} + +func testSendSms(t *testing.T) { + _, err := mockSms.SendSmsWithTemplate(ctx, requestSuccess) + assert.NoError(t, err) +} + +func testSendSmsNotInit(t *testing.T) { + svc := tencentcloud.NewSms() + _, err := svc.SendSmsWithTemplate(ctx, requestSuccess) + assert.Error(t, err) +} + +func testSendSmsNoPhoneNumbers(t *testing.T) { + _, err := mockSms.SendSmsWithTemplate(ctx, requestNoPhoneNumbers) + assert.Error(t, err) +} + +func testSendSmsNoTemplate(t *testing.T) { + _, err := mockSms.SendSmsWithTemplate(ctx, requestNoTemplate) + assert.Error(t, err) +} + +func testSendSmsWithWrongTemplate(t *testing.T) { + _, err := mockSms.SendSmsWithTemplate(ctx, requestWithWrongTemplate1) + assert.Error(t, err) + _, err = mockSms.SendSmsWithTemplate(ctx, requestWithWrongTemplate2) + assert.Error(t, err) +} + +func testSendSmsFailed(t *testing.T) { + _, err := mockSms.SendSmsWithTemplate(ctx, requestFailed) + assert.Error(t, err) +} + +func TestSms_SendSmsWithTemplate(t *testing.T) { + t.Run("TestSendSms", testSendSms) + t.Run("TestSendSmsNotInit", testSendSmsNotInit) + t.Run("TestSendSmsNoPhoneNumbers", testSendSmsNoPhoneNumbers) + t.Run("TestSendSmsNoTemplate", testSendSmsNoTemplate) + t.Run("TestSendSmsWithWrongTemplate", testSendSmsWithWrongTemplate) + t.Run("TestSendSmsFailed", testSendSmsFailed) +} From ff3c965eaa8b9b1be73f51aa7c3e095fa90304c7 Mon Sep 17 00:00:00 2001 From: alilestera Date: Sat, 14 Oct 2023 09:36:10 +0800 Subject: [PATCH 4/5] feat: provide client demo for tencentcloud sms --- cmd/layotto/main.go | 7 +++ cmd/layotto_multiple_api/main.go | 7 +++ cmd/layotto_without_xds/main.go | 7 +++ configs/config_sms_tencentcloud.json | 82 ++++++++++++++++++++++++++++ demo/sms/tencentcloud/client.go | 47 ++++++++++++++++ go.mod | 2 + go.sum | 4 ++ 7 files changed, 156 insertions(+) create mode 100644 configs/config_sms_tencentcloud.json create mode 100644 demo/sms/tencentcloud/client.go diff --git a/cmd/layotto/main.go b/cmd/layotto/main.go index a27246d87b..b96cb156f8 100644 --- a/cmd/layotto/main.go +++ b/cmd/layotto/main.go @@ -23,6 +23,8 @@ import ( "strconv" "time" + "mosn.io/layotto/components/sms" + "mosn.io/layotto/components/cryption" "mosn.io/layotto/components/email" @@ -46,6 +48,7 @@ import ( aliyun_file "mosn.io/layotto/components/file/aliyun" aliyun_email "mosn.io/layotto/components/email/aliyun" + tencentcloud_sms "mosn.io/layotto/components/sms/tencentcloud" "github.com/dapr/components-contrib/secretstores" "github.com/dapr/components-contrib/secretstores/aws/parameterstore" @@ -320,6 +323,10 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp runtime.WithEmailServiceFactory( email.NewFactory("aliyun.email", aliyun_email.NewAliyunEmail), ), + // Sms + runtime.WithSmsServiceFactory( + sms.NewFactory("tencentcloud.sms", tencentcloud_sms.NewSms), + ), // PubSub runtime.WithPubSubFactory( pubsub.NewFactory("redis", func() dapr_comp_pubsub.PubSub { diff --git a/cmd/layotto_multiple_api/main.go b/cmd/layotto_multiple_api/main.go index 436157e02c..1856926a2c 100644 --- a/cmd/layotto_multiple_api/main.go +++ b/cmd/layotto_multiple_api/main.go @@ -23,9 +23,12 @@ import ( "strconv" "time" + "mosn.io/layotto/components/sms" + "mosn.io/layotto/components/cryption" aliyun_cryption "mosn.io/layotto/components/cryption/aliyun" aws_cryption "mosn.io/layotto/components/cryption/aws" + tencentcloud_sms "mosn.io/layotto/components/sms/tencentcloud" "mosn.io/layotto/pkg/grpc/lifecycle" @@ -424,6 +427,10 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp cryption.NewFactory("aliyun.kms", aliyun_cryption.NewCryption), cryption.NewFactory("aws.kms", aws_cryption.NewCryption), ), + // Sms + runtime.WithSmsServiceFactory( + sms.NewFactory("tencentcloud.sms", tencentcloud_sms.NewSms), + ), // Lock runtime.WithLockFactory( runtime_lock.NewFactory("redis_cluster", func() lock.LockStore { diff --git a/cmd/layotto_without_xds/main.go b/cmd/layotto_without_xds/main.go index 4160daec26..daa277828c 100644 --- a/cmd/layotto_without_xds/main.go +++ b/cmd/layotto_without_xds/main.go @@ -23,9 +23,12 @@ import ( "strconv" "time" + "mosn.io/layotto/components/sms" + "mosn.io/layotto/components/cryption" aliyun_cryption "mosn.io/layotto/components/cryption/aliyun" aws_cryption "mosn.io/layotto/components/cryption/aws" + tencentcloud_sms "mosn.io/layotto/components/sms/tencentcloud" "mosn.io/layotto/pkg/grpc/lifecycle" @@ -428,6 +431,10 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp cryption.NewFactory("aliyun.kms", aliyun_cryption.NewCryption), cryption.NewFactory("aws.kms", aws_cryption.NewCryption), ), + // Sms + runtime.WithSmsServiceFactory( + sms.NewFactory("tencentcloud.sms", tencentcloud_sms.NewSms), + ), // Sequencer runtime.WithSequencerFactory( runtime_sequencer.NewFactory("etcd", func() sequencer.Store { diff --git a/configs/config_sms_tencentcloud.json b/configs/config_sms_tencentcloud.json new file mode 100644 index 0000000000..f07246bf7a --- /dev/null +++ b/configs/config_sms_tencentcloud.json @@ -0,0 +1,82 @@ +{ + "servers": [ + { + "default_log_path": "stdout", + "default_log_level": "DEBUG", + "listeners": [ + { + "name": "grpc", + "address": "127.0.0.1:34904", + "bind_port": true, + "filter_chains": [ + { + "filters": [ + { + "type": "grpc", + "config": { + "server_name": "runtime", + "grpc_config": { + "sms": { + "sms_demo": { + "type": "tencentcloud.sms", + "metadata": { + "accessKeyID": "access_key", + "accessKeySecret": "access_secret", + "region": "region" + } + } + } + } + } + } + ] + } + ] + } + ] + } + ], + "dynamic_resources": { + "lds_config": { + "ads": {}, + "initial_fetch_timeout": "0s", + "resource_api_version": "V3" + }, + "cds_config": { + "ads": {}, + "initial_fetch_timeout": "0s", + "resource_api_version": "V3" + }, + "ads_config": { + "api_type": "GRPC", + "set_node_on_first_message_only": true, + "transport_api_version": "V3", + "grpc_services": [{ + "envoy_grpc": { + "cluster_name": "xds-grpc" + } + }] + } + }, + "static_resources": { + "clusters": [{ + "name": "xds-grpc", + "type": "STATIC", + "connect_timeout": "1s", + "lb_policy": "ROUND_ROBIN", + "load_assignment": { + "cluster_name": "xds-grpc", + "endpoints": [{ + "lb_endpoints": [{ + "endpoint": { + "address": { + "socket_address": {"address": "127.0.0.1", "port_value": 30681} + } + } + } + ] + }] + } + }] + } +} diff --git a/demo/sms/tencentcloud/client.go b/demo/sms/tencentcloud/client.go new file mode 100644 index 0000000000..c4b2e5da4e --- /dev/null +++ b/demo/sms/tencentcloud/client.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "fmt" + + "google.golang.org/grpc" + + smsv1 "mosn.io/layotto/spec/proto/extension/v1/sms" +) + +var ( + componentName = "sms_demo" +) + +func main() { + // Dial to the gRPC server + conn, err := grpc.Dial("127.0.0.1:34904", grpc.WithInsecure()) + if err != nil { + fmt.Printf("conn build failed,err: %+v", err) + panic(err) + } + defer conn.Close() + + // Create a new client + client := smsv1.NewSmsServiceClient(conn) + + // Make a request to send sms + request := &smsv1.SendSmsWithTemplateRequest{ + ComponentName: componentName, + PhoneNumbers: []string{"+8610001000100"}, + Template: &smsv1.Template{ + TemplateId: "10000", + TemplateParams: map[string]string{}, + }, + SignName: "sign_name", + Metadata: map[string]string{"SdkAppId": "app_id"}, + } + response, err := client.SendSmsWithTemplate(context.Background(), request) + if err != nil { + fmt.Printf("send sms failed: %+v", err) + panic(err) + } + + // Print the result of the SendSmsWithTemplate response + fmt.Println(response) +} diff --git a/go.mod b/go.mod index e46a10e642..c4747c4b7b 100644 --- a/go.mod +++ b/go.mod @@ -246,6 +246,8 @@ require ( github.com/streadway/amqp v1.0.0 // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759 // indirect github.com/tencentyun/cos-go-sdk-v5 v0.7.33 // indirect github.com/tjfoc/gmsm v1.3.2 // indirect github.com/tklauser/go-sysconf v0.3.6 // indirect diff --git a/go.sum b/go.sum index 5cc77094cc..15eead0a85 100644 --- a/go.sum +++ b/go.sum @@ -1275,7 +1275,11 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/supplyon/gremcos v0.1.0/go.mod h1:ZnXsXGVbGCYDFU5GLPX9HZLWfD+ZWkiPo30KUjNoOtw= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759 h1:flWgFybB3MYWFxwRO4yXbdiPT3SNwjSLuXCXsfs6kN4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.759/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759 h1:GrpwnCcbPYewCj/lvyAWEShTprW+5CVWrU+89UyjXcs= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.759/go.mod h1:PEv4IxnbGqYejMUrUxawrlKHQUiAvgEXHpYP4Rfa538= github.com/tencentyun/cos-go-sdk-v5 v0.7.33 h1:5jmJU7U/1nf/7ZPDkrUL8KlF1oDUzTHsdtLNY6x0hq4= github.com/tencentyun/cos-go-sdk-v5 v0.7.33/go.mod h1:4E4+bQ2gBVJcgEC9Cufwylio4mXOct2iu05WjgEBx1o= github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= From 51b3232f09f8915e8395def1ee07dbea057dfadc Mon Sep 17 00:00:00 2001 From: alilestera Date: Sat, 14 Oct 2023 09:39:00 +0800 Subject: [PATCH 5/5] fix: add copyright comments for tencentcloud sms client demo --- demo/sms/tencentcloud/client.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/demo/sms/tencentcloud/client.go b/demo/sms/tencentcloud/client.go index c4b2e5da4e..4c9921ceac 100644 --- a/demo/sms/tencentcloud/client.go +++ b/demo/sms/tencentcloud/client.go @@ -1,3 +1,16 @@ +// Copyright 2021 Layotto Authors +// 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 main import (