feat: add template
This commit is contained in:
parent
2921bc9869
commit
727f949f50
|
@ -14,16 +14,20 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
NoneReq = notification.NoneReq
|
||||
OKResp = notification.OKResp
|
||||
SendMailReq = notification.SendMailReq
|
||||
SendSMSReq = notification.SendSMSReq
|
||||
NoneReq = notification.NoneReq
|
||||
OKResp = notification.OKResp
|
||||
SendMailReq = notification.SendMailReq
|
||||
SendSMSReq = notification.SendSMSReq
|
||||
TemplateReq = notification.TemplateReq
|
||||
TemplateResp = notification.TemplateResp
|
||||
|
||||
SenderService interface {
|
||||
// SendMail 寄信
|
||||
SendMail(ctx context.Context, in *SendMailReq, opts ...grpc.CallOption) (*OKResp, error)
|
||||
// SendSms 寄簡訊
|
||||
SendSms(ctx context.Context, in *SendSMSReq, opts ...grpc.CallOption) (*OKResp, error)
|
||||
// 取得 Template
|
||||
GetStaticTemplate(ctx context.Context, in *TemplateReq, opts ...grpc.CallOption) (*TemplateResp, error)
|
||||
}
|
||||
|
||||
defaultSenderService struct {
|
||||
|
@ -48,3 +52,9 @@ func (m *defaultSenderService) SendSms(ctx context.Context, in *SendSMSReq, opts
|
|||
client := notification.NewSenderServiceClient(m.cli.Conn())
|
||||
return client.SendSms(ctx, in, opts...)
|
||||
}
|
||||
|
||||
// 取得 Template
|
||||
func (m *defaultSenderService) GetStaticTemplate(ctx context.Context, in *TemplateReq, opts ...grpc.CallOption) (*TemplateResp, error) {
|
||||
client := notification.NewSenderServiceClient(m.cli.Conn())
|
||||
return client.GetStaticTemplate(ctx, in, opts...)
|
||||
}
|
||||
|
|
|
@ -22,11 +22,24 @@ message SendSMSReq {
|
|||
string recipient_name=3;
|
||||
}
|
||||
|
||||
message TemplateReq {
|
||||
string language = 1;
|
||||
string template_id = 2;
|
||||
}
|
||||
|
||||
message TemplateResp {
|
||||
string title = 1;
|
||||
string body = 2;
|
||||
}
|
||||
|
||||
|
||||
service SenderService {
|
||||
// SendMail 寄信
|
||||
rpc SendMail(SendMailReq) returns(OKResp);
|
||||
// SendSms 寄簡訊
|
||||
rpc SendSms(SendSMSReq) returns(OKResp);
|
||||
// 取得 Template
|
||||
rpc GetStaticTemplate(TemplateReq) returns(TemplateResp);
|
||||
}
|
||||
|
||||
|
||||
|
|
19
go.mod
19
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.61
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.30.0
|
||||
github.com/matcornic/hermes/v2 v2.1.0
|
||||
github.com/minchao/go-mitake v1.0.0
|
||||
github.com/zeromicro/go-zero v1.7.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
|
@ -16,6 +17,11 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2 // indirect
|
||||
github.com/Masterminds/sprig v2.16.0+incompatible // indirect
|
||||
github.com/PuerkitoBio/goquery v1.5.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.0.0 // indirect
|
||||
github.com/aokoli/goutils v1.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
|
@ -40,15 +46,21 @@ require (
|
|||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/huandu/xstrings v1.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // 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
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.10.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
|
@ -57,7 +69,13 @@ require (
|
|||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect
|
||||
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||
|
@ -76,6 +94,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/sync v0.7.0 // indirect
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package senderservicelogic
|
||||
|
||||
import (
|
||||
"app-cloudep-notification-service/pkg/domain/template"
|
||||
"context"
|
||||
|
||||
"app-cloudep-notification-service/gen_result/pb/notification"
|
||||
"app-cloudep-notification-service/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetStaticTemplateLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewGetStaticTemplateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetStaticTemplateLogic {
|
||||
return &GetStaticTemplateLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
// GetStaticTemplate 取得 Template
|
||||
func (l *GetStaticTemplateLogic) GetStaticTemplate(in *notification.TemplateReq) (*notification.TemplateResp, error) {
|
||||
tmp, err := l.svcCtx.TemplateUseCase.GetEmailTemplateByStatic(
|
||||
l.ctx,
|
||||
template.Language(in.GetLanguage()),
|
||||
template.Type(in.GetTemplateId()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ¬ification.TemplateResp{
|
||||
Title: tmp.Title,
|
||||
Body: tmp.Body,
|
||||
}, nil
|
||||
}
|
|
@ -34,3 +34,9 @@ func (s *SenderServiceServer) SendSms(ctx context.Context, in *notification.Send
|
|||
l := senderservicelogic.NewSendSmsLogic(ctx, s.svcCtx)
|
||||
return l.SendSms(in)
|
||||
}
|
||||
|
||||
// 取得 Template
|
||||
func (s *SenderServiceServer) GetStaticTemplate(ctx context.Context, in *notification.TemplateReq) (*notification.TemplateResp, error) {
|
||||
l := senderservicelogic.NewGetStaticTemplateLogic(ctx, s.svcCtx)
|
||||
return l.GetStaticTemplate(in)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
DeliveryUseCase useD.DeliveryUseCase
|
||||
TemplateUseCase useD.TemplateUseCase
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
|
@ -80,5 +81,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||
return &ServiceContext{
|
||||
Config: c,
|
||||
DeliveryUseCase: uc,
|
||||
TemplateUseCase: usecase.MustTemplateUseCase(usecase.TemplateUseCaseParam{}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package template
|
||||
|
||||
// ==============================
|
||||
// 產品資訊常數
|
||||
// ==============================
|
||||
|
||||
const (
|
||||
ProductName = "TrueHeart 團隊"
|
||||
ProductLink = "https://code.30cm.net"
|
||||
ProductLogo = "https://true-heart-dev.s3.ap-northeast-3.amazonaws.com/f70904eb-1a29-40f7-8940-9a124f23793a.png"
|
||||
ProductCopyright = "© 2025 TrueHeart Inc. 版權所有"
|
||||
)
|
|
@ -0,0 +1,128 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"github.com/matcornic/hermes/v2"
|
||||
)
|
||||
|
||||
// ==============================
|
||||
// Email 結構定義
|
||||
// ==============================
|
||||
|
||||
// EmailTemplate 代表 Email 樣板
|
||||
type EmailTemplate struct {
|
||||
Title string
|
||||
Body string
|
||||
}
|
||||
|
||||
// ProductInfo 包含產品相關的資訊
|
||||
type ProductInfo struct {
|
||||
Name string
|
||||
Link string
|
||||
Logo string
|
||||
Copyright string
|
||||
}
|
||||
|
||||
// EmailBodyContent 包含郵件正文的資訊
|
||||
type EmailBodyContent struct {
|
||||
RecipientName string
|
||||
Intros []string
|
||||
Actions []hermes.Action
|
||||
Outros []string
|
||||
Signature string
|
||||
}
|
||||
|
||||
// EmailContentParams 組成 Email 內容的參數
|
||||
type EmailContentParams struct {
|
||||
Product ProductInfo
|
||||
Content EmailBodyContent
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// Email 內容產生器
|
||||
// ==============================
|
||||
|
||||
// GenerateEmailBody 根據參數產生 Email HTML 內容
|
||||
func GenerateEmailBody(params EmailContentParams) (string, error) {
|
||||
product := hermes.Product{
|
||||
Name: params.Product.Name,
|
||||
Link: params.Product.Link,
|
||||
Logo: params.Product.Logo,
|
||||
Copyright: params.Product.Copyright,
|
||||
}
|
||||
|
||||
body := hermes.Body{
|
||||
Name: params.Content.RecipientName,
|
||||
Intros: params.Content.Intros,
|
||||
Actions: params.Content.Actions,
|
||||
Outros: params.Content.Outros,
|
||||
Signature: params.Content.Signature,
|
||||
}
|
||||
|
||||
h := hermes.Hermes{Product: product}
|
||||
email := hermes.Email{Body: body}
|
||||
|
||||
return h.GenerateHTML(email)
|
||||
}
|
||||
|
||||
// GenerateEmailContent 生成 Email 內容(可用於不同驗證類型)
|
||||
func GenerateEmailContent(title string, intros []string) (EmailTemplate, error) {
|
||||
req := EmailContentParams{
|
||||
Product: ProductInfo{
|
||||
Name: ProductName,
|
||||
Link: ProductLink,
|
||||
Logo: ProductLogo,
|
||||
Copyright: ProductCopyright,
|
||||
},
|
||||
Content: EmailBodyContent{
|
||||
RecipientName: "{{.Username}}",
|
||||
Intros: intros,
|
||||
Actions: []hermes.Action{
|
||||
{
|
||||
Instructions: "請複製您的驗證碼,到網頁重置",
|
||||
InviteCode: "{{.VerifyCode}}",
|
||||
},
|
||||
},
|
||||
Outros: []string{
|
||||
"如果您沒有請求此操作,請忽略此郵件。",
|
||||
},
|
||||
Signature: "",
|
||||
},
|
||||
}
|
||||
|
||||
emailBody, err := GenerateEmailBody(req)
|
||||
if err != nil {
|
||||
return EmailTemplate{}, err
|
||||
}
|
||||
|
||||
return EmailTemplate{
|
||||
Title: title,
|
||||
Body: emailBody,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// Email 產生函數
|
||||
// ==============================
|
||||
|
||||
// GenerateForgetPasswordEmailZHTW 生成繁體中文的忘記密碼驗證信
|
||||
func GenerateForgetPasswordEmailZHTW() (EmailTemplate, error) {
|
||||
return GenerateEmailContent("TrueHeart 重設密碼驗證信",
|
||||
[]string{"您收到此電子郵件是因為我們收到了針對帳戶的密碼重置請求。"})
|
||||
}
|
||||
|
||||
// GenerateBindingEmailZHTW 生成綁定帳號驗證信
|
||||
func GenerateBindingEmailZHTW() (EmailTemplate, error) {
|
||||
return GenerateEmailContent("TrueHeart 綁定信箱驗證信",
|
||||
[]string{"您收到此電子郵件是因為我們收到了針對帳戶的 Email 認證請求。"})
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// Email 模板對應表
|
||||
// ==============================
|
||||
|
||||
var EmailTemplateMap = map[Language]map[Type]func() (EmailTemplate, error){
|
||||
LanguageZhTW: {
|
||||
ForgetPasswordVerify: GenerateForgetPasswordEmailZHTW,
|
||||
BindingEmail: GenerateBindingEmailZHTW,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package template
|
||||
|
||||
// ==============================
|
||||
// 語言與驗證碼類型
|
||||
// ==============================
|
||||
|
||||
type Language string
|
||||
|
||||
const (
|
||||
LanguageZhTW Language = "zh-tw"
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package template
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
BindingEmail Type = "binding_email" // 驗證碼:綁定 Email
|
||||
BindingPhone Type = "binding_phone" // 驗證碼:綁定 手機
|
||||
ForgetPasswordVerify Type = "forget_password" // 驗證碼: 忘記密碼
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"app-cloudep-notification-service/pkg/domain/template"
|
||||
"context"
|
||||
)
|
||||
|
||||
type TemplateUseCase interface {
|
||||
GetEmailTemplateByStatic(ctx context.Context, language template.Language, templateID template.Type) (template.EmailTemplate, error)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"app-cloudep-notification-service/pkg/domain/template"
|
||||
"app-cloudep-notification-service/pkg/domain/usecase"
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type TemplateUseCaseParam struct{}
|
||||
|
||||
type TemplateUseCase struct {
|
||||
TemplateUseCaseParam
|
||||
}
|
||||
|
||||
func MustTemplateUseCase(param TemplateUseCaseParam) usecase.TemplateUseCase {
|
||||
return &TemplateUseCase{
|
||||
param,
|
||||
}
|
||||
}
|
||||
|
||||
func (use *TemplateUseCase) GetEmailTemplateByStatic(_ context.Context, language template.Language, templateID template.Type) (template.EmailTemplate, error) {
|
||||
// 查找指定語言的模板映射
|
||||
templateByLang, exists := template.EmailTemplateMap[language]
|
||||
if !exists {
|
||||
return template.EmailTemplate{}, fmt.Errorf("email template not found for language: %s", language)
|
||||
}
|
||||
|
||||
// 查找指定類型的模板生成函數
|
||||
templateFunc, exists := templateByLang[templateID]
|
||||
if !exists {
|
||||
return template.EmailTemplate{}, fmt.Errorf("email template not found for type ID: %s", templateID)
|
||||
}
|
||||
|
||||
// 執行模板生成函數
|
||||
tmp, err := templateFunc()
|
||||
if err != nil {
|
||||
return template.EmailTemplate{}, fmt.Errorf("error generating email template: %w", err)
|
||||
}
|
||||
|
||||
// 返回構建好的響應
|
||||
return tmp, nil
|
||||
}
|
Loading…
Reference in New Issue