From 73ed8628671c1ad21733f70dafd7f175f3b25c94 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 20 Aug 2024 21:08:00 +0800 Subject: [PATCH] feat: add main sender and sms base sender --- .gitignore | 2 +- client/senderservice/sender_service.go | 12 +++--- generate/protobuf/service.proto | 8 ++-- go.mod | 10 +++++ internal/config/config.go | 12 ++++++ internal/domain/usecase/mail.go | 18 ++++++++ internal/domain/usecase/sms.go | 18 ++++++++ .../logic/senderservice/send_mail_logic.go | 37 ++++++++++++++++- .../logic/senderservice/send_sms_logic.go | 35 +++++++++++++++- .../senderservice/sender_service_server.go | 7 ++-- internal/svc/service_context.go | 16 +++++++- internal/usecase/mitake.go | 35 ++++++++++++++++ internal/usecase/smtp.go | 41 +++++++++++++++++++ 13 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 internal/domain/usecase/mail.go create mode 100644 internal/domain/usecase/sms.go create mode 100644 internal/usecase/mitake.go create mode 100644 internal/usecase/smtp.go diff --git a/.gitignore b/.gitignore index 61a2d61..0a56a16 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ go.sum account/ gen_result/ etc/service.yaml -./client \ No newline at end of file +client/ \ No newline at end of file diff --git a/client/senderservice/sender_service.go b/client/senderservice/sender_service.go index a647688..33bcb18 100644 --- a/client/senderservice/sender_service.go +++ b/client/senderservice/sender_service.go @@ -22,11 +22,11 @@ type ( SenderService interface { // SendMail 寄信 SendMail(ctx context.Context, in *SendMailReq, opts ...grpc.CallOption) (*OKResp, error) - // SendSMS 寄簡訊 + // SendSms 寄簡訊 SendSms(ctx context.Context, in *SendSMSReq, opts ...grpc.CallOption) (*OKResp, error) - // SendMailByTemplateID 寄送模板信件 + // SendMailByTemplateId 寄送模板信件 SendMailByTemplateId(ctx context.Context, in *SendByTemplateIDReq, opts ...grpc.CallOption) (*OKResp, error) - // SendSMSByTemplateID 寄送模板簡訊 + // SendSmsByTemplateId 寄送模板簡訊 SendSmsByTemplateId(ctx context.Context, in *SendByTemplateIDReq, opts ...grpc.CallOption) (*OKResp, error) } @@ -47,19 +47,19 @@ func (m *defaultSenderService) SendMail(ctx context.Context, in *SendMailReq, op return client.SendMail(ctx, in, opts...) } -// SendSMS 寄簡訊 +// SendSms 寄簡訊 func (m *defaultSenderService) SendSms(ctx context.Context, in *SendSMSReq, opts ...grpc.CallOption) (*OKResp, error) { client := notification.NewSenderServiceClient(m.cli.Conn()) return client.SendSms(ctx, in, opts...) } -// SendMailByTemplateID 寄送模板信件 +// SendMailByTemplateId 寄送模板信件 func (m *defaultSenderService) SendMailByTemplateId(ctx context.Context, in *SendByTemplateIDReq, opts ...grpc.CallOption) (*OKResp, error) { client := notification.NewSenderServiceClient(m.cli.Conn()) return client.SendMailByTemplateId(ctx, in, opts...) } -// SendSMSByTemplateID 寄送模板簡訊 +// SendSmsByTemplateId 寄送模板簡訊 func (m *defaultSenderService) SendSmsByTemplateId(ctx context.Context, in *SendByTemplateIDReq, opts ...grpc.CallOption) (*OKResp, error) { client := notification.NewSenderServiceClient(m.cli.Conn()) return client.SendSmsByTemplateId(ctx, in, opts...) diff --git a/generate/protobuf/service.proto b/generate/protobuf/service.proto index 2d84dce..e3d5304 100644 --- a/generate/protobuf/service.proto +++ b/generate/protobuf/service.proto @@ -13,11 +13,13 @@ message SendMailReq { string to = 1; string subject = 2; string body = 3; + string from =4; } message SendSMSReq { string to = 1; string body = 2; + string recipient_name=3; } message SendByTemplateIDReq { @@ -31,11 +33,11 @@ message SendByTemplateIDReq { service SenderService { // SendMail 寄信 rpc SendMail(SendMailReq) returns(OKResp); - // SendSMS 寄簡訊 + // SendSms 寄簡訊 rpc SendSms(SendSMSReq) returns(OKResp); - // SendMailByTemplateID 寄送模板信件 + // SendMailByTemplateId 寄送模板信件 rpc SendMailByTemplateId(SendByTemplateIDReq) returns(OKResp); - // SendSMSByTemplateID 寄送模板簡訊 + // SendSmsByTemplateId 寄送模板簡訊 rpc SendSmsByTemplateId(SendByTemplateIDReq) returns(OKResp); } diff --git a/go.mod b/go.mod index a339282..eee482d 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.22.3 require ( code.30cm.net/digimon/library-go/errors v1.0.0 + code.30cm.net/digimon/library-go/validator v1.0.0 + github.com/minchao/go-mitake v1.0.0 github.com/zeromicro/go-zero v1.7.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) require ( @@ -19,11 +22,15 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/fatih/color v1.17.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -34,6 +41,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -66,6 +74,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sys v0.22.0 // indirect @@ -74,6 +83,7 @@ require ( golang.org/x/time v0.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/internal/config/config.go b/internal/config/config.go index c1f85b9..3521805 100755 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,4 +4,16 @@ import "github.com/zeromicro/go-zero/zrpc" type Config struct { zrpc.RpcServerConf + + SMTP struct { + Host string + Port int + User string + Password string + } + + SMSSender struct { + User string + Password string + } } diff --git a/internal/domain/usecase/mail.go b/internal/domain/usecase/mail.go new file mode 100644 index 0000000..95fb400 --- /dev/null +++ b/internal/domain/usecase/mail.go @@ -0,0 +1,18 @@ +package usecase + +import "context" + +type MailClientUseCase interface { + SendMail(ctx context.Context, req MailReq) error +} + +type MailReq struct { + To string + From string + Subject string + Body string +} + +type MailUseCase interface { + GetMailTemplateByID(ctx context.Context, tid int64) ([]rune, error) +} diff --git a/internal/domain/usecase/sms.go b/internal/domain/usecase/sms.go new file mode 100644 index 0000000..c4e6d66 --- /dev/null +++ b/internal/domain/usecase/sms.go @@ -0,0 +1,18 @@ +package usecase + +import ( + "context" +) + +type SMSClientUseCase interface { + SendSMS(ctx context.Context, req SMSReq) error +} + +type SMSReq struct { + // RecipientAddress 接收者號碼 + RecipientAddress string + // RecipientName 接收者姓名 + RecipientName string + // Body 要傳送的訊息 + Body string +} diff --git a/internal/logic/senderservice/send_mail_logic.go b/internal/logic/senderservice/send_mail_logic.go index 6af7fc1..39ad124 100644 --- a/internal/logic/senderservice/send_mail_logic.go +++ b/internal/logic/senderservice/send_mail_logic.go @@ -1,6 +1,8 @@ package senderservicelogic import ( + "app-cloudep-notification-service/internal/domain/usecase" + ers "code.30cm.net/digimon/library-go/errors" "context" "app-cloudep-notification-service/gen_result/pb/notification" @@ -23,9 +25,42 @@ func NewSendMailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendMail } } +type sendMailReq struct { + // TO 收件者 + To string `validate:"required,email"` + // Subject 信件主旨 + Subject string `validate:"required,max=128"` + // Body 內容 + Body string `validate:"required"` + // From 寄件者 + From string `validate:"required"` +} + // SendMail 寄信 func (l *SendMailLogic) SendMail(in *notification.SendMailReq) (*notification.OKResp, error) { - // todo: add your logic here and delete this line + if err := l.svcCtx.Validate.ValidateAll(&sendMailReq{ + To: in.GetTo(), + Subject: in.GetSubject(), + Body: in.GetBody(), + From: in.GetFrom(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.MailSender.SendMail(l.ctx, usecase.MailReq{ + To: in.GetTo(), + Subject: in.GetSubject(), + From: in.GetFrom(), + Body: in.GetBody(), + }) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "MailSender.SendMail"), + logx.Field("in", in), + logx.Field("err", err), + ).Error(err.Error()) + return nil, ers.ArkInternal("MailSender.SendMail failed to send mail") + } return ¬ification.OKResp{}, nil } diff --git a/internal/logic/senderservice/send_sms_logic.go b/internal/logic/senderservice/send_sms_logic.go index 7e5e4f9..152d890 100644 --- a/internal/logic/senderservice/send_sms_logic.go +++ b/internal/logic/senderservice/send_sms_logic.go @@ -1,6 +1,8 @@ package senderservicelogic import ( + "app-cloudep-notification-service/internal/domain/usecase" + ers "code.30cm.net/digimon/library-go/errors" "context" "app-cloudep-notification-service/gen_result/pb/notification" @@ -23,9 +25,38 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo } } -// SendSMS 寄簡訊 +type sendSMSReq struct { + // RecipientAddress 收件者 + RecipientAddress string `validate:"required,phone"` + // Body 內容 + Body string `validate:"required"` + // RecipientName 收件者信名 + RecipientName string `validate:"required"` +} + +// SendSms 寄簡訊 func (l *SendSmsLogic) SendSms(in *notification.SendSMSReq) (*notification.OKResp, error) { - // todo: add your logic here and delete this line + if err := l.svcCtx.Validate.ValidateAll(&sendSMSReq{ + RecipientName: in.GetTo(), + Body: in.GetBody(), + RecipientAddress: in.GetTo(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.SMSSender.SendSMS(l.ctx, usecase.SMSReq{ + RecipientAddress: in.GetTo(), + RecipientName: in.GetRecipientName(), + Body: in.GetBody(), + }) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "SMSSender.SendSMS"), + logx.Field("in", in), + logx.Field("err", err), + ).Error(err.Error()) + return nil, ers.ArkInternal("SMSSender.SendSMS failed to send sms") + } return ¬ification.OKResp{}, nil } diff --git a/internal/server/senderservice/sender_service_server.go b/internal/server/senderservice/sender_service_server.go index 14d51b9..7300421 100644 --- a/internal/server/senderservice/sender_service_server.go +++ b/internal/server/senderservice/sender_service_server.go @@ -7,7 +7,6 @@ import ( "context" "app-cloudep-notification-service/gen_result/pb/notification" - senderservicelogic "app-cloudep-notification-service/internal/logic/senderservice" "app-cloudep-notification-service/internal/logic/senderservice" "app-cloudep-notification-service/internal/svc" ) @@ -29,19 +28,19 @@ func (s *SenderServiceServer) SendMail(ctx context.Context, in *notification.Sen return l.SendMail(in) } -// SendSMS 寄簡訊 +// SendSms 寄簡訊 func (s *SenderServiceServer) SendSms(ctx context.Context, in *notification.SendSMSReq) (*notification.OKResp, error) { l := senderservicelogic.NewSendSmsLogic(ctx, s.svcCtx) return l.SendSms(in) } -// SendMailByTemplateID 寄送模板信件 +// SendMailByTemplateId 寄送模板信件 func (s *SenderServiceServer) SendMailByTemplateId(ctx context.Context, in *notification.SendByTemplateIDReq) (*notification.OKResp, error) { l := senderservicelogic.NewSendMailByTemplateIdLogic(ctx, s.svcCtx) return l.SendMailByTemplateId(in) } -// SendSMSByTemplateID 寄送模板簡訊 +// SendSmsByTemplateId 寄送模板簡訊 func (s *SenderServiceServer) SendSmsByTemplateId(ctx context.Context, in *notification.SendByTemplateIDReq) (*notification.OKResp, error) { l := senderservicelogic.NewSendSmsByTemplateIdLogic(ctx, s.svcCtx) return l.SendSmsByTemplateId(in) diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index d0f930f..09a29c1 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -1,13 +1,25 @@ package svc -import "app-cloudep-notification-service/internal/config" +import ( + "app-cloudep-notification-service/internal/config" + domainUC "app-cloudep-notification-service/internal/domain/usecase" + "app-cloudep-notification-service/internal/usecase" + v "code.30cm.net/digimon/library-go/validator" +) type ServiceContext struct { Config config.Config + + Validate v.Validate + MailSender domainUC.MailClientUseCase + SMSSender domainUC.SMSClientUseCase } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ - Config: c, + Config: c, + MailSender: usecase.MustMailgunUseCase(usecase.MailUseCaseParam{Conf: c}), + SMSSender: usecase.MustMitakeUseCase(usecase.SMSUseCaseParam{Conf: c}), + Validate: v.MustValidator(), } } diff --git a/internal/usecase/mitake.go b/internal/usecase/mitake.go new file mode 100644 index 0000000..cd8e622 --- /dev/null +++ b/internal/usecase/mitake.go @@ -0,0 +1,35 @@ +package usecase + +import ( + "app-cloudep-notification-service/internal/config" + "app-cloudep-notification-service/internal/domain/usecase" + "context" + "github.com/minchao/go-mitake" +) + +type SMSUseCaseParam struct { + Conf config.Config +} + +type SMSUseCase struct { + Client *mitake.Client +} + +func (s *SMSUseCase) SendSMS(_ context.Context, req usecase.SMSReq) error { + message := mitake.Message{ + Dstaddr: req.RecipientAddress, + Destname: req.RecipientName, + Smbody: req.Body, + } + _, err := s.Client.Send(message) + if err != nil { + return err + } + return nil +} + +func MustMitakeUseCase(param SMSUseCaseParam) usecase.SMSClientUseCase { + return &SMSUseCase{ + Client: mitake.NewClient(param.Conf.SMSSender.User, param.Conf.SMSSender.Password, nil), + } +} diff --git a/internal/usecase/smtp.go b/internal/usecase/smtp.go new file mode 100644 index 0000000..9a10771 --- /dev/null +++ b/internal/usecase/smtp.go @@ -0,0 +1,41 @@ +package usecase + +import ( + "app-cloudep-notification-service/internal/config" + "app-cloudep-notification-service/internal/domain/usecase" + "context" + "gopkg.in/gomail.v2" +) + +type MailUseCaseParam struct { + Conf config.Config +} + +type MailUseCase struct { + Host string + Port int + User string + Password string +} + +func (mu *MailUseCase) SendMail(_ context.Context, req usecase.MailReq) error { + m := gomail.NewMessage() + m.SetHeader("From", req.From) + m.SetHeader("To", req.To) + m.SetHeader("Subject", req.Subject) + m.SetBody("text/html", req.Body) + d := gomail.NewDialer(mu.Host, mu.Port, mu.User, mu.Password) + if err := d.DialAndSend(m); err != nil { + return err + } + return nil +} + +func MustMailgunUseCase(param MailUseCaseParam) usecase.MailClientUseCase { + return &MailUseCase{ + Host: param.Conf.SMTP.Host, + Port: param.Conf.SMTP.Port, + User: param.Conf.SMTP.User, + Password: param.Conf.SMTP.Password, + } +}