From 68f92dd1b19ebf3cbdf8e7f0f50edde213f5e523 Mon Sep 17 00:00:00 2001 From: Ashang Date: Mon, 4 Apr 2022 16:18:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E4=BA=A4=E6=98=93=E6=B5=81=E7=A8=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=B0=86=E9=87=91=E9=A2=9D=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=BF=9D=E7=95=993=E4=BD=8D=E5=B0=8F=E6=95=B0?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.go | 8 +- src/go.mod | 4 +- src/go.sum | 5 + src/model/data/order_data.go | 61 ++++------ src/model/request/order_request.go | 2 +- src/model/service/order_service.go | 148 +++++++++--------------- src/model/service/task_service.go | 56 +++++---- src/mq/handle/order_expiration_queue.go | 4 +- src/task/usdt_rate_job.go | 8 +- src/util/math/math.go | 16 +++ 10 files changed, 144 insertions(+), 168 deletions(-) create mode 100644 src/util/math/math.go diff --git a/src/config/config.go b/src/config/config.go index 54bbea7..1a98119 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/spf13/viper" "os" + "time" ) var ( @@ -53,7 +54,7 @@ func Init() { } func GetAppVersion() string { - return "0.0.1" + return "0.0.2" } func GetAppName() string { @@ -90,3 +91,8 @@ func GetOrderExpirationTime() int { } return timer } + +func GetOrderExpirationTimeDuration() time.Duration { + timer := GetOrderExpirationTime() + return time.Minute * time.Duration(timer) +} diff --git a/src/go.mod b/src/go.mod index fc78c0f..40d4a3d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,8 +7,8 @@ require ( github.com/go-resty/resty/v2 v2.7.0 github.com/golang-module/carbon/v2 v2.0.1 github.com/gookit/color v1.5.0 - github.com/gookit/goutil v0.4.4 - github.com/gookit/validate v1.2.11 + github.com/gookit/goutil v0.4.6 + github.com/gookit/validate v1.3.1 github.com/hibiken/asynq v0.22.1 github.com/json-iterator/go v1.1.12 github.com/labstack/echo/v4 v4.6.0 diff --git a/src/go.sum b/src/go.sum index a8fd7b2..2c77e94 100644 --- a/src/go.sum +++ b/src/go.sum @@ -197,8 +197,12 @@ github.com/gookit/filter v1.1.2/go.mod h1:pVXLLDD+A8yH9GRztq2Cp7zwZocnuTUpbZs9Q+ github.com/gookit/goutil v0.3.12/go.mod h1:ITj7Lw0muhJNOX+QRa+j+HH0+RNoQVuTmZx5d5LE1vE= github.com/gookit/goutil v0.4.4 h1:18xr8NKbs4LteyOHZfQ8g7JqTqZal801mAT6LERGdpE= github.com/gookit/goutil v0.4.4/go.mod h1:qlGVh0PI+WnWSjYnIocfz/7tkeogxL6+EDNP1mRe+7o= +github.com/gookit/goutil v0.4.6 h1:LSATnmIyR0rV4BMj3XyaKFYtX/HbRURnPVHt6Zb5ABs= +github.com/gookit/goutil v0.4.6/go.mod h1:pq1eTibwb2wN96jrci0xy7xogWzzo9CihOQJEAvz4yQ= github.com/gookit/validate v1.2.11 h1:zUMsezhMrW3Cy8St3cQJgCKB1ZIbKOWK8e7WMSuVIRc= github.com/gookit/validate v1.2.11/go.mod h1:wXo0Vr+AzFUCEUCbTFXgKlPfT+V/V0wPK3zLp49jQq0= +github.com/gookit/validate v1.3.1 h1:YoxBxG5H+WucKjfEo8X+QvYOzED7ue3Mfpq6jtpIbYs= +github.com/gookit/validate v1.3.1/go.mod h1:mzcr3U5XtAlQVAgbW0wbmDGiuefU/MNuDkXFMBNb1ko= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -370,6 +374,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/src/model/data/order_data.go b/src/model/data/order_data.go index 8a25b00..d66c5b1 100644 --- a/src/model/data/order_data.go +++ b/src/model/data/order_data.go @@ -8,11 +8,11 @@ import ( "github.com/assimon/luuu/model/request" "github.com/go-redis/redis/v8" "gorm.io/gorm" + "time" ) var ( - CacheWalletAddressToAvailableAmountKey = "WalletAddressToAvailableAmountKey:" // 钱包 - 可用金额 - 过期时间 - CacheWalletAddressToOrdersKey = "WalletAddressToOrdersKey:" // 钱包 - 待支付金额 - 订单号 + CacheWalletAddressWithAmountToTradeIdKey = "wallet:%s_%v" // 钱包_待支付金额 : 交易号 ) // GetOrderInfoByOrderId 通过客户订单号查询订单 @@ -78,36 +78,11 @@ func UpdateOrderIsExpirationById(id uint64) error { return err } -// LockPayCache 锁定支付池 -func LockPayCache(token, tradeId, amount string, expirationTime int64) error { - // 载入Redis +// GetTradeIdByWalletAddressAndAmount 通过钱包地址,支付金额获取交易号 +func GetTradeIdByWalletAddressAndAmount(token string, amount float64) (string, error) { ctx := context.Background() - pipe := dao.Rdb.TxPipeline() - lockAvailableAmountKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token) - // 占用金额 - pipe.HSet(ctx, lockAvailableAmountKey, amount, expirationTime) - // 标记金额对应订单号 - lockWalletOrdersKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token) - pipe.HSet(ctx, lockWalletOrdersKey, amount, tradeId) - _, err := pipe.Exec(ctx) - return err -} - -// ClearPayCache 清理支付池 -func ClearPayCache(token, amount string) error { - ctx := context.Background() - lockAvailableAmountKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token) - lockWalletOrdersKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token) - pipe := dao.Rdb.TxPipeline() - pipe.HDel(ctx, lockAvailableAmountKey, amount) - pipe.HDel(ctx, lockWalletOrdersKey, amount) - _, err := pipe.Exec(ctx) - return err -} - -func GetTradeIdByAmount(ctx context.Context, token, amount string) (string, error) { - cacheKey := fmt.Sprintf("%s%s", CacheWalletAddressToOrdersKey, token) - result, err := dao.Rdb.HGet(ctx, cacheKey, amount).Result() + cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount) + result, err := dao.Rdb.Get(ctx, cacheKey).Result() if err == redis.Nil { return "", nil } @@ -117,14 +92,18 @@ func GetTradeIdByAmount(ctx context.Context, token, amount string) (string, erro return result, nil } -func GetExpirationTimeByAmount(ctx context.Context, token, amount string) (string, error) { - cacheKey := fmt.Sprintf("%s%s", CacheWalletAddressToAvailableAmountKey, token) - result, err := dao.Rdb.HGet(ctx, cacheKey, amount).Result() - if err == redis.Nil { - return "", nil - } - if err != nil { - return "", err - } - return result, nil +// LockTransaction 锁定交易 +func LockTransaction(token, tradeId string, amount float64, expirationTime time.Duration) error { + ctx := context.Background() + cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount) + err := dao.Rdb.Set(ctx, cacheKey, tradeId, expirationTime).Err() + return err +} + +// UnLockTransaction 解锁交易 +func UnLockTransaction(token string, amount float64) error { + ctx := context.Background() + cacheKey := fmt.Sprintf(CacheWalletAddressWithAmountToTradeIdKey, token, amount) + err := dao.Rdb.Del(ctx, cacheKey).Err() + return err } diff --git a/src/model/request/order_request.go b/src/model/request/order_request.go index 4df1eda..3fdf049 100644 --- a/src/model/request/order_request.go +++ b/src/model/request/order_request.go @@ -23,7 +23,7 @@ func (r CreateTransactionRequest) Translates() map[string]string { // OrderProcessingRequest 订单处理 type OrderProcessingRequest struct { Token string - Amount string + Amount float64 TradeId string BlockTransactionId string } diff --git a/src/model/service/order_service.go b/src/model/service/order_service.go index 46ad79d..81a1858 100644 --- a/src/model/service/order_service.go +++ b/src/model/service/order_service.go @@ -1,7 +1,6 @@ package service import ( - "context" "fmt" "github.com/assimon/luuu/config" "github.com/assimon/luuu/model/dao" @@ -11,35 +10,40 @@ import ( "github.com/assimon/luuu/model/response" "github.com/assimon/luuu/mq" "github.com/assimon/luuu/mq/handle" - "github.com/assimon/luuu/telegram" "github.com/assimon/luuu/util/constant" + "github.com/assimon/luuu/util/math" "github.com/golang-module/carbon/v2" - "github.com/gookit/goutil/mathutil" "github.com/hibiken/asynq" "github.com/shopspring/decimal" "math/rand" - "strconv" "sync" "time" ) +const ( + CnyMinimumPaymentAmount = 0.01 // cny最低支付金额 + UsdtMinimumPaymentAmount = 0.001 // usdt最低支付金额 + UsdtAmountPerIncrement = 0.001 // usdt每次递增金额 + IncrementalMaximumNumber = 100 // 最大递增次数 +) + var gCreateTransactionLock sync.Mutex // CreateTransaction 创建订单 func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateTransactionResponse, error) { gCreateTransactionLock.Lock() defer gCreateTransactionLock.Unlock() - // 汇率计算金额 - rmb := decimal.NewFromFloat(req.Amount) - rate := decimal.NewFromFloat(config.GetUsdtRate()) - amount := rmb.Div(rate).InexactFloat64() - actualAmountStr := fmt.Sprintf("%.4f", amount) - actualAmountFloat, err := strconv.ParseFloat(actualAmountStr, 64) - if err != nil { - return nil, err + payAmount := math.MustParsePrecFloat64(req.Amount, 2) + // 按照汇率转化USDT + decimalPayAmount := decimal.NewFromFloat(payAmount) + decimalRate := decimal.NewFromFloat(config.GetUsdtRate()) + decimalUsdt := decimalPayAmount.Div(decimalRate) + // cny 是否可以满足最低支付金额 + if decimalPayAmount.Cmp(decimal.NewFromFloat(CnyMinimumPaymentAmount)) == -1 { + return nil, constant.PayAmountErr } - // 是否可以满足最低支付金额 - if actualAmountFloat <= 0 { + // Usdt是否可以满足最低支付金额 + if decimalUsdt.Cmp(decimal.NewFromFloat(UsdtMinimumPaymentAmount)) == -1 { return nil, constant.PayAmountErr } // 已经存在了的交易 @@ -58,23 +62,20 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT if len(walletAddress) <= 0 { return nil, constant.NotAvailableWalletAddress } - availableToken, availableAmountStr, err := CalculateAvailableWalletTokenAndAmount(actualAmountStr, walletAddress) + amount := math.MustParsePrecFloat64(decimalUsdt.InexactFloat64(), 3) + availableToken, availableAmount, err := CalculateAvailableWalletAndAmount(amount, walletAddress) if err != nil { return nil, err } - if availableToken == "" || availableAmountStr == "" { + if availableToken == "" { return nil, constant.NotAvailableAmountErr } - availableAmountFloat, err := strconv.ParseFloat(availableAmountStr, 64) - if err != nil { - return nil, err - } tx := dao.Mdb.Begin() order := &mdb.Orders{ TradeId: GenerateCode(), OrderId: req.OrderId, Amount: req.Amount, - ActualAmount: availableAmountFloat, + ActualAmount: availableAmount, Token: availableToken, Status: mdb.StatusWaitPay, NotifyUrl: req.NotifyUrl, @@ -85,9 +86,8 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT tx.Rollback() return nil, err } - ExpirationTime := carbon.Now().AddMinutes(config.GetOrderExpirationTime()).Timestamp() // 锁定支付池 - err = data.LockPayCache(availableToken, order.TradeId, availableAmountStr, ExpirationTime) + err = data.LockTransaction(availableToken, order.TradeId, availableAmount, config.GetOrderExpirationTimeDuration()) if err != nil { tx.Rollback() return nil, err @@ -95,7 +95,8 @@ func CreateTransaction(req *request.CreateTransactionRequest) (*response.CreateT tx.Commit() // 超时过期消息队列 orderExpirationQueue, _ := handle.NewOrderExpirationQueue(order.TradeId) - mq.MClient.Enqueue(orderExpirationQueue, asynq.ProcessIn(time.Minute*time.Duration(config.GetOrderExpirationTime()))) + mq.MClient.Enqueue(orderExpirationQueue, asynq.ProcessIn(config.GetOrderExpirationTimeDuration())) + ExpirationTime := carbon.Now().AddMinutes(config.GetOrderExpirationTime()).Timestamp() resp := &response.CreateTransactionResponse{ TradeId: order.TradeId, OrderId: order.OrderId, @@ -125,88 +126,51 @@ func OrderProcessing(req *request.OrderProcessingRequest) error { tx.Rollback() return err } - err = data.ClearPayCache(req.Token, req.Amount) - tx.Commit() - order, err := data.GetOrderInfoByTradeId(req.TradeId) + // 解锁交易 + err = data.UnLockTransaction(req.Token, req.Amount) if err != nil { + tx.Rollback() return err } - // 回调队列 - orderCallbackQueue, _ := handle.NewOrderCallbackQueue(order) - mq.MClient.Enqueue(orderCallbackQueue, asynq.MaxRetry(5)) - // 发送机器人消息 - msgTpl := ` -📢📢有新的交易支付成功! -
交易号:%s
-
订单号:%s
-
请求支付金额:%.4f cny
-
实际支付金额:%.4f usdt
-
钱包地址:%s
-
订单创建时间:%s
-
支付成功时间:%s
-` - msg := fmt.Sprintf(msgTpl, order.TradeId, order.OrderId, order.Amount, order.ActualAmount, order.Token, order.CreatedAt.ToDateTimeString(), carbon.Now().ToDateTimeString()) - telegram.SendToBot(msg) + tx.Commit() return nil } -func CalculateAvailableWalletTokenAndAmount(amount string, walletAddress []mdb.WalletAddress) (string, string, error) { - calculateAmountStr := amount - availableAmountStr := "" +// CalculateAvailableWalletAndAmount 计算可用钱包地址和金额 +func CalculateAvailableWalletAndAmount(amount float64, walletAddress []mdb.WalletAddress) (string, float64, error) { availableToken := "" - for i := 0; i < 100; i++ { - token, err := CalculateAvailableWalletToken(calculateAmountStr, walletAddress) - if err != nil { - return "", "", err + availableAmount := amount + calculateAvailableWalletFunc := func(amount float64) (string, error) { + availableWallet := "" + for _, address := range walletAddress { + token := address.Token + result, err := data.GetTradeIdByWalletAddressAndAmount(token, amount) + if err != nil { + return "", err + } + if result == "" { + availableWallet = token + break + } } - // 这个金额没有拿到可用的钱包,重试,金额+0.0001 + return availableWallet, nil + } + for i := 0; i < IncrementalMaximumNumber; i++ { + token, err := calculateAvailableWalletFunc(availableAmount) + if err != nil { + return "", 0, err + } + // 拿不到可用钱包就累加金额 if token == "" { - x, err := decimal.NewFromString(calculateAmountStr) - if err != nil { - return "", "", err - } - y, err := decimal.NewFromString("0.0001") - if err != nil { - return "", "", err - } - calculateAmountStr = x.Add(y).String() + decimalOldAmount := decimal.NewFromFloat(availableAmount) + decimalIncr := decimal.NewFromFloat(UsdtAmountPerIncrement) + availableAmount = decimalOldAmount.Add(decimalIncr).InexactFloat64() continue } - availableAmountStr = calculateAmountStr availableToken = token break } - return availableToken, availableAmountStr, nil -} - -// CalculateAvailableWalletToken 计算可用钱包token -func CalculateAvailableWalletToken(payAmount string, walletAddress []mdb.WalletAddress) (string, error) { - nowTime := time.Now().Unix() - ctx := context.Background() - walletToken := "" - for _, address := range walletAddress { - result, err := data.GetExpirationTimeByAmount(ctx, address.Token, payAmount) - if err != nil { - return "", err - } - // 这个钱包金额被占用了 - if result != "" { - endTime := mathutil.MustInt64(result) - // 但是过期了 - if endTime < nowTime { - // 删掉过期,还能继续用这个地址 - err = data.ClearPayCache(address.Token, payAmount) - if err != nil { - return "", err - } - } else { - continue - } - } - walletToken = address.Token - break - } - return walletToken, nil + return availableToken, availableAmount, nil } // GenerateCode 订单号生成 diff --git a/src/model/service/task_service.go b/src/model/service/task_service.go index e8ff3d6..804d0d1 100644 --- a/src/model/service/task_service.go +++ b/src/model/service/task_service.go @@ -1,20 +1,21 @@ package service import ( - "context" "fmt" "github.com/assimon/luuu/model/data" "github.com/assimon/luuu/model/request" + "github.com/assimon/luuu/mq" + "github.com/assimon/luuu/mq/handle" + "github.com/assimon/luuu/telegram" "github.com/assimon/luuu/util/http_client" "github.com/assimon/luuu/util/json" "github.com/assimon/luuu/util/log" "github.com/golang-module/carbon/v2" - "github.com/gookit/goutil/mathutil" "github.com/gookit/goutil/stdutil" + "github.com/hibiken/asynq" "github.com/shopspring/decimal" "net/http" "sync" - "time" ) const UsdtTrc20ApiUri = "https://apilist.tronscan.org/api/token_trc20/transfers" @@ -90,38 +91,33 @@ func Trc20CallBack(token string, wg *sync.WaitGroup) { if trc20Resp.Total <= 0 { return } - ctx := context.Background() - nowTime := time.Now().Unix() for _, transfer := range trc20Resp.TokenTransfers { if transfer.ToAddress != token || transfer.FinalResult != "SUCCESS" { continue } - x, _ := decimal.NewFromString(transfer.Quant) - y, _ := decimal.NewFromString("1000000") - quant := x.Div(y).InexactFloat64() - amount := fmt.Sprintf("%.4f", quant) - result, err := data.GetExpirationTimeByAmount(ctx, token, amount) + decimalQuant, err := decimal.NewFromString(transfer.Quant) if err != nil { panic(err) } - if result != "" { - expTime := mathutil.MustInt64(result) - // 但是过期了 - if expTime < nowTime { - // 删掉过期 - _ = data.ClearPayCache(token, amount) - continue - } - } - // 该钱包下有无匹配金额订单 - tradeId, err := data.GetTradeIdByAmount(ctx, token, amount) + decimalDivisor := decimal.NewFromFloat(1000000) + amount := decimalQuant.Div(decimalDivisor).InexactFloat64() + tradeId, err := data.GetTradeIdByWalletAddressAndAmount(token, amount) if err != nil { panic(err) } if tradeId == "" { continue } - // 到这一步就匹配到金额了 + order, err := data.GetOrderInfoByTradeId(tradeId) + if err != nil { + panic(err) + } + // 区块的确认时间必须在订单创建时间之后 + createTime := order.CreatedAt.TimestampWithMillisecond() + if transfer.BlockTs < createTime { + panic("Orders cannot actually be matched") + } + // 到这一步就完全算是支付成功了 req := &request.OrderProcessingRequest{ Token: token, TradeId: tradeId, @@ -132,5 +128,21 @@ func Trc20CallBack(token string, wg *sync.WaitGroup) { if err != nil { panic(err) } + // 回调队列 + orderCallbackQueue, _ := handle.NewOrderCallbackQueue(order) + mq.MClient.Enqueue(orderCallbackQueue, asynq.MaxRetry(5)) + // 发送机器人消息 + msgTpl := ` +📢📢有新的交易支付成功! +
交易号:%s
+
订单号:%s
+
请求支付金额:%f cny
+
实际支付金额:%f usdt
+
钱包地址:%s
+
订单创建时间:%s
+
支付成功时间:%s
+` + msg := fmt.Sprintf(msgTpl, order.TradeId, order.OrderId, order.Amount, order.ActualAmount, order.Token, order.CreatedAt.ToDateTimeString(), carbon.Now().ToDateTimeString()) + telegram.SendToBot(msg) } } diff --git a/src/mq/handle/order_expiration_queue.go b/src/mq/handle/order_expiration_queue.go index 8ce2344..9100796 100644 --- a/src/mq/handle/order_expiration_queue.go +++ b/src/mq/handle/order_expiration_queue.go @@ -2,7 +2,6 @@ package handle import ( "context" - "fmt" "github.com/assimon/luuu/model/data" "github.com/assimon/luuu/model/mdb" "github.com/hibiken/asynq" @@ -28,8 +27,7 @@ func OrderExpirationHandle(ctx context.Context, t *asynq.Task) error { if err != nil { return err } - actualAmount := fmt.Sprintf("%.4f", orderInfo.ActualAmount) - err = data.ClearPayCache(orderInfo.Token, actualAmount) + err = data.UnLockTransaction(orderInfo.Token, orderInfo.ActualAmount) if err != nil { return err } diff --git a/src/task/usdt_rate_job.go b/src/task/usdt_rate_job.go index 6e2b5e4..90a8891 100644 --- a/src/task/usdt_rate_job.go +++ b/src/task/usdt_rate_job.go @@ -1,12 +1,11 @@ package task import ( - "fmt" "github.com/assimon/luuu/config" "github.com/assimon/luuu/util/http_client" "github.com/assimon/luuu/util/json" "github.com/assimon/luuu/util/log" - "strconv" + "github.com/assimon/luuu/util/math" "time" ) @@ -56,10 +55,7 @@ func (r UsdtRateJob) Run() { } for _, points := range usdtResp.Data.Points { if len(points.C) > 0 && points.C[0] > 0 { - config.UsdtRate, err = strconv.ParseFloat(fmt.Sprintf("%.2f", points.C[0]), 64) - if err != nil { - log.Sugar.Error("Calculate UsdtRate err:", err.Error()) - } + config.UsdtRate = math.MustParsePrecFloat64(points.C[0], 2) return } } diff --git a/src/util/math/math.go b/src/util/math/math.go new file mode 100644 index 0000000..2a99319 --- /dev/null +++ b/src/util/math/math.go @@ -0,0 +1,16 @@ +package math + +import ( + "fmt" + "strconv" +) + +// MustParsePrecFloat64 按小数位截取float +func MustParsePrecFloat64(value float64, prec int) float64 { + format := fmt.Sprintf("%%.%df", prec) + v, err := strconv.ParseFloat(fmt.Sprintf(format, value), 64) + if err != nil { + return 0 + } + return v +}