add wallet
This commit is contained in:
parent
9caa5b1f13
commit
2f5b04de29
4
go.mod
4
go.mod
|
@ -3,9 +3,11 @@ module code.30cm.net/digimon/app-cloudep-wallet-service
|
|||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/zeromicro/go-zero v1.8.2
|
||||
google.golang.org/grpc v1.71.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -31,6 +33,8 @@ require (
|
|||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
|
|
8
go.sum
8
go.sum
|
@ -66,6 +66,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
|
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
@ -123,6 +127,8 @@ github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0
|
|||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
|
@ -259,6 +265,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
package domain
|
|
@ -0,0 +1,34 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// 🔐 重點設計理念:
|
||||
// 避免競爭與資金衝突
|
||||
// 將錢包用途拆分成不同 type,避免一個錢包同時處理可用資金與凍結資金,降低邏輯錯誤風險。
|
||||
//
|
||||
// 資金安全性與追蹤性強
|
||||
// 分別記錄「凍結」「未確認」「可用」等不同狀態,方便核對資產總額與可提資金。
|
||||
//
|
||||
// 合約與模擬隔離
|
||||
// 保留合約交易與模擬交易專屬錢包,實現清楚的邊界控制與風險隔離。
|
||||
//
|
||||
// 多品牌支援
|
||||
// Brand 欄位讓平台可以支援多租戶架構(不同品牌有不同的錢包群組)。
|
||||
|
||||
type Wallet struct {
|
||||
ID int64 `gorm:"column:id"` // 主鍵 ID(錢包的唯一識別)
|
||||
Brand string `gorm:"column:brand"` // 品牌/平台(區分多租戶、多品牌情境)
|
||||
UID string `gorm:"column:uid"` // 使用者 UID
|
||||
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
||||
Balance decimal.Decimal `gorm:"column:balance"` // 餘額(使用高精度 decimal 避免浮點誤差)
|
||||
Type wallet.Types `gorm:"column:type"` // 錢包類型
|
||||
CreateTime int64 `gorm:"column:create_time;autoCreateTime"` // 建立時間(UnixNano timestamp)
|
||||
UpdateTime int64 `gorm:"column:update_time;autoUpdateTime"` // 更新時間(UnixNano timestamp)
|
||||
}
|
||||
|
||||
func (c *Wallet) TableName() string {
|
||||
return "wallet" // 對應的資料表名稱
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package domain
|
|
@ -0,0 +1,87 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||
"context"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
Brand string `gorm:"column:brand"` // 品牌/平台(區分多租戶、多品牌情境)
|
||||
UID string `gorm:"column:uid"` // 使用者 UID
|
||||
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
||||
Balance decimal.Decimal `gorm:"column:balance"` // 餘額(使用高精度 decimal 避免浮點誤差)
|
||||
Type wallet.Types `gorm:"column:type"` // 錢包類型
|
||||
}
|
||||
|
||||
// WalletRepository 是錢包的總入口,負責查詢、初始化與跨帳戶查詢邏輯
|
||||
type WalletRepository interface {
|
||||
// NewDB 建立新的 DB 實例(提供給需要操作 tx 的場景)
|
||||
NewDB() *gorm.DB
|
||||
// Session 取得單一使用者資產的錢包服務(非交易模式)
|
||||
//📌 使用場景:
|
||||
// 用在 不需要交易機制的場景,例如:
|
||||
// 純查詢錢包餘額,查詢快取、Log、統計報表,非敏感資料更新,失敗可以重試的情境
|
||||
// ✅ 優點:
|
||||
// 簡單快速、使用預設的資料庫連線
|
||||
// 不用包 Transaction ,沒有 Rollback 負擔
|
||||
Session(uid, asset string) UserWalletService
|
||||
// SessionWithTx 在資料庫交易內取得錢包服務
|
||||
// 📌 使用場景:
|
||||
// 用在 資料需要一致性與原子性保證的邏輯中,例如:
|
||||
// 加值與扣款(同時操作多個錢包)檢查餘額後立刻寫入交易記錄,綁定訂單與錢包扣款的行為
|
||||
// 所有與 Add/Commit 有關的處理,與其他模組(訂單、KYC)共用一個 transaction
|
||||
// ✅ 優點:
|
||||
// 保障操作過程中不被其他並發操作影響
|
||||
// 可控制 rollback 行為避免中間失敗導致不一致
|
||||
// 可組合複雜操作(如:更新錢包同時寫入交易紀錄)
|
||||
SessionWithTx(db *gorm.DB, uid, asset string) UserWalletService
|
||||
// Transaction 資料庫交易包裝器(確保交易一致性)
|
||||
Transaction(fn func(db *gorm.DB) error) error
|
||||
// InitWallets 初始化使用者的所有錢包類型(如可用、凍結等)
|
||||
InitWallets(ctx context.Context, param []Wallet) error
|
||||
// QueryBalances 查詢特定資產的錢包餘額
|
||||
QueryBalances(ctx context.Context, req BalanceQuery) ([]entity.Wallet, error)
|
||||
// QueryBalancesByUIDs 查詢多個使用者在特定資產下的錢包餘額
|
||||
QueryBalancesByUIDs(ctx context.Context, uids []string, req BalanceQuery) ([]entity.Wallet, error)
|
||||
//// GetDailyTxAmount 查詢使用者今日交易總金額(指定類型與業務)
|
||||
//GetDailyTxAmount(ctx context.Context, uid string, txTypes []domain.TxType, business wallet.BusinessName) ([]entity.Wallet, error)
|
||||
}
|
||||
|
||||
// BalanceQuery 是查詢餘額時的篩選條件
|
||||
type BalanceQuery struct {
|
||||
UID string // 使用者 ID
|
||||
Asset string // 資產類型(Crypto、寶石等)
|
||||
Kinds []wallet.Types // 錢包類型(如可用、凍結等)
|
||||
}
|
||||
|
||||
// UserWalletService 專注於某位使用者在單一資產下的錢包操作邏輯
|
||||
type UserWalletService interface {
|
||||
// Init 初始化錢包(如建立可用、凍結、未確認等錢包)
|
||||
Init(ctx context.Context, uid, asset, brand string) ([]entity.Wallet, error)
|
||||
// All 查詢所有錢包餘額
|
||||
All(ctx context.Context) ([]entity.Wallet, error)
|
||||
// Get 查詢單一或多種類型的餘額
|
||||
Get(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error)
|
||||
// GetWithLock 查詢鎖定後的錢包(交易使用)
|
||||
GetWithLock(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error)
|
||||
// LocalBalance 查詢記憶中的快取值(非查資料庫)
|
||||
LocalBalance(kind wallet.Types) decimal.Decimal
|
||||
// LockByIDs 根據錢包 ID 鎖定(資料一致性用)
|
||||
LockByIDs(ctx context.Context, ids []int64) ([]entity.Wallet, error)
|
||||
// CheckReady 檢查錢包是否已經存在並準備好
|
||||
CheckReady(ctx context.Context) (bool, error)
|
||||
// Add 加值與扣款邏輯(含業務類別)
|
||||
Add(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
||||
Sub(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
||||
// AddTransaction 新增一筆交易紀錄(建立資料)
|
||||
AddTransaction(txID int64, orderID string, brand string, business wallet.BusinessName, kind wallet.Types, amount decimal.Decimal)
|
||||
//// PendingTransactions 查詢尚未執行的交易清單(會在 Execute 中一次提交)
|
||||
//PendingTransactions() []entity.WalletTransaction
|
||||
|
||||
// Commit 提交所有操作(更新錢包與新增交易紀錄)
|
||||
Commit(ctx context.Context) error
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package wallet
|
||||
|
||||
type BusinessName string
|
|
@ -0,0 +1,15 @@
|
|||
package wallet
|
||||
|
||||
type Types int8
|
||||
|
||||
const (
|
||||
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
|
||||
TypeFreezeType // 被凍結金額(交易進行中或風控鎖住的金額)
|
||||
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
|
||||
// 以下為進階用途:合約或模擬交易錢包
|
||||
|
||||
TypeContractAvailable // 合約系統的可用金額
|
||||
TypeContractFreeze // 合約中被凍結的金額
|
||||
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
|
||||
TypeSimulationFreeze // 模擬交易凍結金額
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
// 充值-增加可用餘額
|
||||
v1.POST("deposit", wallet.Deposit)
|
||||
|
||||
// 充值-增加限制餘額
|
||||
v1.POST("deposit/unconfirmed", wallet.DepositUnconfirmed)
|
||||
|
||||
// 提現-減少可用餘額
|
||||
v1.POST("withdraw", wallet.Withdraw)
|
||||
|
||||
// 凍結-減少可用餘額,加在凍結餘額
|
||||
v1.POST("freeze", wallet.Freeze)
|
||||
|
||||
// 追加凍結(原凍結金額上)-減少可用餘額,加在凍結餘額
|
||||
v1.POST("freeze/append", wallet.AppendFreeze)
|
||||
|
||||
// 解凍-減少凍結餘額
|
||||
v1.POST("unfreeze", wallet.UnFreeze)
|
||||
|
||||
// rollback凍結-減少凍結餘額,加回可用餘額,不可指定金額
|
||||
v1.POST("freeze/rollback", wallet.RollbackFreeze)
|
||||
|
||||
// rollback凍結-rollback凍結餘額,指定金額加回可用餘額
|
||||
v1.POST("freeze/rollback/add", wallet.RollbackFreezeAddAvailable)
|
||||
|
||||
// 取消凍結-減少凍結餘額,加回可用餘額,可指定金額
|
||||
v1.POST("freeze/cancel", wallet.CancelFreeze)
|
||||
|
||||
// 限制-減少凍結餘額,加別人限制餘額
|
||||
v1.POST("unconfirmed", wallet.Unconfirmed)
|
||||
|
||||
// 合約劃轉
|
||||
v1.POST("contract/transfer", wallet.ContractTransfer)
|
||||
|
||||
// 系統劃轉
|
||||
v1.POST("system-transfer", wallet.systemTransfer)
|
||||
|
||||
// 餘額
|
||||
v1.GET("balance/:uid/user", wallet.Balance)
|
||||
|
||||
// 歷史餘額
|
||||
v1.GET("balance/history/:uid/user", wallet.HistoryBalance)
|
||||
|
||||
// 資產
|
||||
v1.GET("assets/balance/:uid/user", wallet.BalanceByAssets)
|
||||
|
||||
// 檢查餘額
|
||||
v1.POST("balance", wallet.CheckBalance)
|
||||
|
||||
// 取得今日已提現金額
|
||||
v1.GET("withdraw/today/user/:uid", wallet.GetTodayWithdraw)
|
||||
|
||||
// 合約平台餘額
|
||||
v1.GET("balance/contract/system", wallet.ContractSystemBalance)
|
Loading…
Reference in New Issue