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{}), } }