feat(exchange): add exchange rate handling and scheduled updates
This commit is contained in:
parent
8562734fde
commit
adbe9a06d8
6
go.mod
6
go.mod
@ -44,7 +44,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.29.0
|
go.opentelemetry.io/otel/sdk v1.29.0
|
||||||
go.opentelemetry.io/otel/trace v1.29.0
|
go.opentelemetry.io/otel/trace v1.29.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.35.0
|
||||||
golang.org/x/oauth2 v0.25.0
|
golang.org/x/oauth2 v0.25.0
|
||||||
golang.org/x/time v0.6.0
|
golang.org/x/time v0.6.0
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
@ -138,8 +138,8 @@ require (
|
|||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -404,6 +404,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
@ -465,6 +467,7 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -480,6 +483,7 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
|||||||
@ -347,6 +347,11 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
|||||||
// Convert cents to decimal amount
|
// Convert cents to decimal amount
|
||||||
amount = float64(src) / float64(100)
|
amount = float64(src) / float64(100)
|
||||||
|
|
||||||
|
if l.svcCtx.ExchangeRate != 0 && to == "CNY" {
|
||||||
|
amount = amount * l.svcCtx.ExchangeRate
|
||||||
|
return amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve system currency configuration
|
// Retrieve system currency configuration
|
||||||
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -36,6 +36,8 @@ type ServiceContext struct {
|
|||||||
Redis *redis.Client
|
Redis *redis.Client
|
||||||
Config config.Config
|
Config config.Config
|
||||||
Queue *asynq.Client
|
Queue *asynq.Client
|
||||||
|
ExchangeRate float64
|
||||||
|
|
||||||
//NodeCache *cache.NodeCacheClient
|
//NodeCache *cache.NodeCacheClient
|
||||||
AuthModel auth.Model
|
AuthModel auth.Model
|
||||||
AdsModel ads.Model
|
AdsModel ads.Model
|
||||||
@ -86,6 +88,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
Redis: rds,
|
Redis: rds,
|
||||||
Config: c,
|
Config: c,
|
||||||
Queue: NewAsynqClient(c),
|
Queue: NewAsynqClient(c),
|
||||||
|
ExchangeRate: 1.0,
|
||||||
//NodeCache: cache.NewNodeCacheClient(rds),
|
//NodeCache: cache.NewNodeCacheClient(rds),
|
||||||
AuthLimiter: authLimiter,
|
AuthLimiter: authLimiter,
|
||||||
AdsModel: ads.NewModel(db, rds),
|
AdsModel: ads.NewModel(db, rds),
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
countrylogic "github.com/perfect-panel/server/queue/logic/country"
|
|
||||||
orderLogic "github.com/perfect-panel/server/queue/logic/order"
|
orderLogic "github.com/perfect-panel/server/queue/logic/order"
|
||||||
smslogic "github.com/perfect-panel/server/queue/logic/sms"
|
smslogic "github.com/perfect-panel/server/queue/logic/sms"
|
||||||
"github.com/perfect-panel/server/queue/logic/subscription"
|
"github.com/perfect-panel/server/queue/logic/subscription"
|
||||||
@ -15,8 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) {
|
func RegisterHandlers(mux *asynq.ServeMux, serverCtx *svc.ServiceContext) {
|
||||||
// get country task
|
|
||||||
mux.Handle(types.ForthwithGetCountry, countrylogic.NewGetNodeCountryLogic(serverCtx))
|
|
||||||
// Send email task
|
// Send email task
|
||||||
mux.Handle(types.ForthwithSendEmail, emailLogic.NewSendEmailLogic(serverCtx))
|
mux.Handle(types.ForthwithSendEmail, emailLogic.NewSendEmailLogic(serverCtx))
|
||||||
// Send sms task
|
// Send sms task
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
package countrylogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetNodeCountryLogic struct {
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetNodeCountryLogic(svcCtx *svc.ServiceContext) *GetNodeCountryLogic {
|
|
||||||
return &GetNodeCountryLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (l *GetNodeCountryLogic) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
52
queue/logic/task/rateLogic.go
Normal file
52
queue/logic/task/rateLogic.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hibiken/asynq"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/exchangeRate"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RateLogic struct {
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRateLogic(svcCtx *svc.ServiceContext) *RateLogic {
|
||||||
|
return &RateLogic{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RateLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error {
|
||||||
|
// Retrieve system currency configuration
|
||||||
|
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("[PurchaseCheckout] GetCurrencyConfig error", logger.Field("error", err.Error()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Parse currency configuration
|
||||||
|
configs := struct {
|
||||||
|
CurrencyUnit string
|
||||||
|
CurrencySymbol string
|
||||||
|
AccessKey string
|
||||||
|
}{}
|
||||||
|
tool.SystemConfigSliceReflectToStruct(currency, &configs)
|
||||||
|
|
||||||
|
// Skip conversion if no exchange rate API key configured
|
||||||
|
if configs.AccessKey == "" {
|
||||||
|
logger.Debugf("[RateLogic] skip exchange rate, no access key configured")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Update exchange rates
|
||||||
|
result, err := exchangeRate.GetExchangeRete(configs.CurrencyUnit, "CNY", configs.AccessKey, 1)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("[RateLogic] GetExchangeRete error", logger.Field("error", err.Error()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.svcCtx.ExchangeRate = result
|
||||||
|
logger.WithContext(ctx).Infof("[RateLogic] GetExchangeRete success, result: %+v", result)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ForthwithGetCountry forthwith country get
|
|
||||||
ForthwithGetCountry = "forthwith:country:get"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetNodeCountry struct {
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
ServerAddr string `json:"server_addr"`
|
|
||||||
}
|
|
||||||
@ -6,4 +6,7 @@ const (
|
|||||||
|
|
||||||
// ForthwithQuotaTask create quota task immediately
|
// ForthwithQuotaTask create quota task immediately
|
||||||
ForthwithQuotaTask = "forthwith:quota:task"
|
ForthwithQuotaTask = "forthwith:quota:task"
|
||||||
|
|
||||||
|
// SchedulerExchangeRate fetch exchange rate task
|
||||||
|
SchedulerExchangeRate = "scheduler:exchange:rate"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -46,6 +46,12 @@ func (m *Service) Start() {
|
|||||||
logger.Errorf("register traffic stat task failed: %s", err.Error())
|
logger.Errorf("register traffic stat task failed: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// schedule update exchange rate task: every day at 01:00
|
||||||
|
rateTask := asynq.NewTask(types.ForthwithQuotaTask, nil)
|
||||||
|
if _, err := m.server.Register("0 1 * * *", rateTask, asynq.MaxRetry(3)); err != nil {
|
||||||
|
logger.Errorf("register update exchange rate task failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if err := m.server.Run(); err != nil {
|
if err := m.server.Run(); err != nil {
|
||||||
logger.Errorf("run scheduler failed: %s", err.Error())
|
logger.Errorf("run scheduler failed: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user