add wallet

This commit is contained in:
王性驊 2025-04-10 17:33:09 +08:00
parent 9caa5b1f13
commit 2f5b04de29
9 changed files with 207 additions and 0 deletions

4
go.mod
View File

@ -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
View File

@ -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=

1
pkg/domain/const.go Normal file
View File

@ -0,0 +1 @@
package domain

View File

@ -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" // 對應的資料表名稱
}

1
pkg/domain/error.go Normal file
View File

@ -0,0 +1 @@
package domain

View File

@ -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
}

View File

@ -0,0 +1,3 @@
package wallet
type BusinessName string

View File

@ -0,0 +1,15 @@
package wallet
type Types int8
const (
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
TypeFreezeType // 被凍結金額(交易進行中或風控鎖住的金額)
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
// 以下為進階用途:合約或模擬交易錢包
TypeContractAvailable // 合約系統的可用金額
TypeContractFreeze // 合約中被凍結的金額
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
TypeSimulationFreeze // 模擬交易凍結金額
)

54
readme.md Normal file
View File

@ -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)