app-cloudep-wallet-service/pkg/usecase/wallet.go

306 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package usecase
import (
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/usecase"
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
"code.30cm.net/digimon/library-go/errs"
"context"
"errors"
"github.com/go-sql-driver/mysql"
"github.com/sirupsen/logrus"
"sync"
)
type WalletUseCaseParam struct {
WalletRepo repository.WalletRepository
TransactionRepo repository.TransactionRepository
WalletTransactionRepo repository.WalletTransactionRepo
}
type WalletUseCase struct {
WalletUseCaseParam
// 內存讀寫所記錄有哪些玩家已經確認有存在錢包過了,減少確認錢包是否存在頻率
sync.RWMutex
existUIDAsset map[string]struct{}
}
func (use *WalletUseCase) Process(ctx context.Context, req usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
// Withdraw 提幣
// 1. 新增一筆提幣交易
// 2. 錢包減少可用餘額
// 3. 錢包變化新增一筆減少可用餘額資料
func (use *WalletUseCase) Withdraw(ctx context.Context, tx usecase.WalletTransferRequest) error {
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
tx.TxType = wallet.Withdraw
return use.ProcessTransaction(ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{use.withLockAvailable(), use.withSubAvailable()},
})
}
// Deposit 充值
// 1. 新增一筆充值交易
// 2. 錢包增加可用餘額
// 3. 錢包變化新增一筆增加可用餘額資料
func (use *WalletUseCase) Deposit(ctx context.Context, tx usecase.WalletTransferRequest) error {
// 確認錢包新增或減少的餘額是否正確
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
tx.TxType = wallet.Deposit
uidAsset := uidAssetKey{
uid: tx.FromUID,
asset: tx.Asset,
}
exists := use.checkWalletExistence(uidAsset)
withLockAvailable := func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
if !exists {
checkWallet, err := w.HasAvailableBalance(ctx)
if err != nil {
return err
}
// 錢包不存在要做新增
if !checkWallet {
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
var mysqlErr *mysql.MySQLError
// 解析是否被其他 transaction insert 了,是的話嘗試取得 insert 後的鎖,不是的話需要直接回傳錯誤
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
logrus.WithFields(logrus.Fields{
"err": err,
"uid": tx.FromUID,
}).Warn("Deposit.Create.Wallet")
} else {
return err
}
} else {
// 因為是透過 transaction 新增,所以不用上鎖
return nil
}
} else {
exists = true
use.markWalletAsExisting(uidAsset)
}
}
// 確認有 wallet 再 lock for update避免 deadlock
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeAvailable})
if err != nil {
return err
}
return nil
}
if err := use.ProcessTransaction(
ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{withLockAvailable, use.withAddAvailable()},
}); err != nil {
return err
}
return nil
}
// DepositUnconfirmed 增加限制餘額
// 1. 新增一筆充值限制交易
// 2. 錢包新增限制餘額
// 3. 錢包變化新增一筆增加限制餘額資料
func (use *WalletUseCase) DepositUnconfirmed(ctx context.Context, tx usecase.WalletTransferRequest) error {
// 確認錢包新增或減少的餘額是否正確
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
uidAsset := uidAssetKey{
uid: tx.FromUID,
asset: tx.Asset,
}
exists := use.checkWalletExistence(uidAsset)
tx.TxType = wallet.DepositUnconfirmed
withLockUnconfirmed := func(ctx context.Context, tx *usecase.WalletTransferRequest, w repository.UserWalletService) error {
if !exists {
checkWallet, err := w.HasAvailableBalance(ctx)
if err != nil {
return err
}
// 錢包不存在要做新增
if !checkWallet {
if _, err := w.InitializeWallets(ctx, tx.Brand); err != nil {
var mysqlErr *mysql.MySQLError
// 解析是否被其他 transaction insert 了,是的話嘗試取得 insert 後的鎖,不是的話需要直接回傳錯誤
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
logrus.WithFields(logrus.Fields{
"err": err,
"uid": tx.FromUID,
}).Warn("Deposit.Create.Wallet")
} else {
return err
}
} else {
// 因為是透過 transaction 新增,所以不用上鎖
return nil
}
} else {
exists = true
use.markWalletAsExisting(uidAsset)
}
}
// 確認有 wallet 再 lock for update避免 deadlock
_, err := w.GetBalancesForUpdate(ctx, []wallet.Types{wallet.TypeUnconfirmed})
if err != nil {
return err
}
return nil
}
if err := use.ProcessTransaction(
ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{withLockUnconfirmed, use.withAddUnconfirmed()},
}); err != nil {
return err
}
return nil
}
// Freeze 凍結
// 1. 新增一筆凍結交易
// 2. 錢包減少可用餘額
// 3. 錢包增加凍結餘額
// 4. 錢包變化新增一筆減少可用餘額資料
// 5. 錢包變化新增一筆增加凍結餘額資料
// 6. 訂單錢包新增一筆資料,餘額是凍結金額
func (use *WalletUseCase) Freeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
// 確認錢包新增或減少的餘額是否正確
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
tx.TxType = wallet.DepositUnconfirmed
return use.ProcessTransaction(ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{use.withLockAvailableAndFreeze(), use.withSubAvailable(), use.withAddFreeze()},
})
}
// AppendFreeze 追加凍結金額
// 1. 新增一筆凍結交易
// 2. 錢包減少可用餘額
// 3. 錢包增加凍結餘額
// 4. 錢包變化新增一筆減少可用餘額資料
// 5. 錢包變化新增一筆增加凍結餘額資料
// 6. 原凍結金額上追加凍結金額
func (use *WalletUseCase) AppendFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
// 確認錢包新增或減少的餘額是否正確
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
tx.TxType = wallet.DepositUnconfirmed
return use.ProcessTransaction(ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{use.withLockAvailableAndFreeze(), use.withSubAvailable(), use.withAddFreeze()},
})
}
// UnFreeze 解凍
// 1. 新增一筆解凍交易
// 2. 減少order餘額
// 3. 錢包減少凍結餘額
// 4. 錢包變化新增一筆減少凍結餘額資料
func (use *WalletUseCase) UnFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
// 確認錢包新增或減少的餘額是否正確
if !tx.Amount.IsPositive() {
return errs.InvalidRange("failed to get correct amount")
}
tx.TxType = wallet.UnFreeze
return use.ProcessTransaction(ctx, tx, userWalletFlow{
UID: tx.FromUID,
Asset: tx.Asset,
Actions: []walletActionOption{use.withSubOrder(nil), use.withLockFreeze(), use.withSubFreeze()},
})
}
func (use *WalletUseCase) RollbackFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) RollbackFreezeAddAvailable(ctx context.Context, tx usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) CancelFreeze(ctx context.Context, tx usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) Unconfirmed(ctx context.Context, tx usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) Balance(ctx context.Context, req usecase.BalanceReq) ([]usecase.Balance, error) {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) HistoryBalance(ctx context.Context, req usecase.BalanceReq) ([]usecase.Balance, error) {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) BalanceByAssets(ctx context.Context, uid, cryptoCode string, walletTypes []wallet.Types) (usecase.BalanceAssetsResp, error) {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) CheckBalance(ctx context.Context, tx usecase.WalletTransferRequest) error {
//TODO implement me
panic("implement me")
}
func (use *WalletUseCase) GetTodayWithdraw(ctx context.Context, uid, toCrypto string) (usecase.TodayWithdrawResp, error) {
//TODO implement me
panic("implement me")
}
func MustWalletUseCase(param WalletUseCaseParam) usecase.WalletTransferUseCase {
return &WalletUseCase{
WalletUseCaseParam: param,
existUIDAsset: make(map[string]struct{}),
}
}