Skip to content

Commit

Permalink
修复:优化创建交易流程代码,将金额修改为保留3位小数点
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashang committed Apr 4, 2022
1 parent 20c576b commit 68f92dd
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 165 deletions.
8 changes: 7 additions & 1 deletion src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/spf13/viper"
"os"
"time"
)

var (
Expand Down Expand Up @@ -53,7 +54,7 @@ func Init() {
}

func GetAppVersion() string {
return "0.0.1"
return "0.0.2"
}

func GetAppName() string {
Expand Down Expand Up @@ -90,3 +91,8 @@ func GetOrderExpirationTime() int {
}
return timer
}

func GetOrderExpirationTimeDuration() time.Duration {
timer := GetOrderExpirationTime()
return time.Minute * time.Duration(timer)
}
4 changes: 2 additions & 2 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
61 changes: 20 additions & 41 deletions src/model/data/order_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 通过客户订单号查询订单
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion src/model/request/order_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
142 changes: 53 additions & 89 deletions src/model/service/order_service.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package service

import (
"context"
"fmt"
"github.com/assimon/luuu/config"
"github.com/assimon/luuu/model/dao"
Expand All @@ -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
}
// 已经存在了的交易
Expand All @@ -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,
Expand All @@ -85,17 +86,17 @@ 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
}
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,
Expand Down Expand Up @@ -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 := `
<b>📢📢有新的交易支付成功!</b>
<pre>交易号:%s</pre>
<pre>订单号:%s</pre>
<pre>请求支付金额:%.4f cny</pre>
<pre>实际支付金额:%.4f usdt</pre>
<pre>钱包地址:%s</pre>
<pre>订单创建时间:%s</pre>
<pre>支付成功时间:%s</pre>
`
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
}
// 这个金额没有拿到可用的钱包,重试,金额+0.0001
if token == "" {
x, err := decimal.NewFromString(calculateAmountStr)
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
return "", err
}
y, err := decimal.NewFromString("0.0001")
if err != nil {
return "", "", err
if result == "" {
availableWallet = token
break
}
calculateAmountStr = x.Add(y).String()
continue
}
availableAmountStr = calculateAmountStr
availableToken = token
break
return availableWallet, nil
}
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)
for i := 0; i < IncrementalMaximumNumber; i++ {
token, err := calculateAvailableWalletFunc(availableAmount)
if err != nil {
return "", err
return "", 0, err
}
// 这个钱包金额被占用了
if result != "" {
endTime := mathutil.MustInt64(result)
// 但是过期了
if endTime < nowTime {
// 删掉过期,还能继续用这个地址
err = data.ClearPayCache(address.Token, payAmount)
if err != nil {
return "", err
}
} else {
continue
}
// 拿不到可用钱包就累加金额
if token == "" {
decimalOldAmount := decimal.NewFromFloat(availableAmount)
decimalIncr := decimal.NewFromFloat(UsdtAmountPerIncrement)
availableAmount = decimalOldAmount.Add(decimalIncr).InexactFloat64()
continue
}
walletToken = address.Token
availableToken = token
break
}
return walletToken, nil
return availableToken, availableAmount, nil
}

// GenerateCode 订单号生成
Expand Down
Loading

0 comments on commit 68f92dd

Please sign in to comment.