feat: add mysql lib
This commit is contained in:
parent
4cb6faefdc
commit
5214ea2283
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS `wallet`;
|
|
@ -0,0 +1,14 @@
|
||||||
|
CREATE TABLE `wallet` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID(錢包唯一識別)',
|
||||||
|
`brand` VARCHAR(50) NOT NULL COMMENT '品牌/平台(多租戶識別)',
|
||||||
|
`uid` VARCHAR(64) NOT NULL COMMENT '使用者 UID',
|
||||||
|
`asset` VARCHAR(32) NOT NULL COMMENT '資產代碼(如 BTC、ETH、GEM_RED 等)',
|
||||||
|
`balance` DECIMAL(30, 18) UNSIGNED NOT NULL DEFAULT 0 COMMENT '資產餘額',
|
||||||
|
`type` TINYINT NOT NULL COMMENT '錢包類型',
|
||||||
|
`create_at` INTEGER NOT NULL DEFAULT 0 COMMENT '建立時間(Unix)',
|
||||||
|
`update_at` INTEGER NOT NULL DEFAULT 0 COMMENT '更新時間(Unix)',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_brand_uid_asset_type` (`brand`, `uid`, `asset`, `type`),
|
||||||
|
KEY `idx_uid` (`uid`),
|
||||||
|
KEY `idx_brand` (`brand`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='錢包';
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS `transaction`;
|
|
@ -0,0 +1,22 @@
|
||||||
|
CREATE TABLE `transaction` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '交易主鍵 ID,自動遞增',
|
||||||
|
`order_id` VARCHAR(64) DEFAULT NULL COMMENT '關聯的訂單 ID,可為空(若不是由訂單觸發)',
|
||||||
|
`transaction_id` VARCHAR(64) NOT NULL COMMENT '此筆交易的唯一識別碼(系統內部使用,可為 UUID)',
|
||||||
|
`brand` VARCHAR(50) NOT NULL COMMENT '所屬品牌(支援多品牌場景)',
|
||||||
|
`uid` VARCHAR(64) NOT NULL COMMENT '交易發起者的 UID',
|
||||||
|
`to_uid` VARCHAR(64) DEFAULT NULL COMMENT '交易對象的 UID(如為轉帳場景)',
|
||||||
|
`type` TINYINT NOT NULL COMMENT '交易類型(如轉帳、入金、出金等,自定義列舉)',
|
||||||
|
`business_type` TINYINT NOT NULL COMMENT '業務類型(如合約、模擬、一般用途等,數字代碼)',
|
||||||
|
`crypto` VARCHAR(32) NOT NULL COMMENT '幣種(如 BTC、ETH、USD、TWD 等)',
|
||||||
|
`amount` DECIMAL(30, 18) NOT NULL COMMENT '本次變動金額(正數為增加,負數為扣減)',
|
||||||
|
`balance` DECIMAL(30, 18) NOT NULL COMMENT '交易完成後的錢包餘額',
|
||||||
|
`before_balance` DECIMAL(30, 18) NOT NULL COMMENT '交易前的錢包餘額(方便審計與對帳)',
|
||||||
|
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '狀態(1: 有效、0: 無效/已取消)',
|
||||||
|
`create_time` BIGINT NOT NULL DEFAULT 0 COMMENT '建立時間(Unix 秒數)',
|
||||||
|
`due_time` BIGINT NOT NULL DEFAULT 0 COMMENT '到期時間(適用於凍結或延後入帳等場景)',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_transaction_id` (`transaction_id`),
|
||||||
|
KEY `idx_uid` (`uid`),
|
||||||
|
KEY `idx_order_id` (`order_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交易紀錄表';
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS `transaction`;
|
|
@ -0,0 +1,22 @@
|
||||||
|
CREATE TABLE `wallet_transaction` (
|
||||||
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID,自動遞增',
|
||||||
|
`transaction_id` BIGINT NOT NULL COMMENT '交易流水號(可對應某次業務操作,例如同一訂單的多筆變化)',
|
||||||
|
`order_id` VARCHAR(64) NOT NULL COMMENT '訂單編號(對應實際訂單或業務事件)',
|
||||||
|
`brand` VARCHAR(50) NOT NULL COMMENT '品牌(多租戶或多平台識別)',
|
||||||
|
`uid` VARCHAR(64) NOT NULL COMMENT '使用者 UID',
|
||||||
|
`wallet_type` TINYINT NOT NULL COMMENT '錢包類型(如主錢包、獎勵錢包、凍結錢包等)',
|
||||||
|
`business_type` TINYINT NOT NULL COMMENT '業務類型(如購物、退款、加值等)',
|
||||||
|
`asset` VARCHAR(32) NOT NULL COMMENT '資產代號(如 BTC、ETH、GEM_RED、USD 等)',
|
||||||
|
`amount` DECIMAL(30, 18) NOT NULL COMMENT '變動金額(正數為收入,負數為支出)',
|
||||||
|
`balance` DECIMAL(30, 18) NOT NULL COMMENT '當前錢包餘額(這筆交易後的餘額快照)',
|
||||||
|
`create_at` BIGINT NOT NULL DEFAULT 0 COMMENT '建立時間(UnixNano,紀錄交易發生時間)',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KET `idx_uid` (`uid`),
|
||||||
|
KEY `idx_transaction_id` (`transaction_id`),
|
||||||
|
KEY `idx_order_id` (`order_id`),
|
||||||
|
KEY `idx_brand` (`brand`),
|
||||||
|
KEY `idx_wallet_type` (`wallet_type`)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
COLLATE = utf8mb4_unicode_ci
|
||||||
|
COMMENT = '錢包資金異動紀錄(每一次交易行為的快照記錄)';
|
|
@ -0,0 +1 @@
|
||||||
|
DROP DATABASE IF EXISTS `digimon_wallet`;
|
|
@ -0,0 +1 @@
|
||||||
|
CREATE DATABASE IF NOT EXISTS `digimon_wallet`;
|
49
go.mod
49
go.mod
|
@ -4,32 +4,49 @@ go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
|
github.com/testcontainers/testcontainers-go v0.36.0
|
||||||
github.com/zeromicro/go-zero v1.8.2
|
github.com/zeromicro/go-zero v1.8.2
|
||||||
google.golang.org/grpc v1.71.1
|
google.golang.org/grpc v1.71.1
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/docker v28.0.1+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.2 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
@ -38,42 +55,64 @@ require (
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.9 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.1.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/prometheus/client_golang v1.21.1 // indirect
|
github.com/prometheus/client_golang v1.21.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.62.0 // indirect
|
github.com/prometheus/common v0.62.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/oauth2 v0.25.0 // indirect
|
golang.org/x/oauth2 v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/term v0.29.0 // indirect
|
golang.org/x/term v0.29.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.10.0 // indirect
|
golang.org/x/time v0.10.0 // indirect
|
||||||
|
|
108
go.sum
108
go.sum
|
@ -1,3 +1,13 @@
|
||||||
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||||
|
@ -14,26 +24,48 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||||
|
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||||
|
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||||
|
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
|
@ -41,6 +73,9 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
|
||||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
@ -52,9 +87,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
@ -87,6 +123,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||||
|
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
@ -94,17 +134,35 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||||
|
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||||
|
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||||
|
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
||||||
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
@ -113,6 +171,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
|
@ -127,8 +187,12 @@ 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/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 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
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=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
@ -139,6 +203,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
@ -146,11 +211,19 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zeromicro/go-zero v1.8.2 h1:AbJckBoojbr1lqCN1dkvURTIHOau7yvKReEd7ZmjuCk=
|
github.com/zeromicro/go-zero v1.8.2 h1:AbJckBoojbr1lqCN1dkvURTIHOau7yvKReEd7ZmjuCk=
|
||||||
github.com/zeromicro/go-zero v1.8.2/go.mod h1:G5dF+jzCEuq0t1j8qdrtVAy30QMgctGcKSfqFIGsvSg=
|
github.com/zeromicro/go-zero v1.8.2/go.mod h1:G5dF+jzCEuq0t1j8qdrtVAy30QMgctGcKSfqFIGsvSg=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||||
|
@ -161,8 +234,10 @@ go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||||
|
@ -175,14 +250,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDO
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
@ -198,6 +273,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
@ -216,14 +293,20 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
|
@ -265,8 +348,13 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||||
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "github.com/zeromicro/go-zero/zrpc"
|
import (
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
zrpc.RpcServerConf
|
zrpc.RpcServerConf
|
||||||
|
|
||||||
|
MySQL struct {
|
||||||
|
UserName string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
MaxIdleConns int
|
||||||
|
MaxOpenConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
LogLevel string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transaction 代表一筆錢包交易紀錄(例如充值、扣款、轉帳等)
|
||||||
|
// 此表記錄所有交易的詳細資訊,包括金額、對象、餘額狀態與交易型態等
|
||||||
|
type Transaction struct {
|
||||||
|
ID int64 `gorm:"column:id"` // 交易主鍵 ID,自動遞增
|
||||||
|
OrderID string `gorm:"column:order_id"` // 關聯的訂單 ID,可為空(若不是由訂單觸發)
|
||||||
|
TransactionID string `gorm:"column:transaction_id"` // 此筆交易的唯一識別碼(系統內部使用,可為 UUID)
|
||||||
|
Brand string `gorm:"column:brand"` // 所屬品牌(支援多品牌場景)
|
||||||
|
UID string `gorm:"column:uid"` // 交易發起者的 UID
|
||||||
|
ToUID string `gorm:"column:to_uid"` // 交易對象的 UID(如為轉帳場景)
|
||||||
|
Type wallet.TxType `gorm:"column:type"` // 交易類型(如轉帳、入金、出金等,自定義列舉)
|
||||||
|
BusinessType int8 `gorm:"column:business_type"` // 業務類型(如合約、模擬、一般用途等,數字代碼)
|
||||||
|
Crypto string `gorm:"column:crypto"` // 幣種(如 BTC、ETH、USD、TWD 等)
|
||||||
|
Amount decimal.Decimal `gorm:"column:amount"` // 本次變動金額(正數為增加,負數為扣減)
|
||||||
|
Balance decimal.Decimal `gorm:"column:balance"` // 交易完成後的錢包餘額
|
||||||
|
BeforeBalance decimal.Decimal `gorm:"column:before_balance"` // 交易前的錢包餘額(方便審計與對帳)
|
||||||
|
Status wallet.Enable `gorm:"column:status"` // 狀態(1: 有效、0: 無效/已取消)
|
||||||
|
CreateAt int64 `gorm:"column:create_time;autoCreateTime"` // 建立時間(Unix 秒數)
|
||||||
|
DueTime int64 `gorm:"column:due_time"` // 到期時間(適用於凍結或延後入帳等場景)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定 GORM 對應的資料表名稱
|
||||||
|
func (t *Transaction) TableName() string {
|
||||||
|
return "transaction"
|
||||||
|
}
|
|
@ -19,14 +19,14 @@ import (
|
||||||
// Brand 欄位讓平台可以支援多租戶架構(不同品牌有不同的錢包群組)。
|
// Brand 欄位讓平台可以支援多租戶架構(不同品牌有不同的錢包群組)。
|
||||||
|
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
ID int64 `gorm:"column:id"` // 主鍵 ID(錢包的唯一識別)
|
ID int64 `gorm:"column:id"` // 主鍵 ID(錢包的唯一識別)
|
||||||
Brand string `gorm:"column:brand"` // 品牌/平台(區分多租戶、多品牌情境)
|
Brand string `gorm:"column:brand"` // 品牌/平台(區分多租戶、多品牌情境)
|
||||||
UID string `gorm:"column:uid"` // 使用者 UID
|
UID string `gorm:"column:uid"` // 使用者 UID
|
||||||
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
||||||
Balance decimal.Decimal `gorm:"column:balance"` // 餘額(使用高精度 decimal 避免浮點誤差)
|
Balance decimal.Decimal `gorm:"column:balance"` // 餘額(使用高精度 decimal 避免浮點誤差)
|
||||||
Type wallet.Types `gorm:"column:type"` // 錢包類型
|
Type wallet.Types `gorm:"column:type"` // 錢包類型
|
||||||
CreateTime int64 `gorm:"column:create_time;autoCreateTime"` // 建立時間(UnixNano timestamp)
|
CreateAt int64 `gorm:"column:create_at"` // 建立時間(UnixNano timestamp)
|
||||||
UpdateTime int64 `gorm:"column:update_time;autoUpdateTime"` // 更新時間(UnixNano timestamp)
|
UpdateAt int64 `gorm:"column:update_at"` // 更新時間(UnixNano timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Wallet) TableName() string {
|
func (c *Wallet) TableName() string {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WalletTransaction 表示錢包的交易紀錄(每一次扣款、加值、入帳、退費都會有一筆紀錄)
|
||||||
|
type WalletTransaction struct {
|
||||||
|
ID int64 `gorm:"column:id"` // 主鍵 ID(自動遞增)
|
||||||
|
TransactionID int64 `gorm:"column:transaction_id"` // 交易流水號(可對應某次業務操作,例如同一訂單的多筆變化)
|
||||||
|
OrderID string `gorm:"column:order_id"` // 訂單編號(對應實際訂單或業務事件)
|
||||||
|
Brand string `gorm:"column:brand"` // 品牌(多租戶或多平台識別)
|
||||||
|
UID string `gorm:"column:uid"` // 使用者 UID
|
||||||
|
WalletType wallet.Types `gorm:"column:wallet_type"` // 錢包類型(例如:主錢包、獎勵錢包、凍結錢包等)
|
||||||
|
BusinessType int8 `gorm:"column:business_type"` // 業務類型(定義本次交易是什麼用途,例如購物、退款、加值等)
|
||||||
|
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
||||||
|
Amount decimal.Decimal `gorm:"column:amount"` // 變動金額(正數為收入,負數為支出)
|
||||||
|
Balance decimal.Decimal `gorm:"column:balance"` // 當前錢包餘額(這筆交易後的餘額快照)
|
||||||
|
CreateAt int64 `gorm:"column:create_at"` // 建立時間(UnixNano,紀錄交易發生時間)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName 指定 GORM 對應的資料表名稱
|
||||||
|
func (t *WalletTransaction) TableName() string {
|
||||||
|
return "wallet_transaction"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrRecordNotFound = errors.New("query record not found")
|
||||||
|
var ErrBalanceInsufficient = errors.New("balance insufficient")
|
|
@ -0,0 +1,32 @@
|
||||||
|
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"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionRepository interface {
|
||||||
|
FindByOrderID(ctx context.Context, orderID string) (entity.Transaction, error)
|
||||||
|
Insert(ctx context.Context, tx *entity.Transaction) error
|
||||||
|
BatchInsert(ctx context.Context, txs []*entity.Transaction) error
|
||||||
|
List(ctx context.Context, query TransactionQuery) ([]entity.Transaction, int64, error)
|
||||||
|
FindByDueTimeRange(ctx context.Context, start time.Time, txType []wallet.TxType) ([]entity.Transaction, error)
|
||||||
|
UpdateStatusByID(ctx context.Context, id int64, status int) error
|
||||||
|
ListWalletTransactions(ctx context.Context, uid string, orderIDs []string, walletType wallet.Types) ([]entity.WalletTransaction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionQuery struct {
|
||||||
|
UID *string
|
||||||
|
OrderID *string
|
||||||
|
Brand *string
|
||||||
|
Assets *string
|
||||||
|
WalletType *wallet.Types
|
||||||
|
TxTypes []wallet.TxType
|
||||||
|
BusinessType []int8
|
||||||
|
StartTime *int64
|
||||||
|
EndTime *int64
|
||||||
|
PageIndex int64
|
||||||
|
PageSize int64
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
Brand string `gorm:"column:brand"` // 品牌/平台(區分多租戶、多品牌情境)
|
Brand string // 品牌/平台(區分多租戶、多品牌情境)
|
||||||
UID string `gorm:"column:uid"` // 使用者 UID
|
UID string // 使用者 UID
|
||||||
Asset string `gorm:"column:asset"` // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
Asset string // 資產代號,可為 BTC、ETH、GEM_RED、GEM_BLUE、POINTS ,USD, TWD 等....
|
||||||
Balance decimal.Decimal `gorm:"column:balance"` // 餘額(使用高精度 decimal 避免浮點誤差)
|
Balance decimal.Decimal // 餘額(使用高精度 decimal 避免浮點誤差)
|
||||||
Type wallet.Types `gorm:"column:type"` // 錢包類型
|
Type wallet.Types // 錢包類型
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalletRepository 是錢包的總入口,負責查詢、初始化與跨帳戶查詢邏輯
|
// WalletRepository 是錢包的總入口,負責查詢、初始化與跨帳戶查詢邏輯
|
||||||
|
@ -75,8 +75,8 @@ type UserWalletService interface {
|
||||||
// CheckReady 檢查錢包是否已經存在並準備好
|
// CheckReady 檢查錢包是否已經存在並準備好
|
||||||
CheckReady(ctx context.Context) (bool, error)
|
CheckReady(ctx context.Context) (bool, error)
|
||||||
// Add 加值與扣款邏輯(含業務類別)
|
// Add 加值與扣款邏輯(含業務類別)
|
||||||
Add(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
Add(kind wallet.Types, orderID string, amount decimal.Decimal) error
|
||||||
Sub(kind wallet.Types, business wallet.BusinessName, amount decimal.Decimal) error
|
Sub(kind wallet.Types, orderID string, amount decimal.Decimal) error
|
||||||
// AddTransaction 新增一筆交易紀錄(建立資料)
|
// AddTransaction 新增一筆交易紀錄(建立資料)
|
||||||
AddTransaction(txID int64, orderID string, brand string, business wallet.BusinessName, kind wallet.Types, amount decimal.Decimal)
|
AddTransaction(txID int64, orderID string, brand string, business wallet.BusinessName, kind wallet.Types, amount decimal.Decimal)
|
||||||
//// PendingTransactions 查詢尚未執行的交易清單(會在 Execute 中一次提交)
|
//// PendingTransactions 查詢尚未執行的交易清單(會在 Execute 中一次提交)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletTransactionRepo interface {
|
||||||
|
Create(ctx context.Context, db *gorm.DB, tx []entity.WalletTransaction) error
|
||||||
|
HistoryBalance(ctx context.Context, req HistoryReq) ([]entity.WalletTransaction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryReq struct {
|
||||||
|
UID string
|
||||||
|
StartTime int64
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
type BusinessName string
|
type BusinessName string
|
||||||
|
|
||||||
|
func (b BusinessName) ToInt8() int8 {
|
||||||
|
return int8(0)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
type Enable int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnableTrue Enable = 1
|
||||||
|
EnableFalse Enable = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e Enable) ToInt8() int8 {
|
||||||
|
return int8(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Enable) ToBool() bool {
|
||||||
|
return e == EnableTrue
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
type TxType int8
|
||||||
|
|
||||||
|
// 交易類型
|
||||||
|
const (
|
||||||
|
// Deposit 充值(增加可用餘額)
|
||||||
|
Deposit TxType = iota + 1
|
||||||
|
// Withdraw 提現(減少可用餘額)
|
||||||
|
Withdraw
|
||||||
|
// Freeze 凍結(減少可用餘額,加在凍結餘額)
|
||||||
|
Freeze
|
||||||
|
// UnFreeze 解凍(減少凍結餘額)
|
||||||
|
UnFreeze
|
||||||
|
// RollbackFreeze (減少凍結餘額,加回可用餘額,不可指定金額,根據訂單)
|
||||||
|
RollbackFreeze
|
||||||
|
// Unconfirmed 限制(減少凍結餘額,加別人限制餘額)
|
||||||
|
Unconfirmed
|
||||||
|
// CancelFreeze 取消凍結(減少凍結餘額,加回可用餘額,,可指定金額)
|
||||||
|
CancelFreeze
|
||||||
|
// DepositUnconfirmed 充值(增加限制餘額)
|
||||||
|
DepositUnconfirmed
|
||||||
|
// AppendFreeze 追加凍結(減少可用餘額,加在凍結餘額)
|
||||||
|
AppendFreeze
|
||||||
|
// RollbackFreezeAddAvailable (rollback凍結餘額,指定金額加回可用餘額)
|
||||||
|
RollbackFreezeAddAvailable
|
||||||
|
// Distribution 平台分發(活動送錢給玩家)
|
||||||
|
Distribution
|
||||||
|
// SystemTransfer 系統劃轉
|
||||||
|
SystemTransfer
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t TxType) ToInt() int {
|
||||||
|
return int(t)
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ type Types int8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
|
TypeAvailable Types = iota + 1 // 可動用金額(使用者可以自由花用的餘額)
|
||||||
TypeFreezeType // 被凍結金額(交易進行中或風控鎖住的金額)
|
TypeFreeze // 被凍結金額(交易進行中或風控鎖住的金額)
|
||||||
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
|
TypeUnconfirmed // 未確認金額(交易已送出但區塊鏈尚未確認)
|
||||||
// 以下為進階用途:合約或模擬交易錢包
|
// 以下為進階用途:合約或模擬交易錢包
|
||||||
|
|
||||||
|
@ -13,3 +13,5 @@ const (
|
||||||
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
|
TypeSimulationAvailable // 模擬交易可用金額(例如沙盒環境)
|
||||||
TypeSimulationFreeze // 模擬交易凍結金額
|
TypeSimulationFreeze // 模擬交易凍結金額
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var AllTypes = []Types{TypeAvailable, TypeFreeze, TypeUnconfirmed}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package sql_client
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
MaxIdleConns int
|
||||||
|
MaxOpenConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
InterpolateParams bool
|
||||||
|
LogLevel string
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package sql_client
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMaxOpenConns = 25
|
||||||
|
defaultMaxIdleConns = 25
|
||||||
|
defaultMaxLifeTime = 5 * time.Minute
|
||||||
|
defaultSlowSQLThreshold = 200 * time.Millisecond
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
package sql_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/internal/config"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMySQLClient(conf config.Config) (*gorm.DB, error) {
|
||||||
|
dbConf := &Config{
|
||||||
|
User: conf.MySQL.UserName,
|
||||||
|
Password: conf.MySQL.Password,
|
||||||
|
Host: conf.MySQL.Host,
|
||||||
|
Port: conf.MySQL.Port,
|
||||||
|
Database: conf.MySQL.Database,
|
||||||
|
MaxIdleConns: conf.MySQL.MaxIdleConns,
|
||||||
|
MaxOpenConns: conf.MySQL.MaxOpenConns,
|
||||||
|
ConnMaxLifetime: conf.MySQL.ConnMaxLifetime,
|
||||||
|
InterpolateParams: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbInit := New(dbConf, WithLogLevel(conf.MySQL.LogLevel))
|
||||||
|
|
||||||
|
orm, err := dbInit.Conn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initiate db connection pool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orm, nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package sql_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MySQL struct {
|
||||||
|
mysqlConf gorm.Dialector
|
||||||
|
gormConf *gorm.Config
|
||||||
|
serverConf *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultConfig = logger.Config{
|
||||||
|
SlowThreshold: defaultSlowSQLThreshold,
|
||||||
|
LogLevel: logger.Warn,
|
||||||
|
IgnoreRecordNotFoundError: false,
|
||||||
|
Colorful: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a MysqlInit using the provided Config and options. If
|
||||||
|
// opts is not provided it will initialize MysqlInit with default configuration.
|
||||||
|
func New(conf *Config, opts ...Option) *MySQL {
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local&interpolateParams=%t",
|
||||||
|
conf.User, conf.Password, conf.Host, conf.Port, conf.Database, conf.InterpolateParams)
|
||||||
|
|
||||||
|
loggerDefault := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), defaultConfig)
|
||||||
|
|
||||||
|
mysqli := &MySQL{
|
||||||
|
mysqlConf: mysql.Open(dsn),
|
||||||
|
gormConf: &gorm.Config{
|
||||||
|
NamingStrategy: schema.NamingStrategy{
|
||||||
|
SingularTable: true, // use singular table name
|
||||||
|
},
|
||||||
|
Logger: loggerDefault,
|
||||||
|
},
|
||||||
|
serverConf: conf,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(mysqli)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysqli
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn initiates connection to database and return a gorm.DB.
|
||||||
|
func (mysqli *MySQL) Conn() (*gorm.DB, error) {
|
||||||
|
db, err := gorm.Open(mysqli.mysqlConf, mysqli.gormConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gorm open error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get connect pool error :%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIdleConns := defaultMaxIdleConns
|
||||||
|
if mysqli.serverConf.MaxIdleConns > 0 {
|
||||||
|
maxIdleConns = mysqli.serverConf.MaxIdleConns
|
||||||
|
}
|
||||||
|
|
||||||
|
maxOpenConns := defaultMaxOpenConns
|
||||||
|
if mysqli.serverConf.MaxOpenConns > 0 {
|
||||||
|
maxOpenConns = mysqli.serverConf.MaxOpenConns
|
||||||
|
}
|
||||||
|
|
||||||
|
maxLifeTime := defaultMaxLifeTime
|
||||||
|
if mysqli.serverConf.ConnMaxLifetime > 0 {
|
||||||
|
maxLifeTime = mysqli.serverConf.ConnMaxLifetime
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDB.SetMaxIdleConns(maxIdleConns)
|
||||||
|
sqlDB.SetMaxOpenConns(maxOpenConns)
|
||||||
|
sqlDB.SetConnMaxLifetime(maxLifeTime)
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package sql_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(*MySQL)
|
||||||
|
|
||||||
|
// WithLogLevel set gorm log level.
|
||||||
|
func WithLogLevel(level string) Option {
|
||||||
|
return func(mysqli *MySQL) {
|
||||||
|
var gormLogLevel logger.LogLevel
|
||||||
|
|
||||||
|
switch strings.ToLower(level) {
|
||||||
|
case "panic", "fatal", "error":
|
||||||
|
gormLogLevel = logger.Error
|
||||||
|
case "warn", "warning":
|
||||||
|
gormLogLevel = logger.Warn
|
||||||
|
case "info", "debug", "trace":
|
||||||
|
gormLogLevel = logger.Info
|
||||||
|
case "silent":
|
||||||
|
gormLogLevel = logger.Silent
|
||||||
|
default:
|
||||||
|
gormLogLevel = logger.Silent
|
||||||
|
}
|
||||||
|
|
||||||
|
newLogger := mysqli.gormConf.Logger.LogMode(gormLogLevel)
|
||||||
|
|
||||||
|
mysqli.gormConf.Logger = newLogger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPerformance disable default transaction and create a prepared statement
|
||||||
|
// related doc: https://gorm.io/docs/performance.html
|
||||||
|
func WithPerformance() Option {
|
||||||
|
return func(mysqli *MySQL) {
|
||||||
|
mysqli.gormConf.SkipDefaultTransaction = true
|
||||||
|
mysqli.gormConf.PrepareStmt = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MySQLUser = "root"
|
||||||
|
MySQLPassword = "password"
|
||||||
|
MySQLDatabase = "testdb"
|
||||||
|
MySQLPort = "3306"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 啟動 MySQL container
|
||||||
|
func startMySQLContainer() (host string, port string, dsn string, tearDown func(), err error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
req := testcontainers.ContainerRequest{
|
||||||
|
Image: "mysql:8.0",
|
||||||
|
Env: map[string]string{
|
||||||
|
"MYSQL_ROOT_PASSWORD": MySQLPassword,
|
||||||
|
"MYSQL_DATABASE": MySQLDatabase,
|
||||||
|
},
|
||||||
|
ExposedPorts: []string{"3306/tcp"},
|
||||||
|
WaitingFor: wait.ForListeningPort("3306/tcp"),
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqlC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||||
|
ContainerRequest: req,
|
||||||
|
Started: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedPort, err := mysqlC.MappedPort(ctx, "3306")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
containerHost, err := mysqlC.Host(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 組成 DSN(Data Source Name)
|
||||||
|
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",
|
||||||
|
MySQLUser,
|
||||||
|
MySQLPassword,
|
||||||
|
containerHost,
|
||||||
|
mappedPort.Port(),
|
||||||
|
MySQLDatabase,
|
||||||
|
)
|
||||||
|
|
||||||
|
tearDown = func() {
|
||||||
|
_ = mysqlC.Terminate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("MySQL ready at: %s\n", dsn)
|
||||||
|
return containerHost, mappedPort.Port(), dsn, tearDown, nil
|
||||||
|
}
|
|
@ -2,17 +2,297 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/repository"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUserWallet(db *gorm.DB, uid, crypto string) repository.UserWallet {
|
// 用戶某個幣種餘額
|
||||||
return &userWallet{
|
type userWallet struct {
|
||||||
db: db,
|
db *gorm.DB
|
||||||
uid: uid,
|
uid string
|
||||||
crypto: crypto,
|
asset string
|
||||||
|
|
||||||
localWalletBalance: make(map[domain.WalletType]entity.Wallet, len(domain.WalletAllType)),
|
// local wallet 相關計算的餘額存在這裡
|
||||||
localOrderBalance: make(map[int64]decimal.Decimal, len(domain.WalletAllType)),
|
localWalletBalance map[wallet.Types]entity.Wallet
|
||||||
|
// local order wallet 相關計算的餘額存在這裡
|
||||||
|
localOrderBalance map[int64]decimal.Decimal
|
||||||
|
// local wallet 內所有餘額變化紀錄
|
||||||
|
transactions []entity.WalletTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserWallet(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
||||||
|
return &userWallet{
|
||||||
|
db: db,
|
||||||
|
uid: uid,
|
||||||
|
asset: asset,
|
||||||
|
localWalletBalance: make(map[wallet.Types]entity.Wallet, len(wallet.AllTypes)),
|
||||||
|
localOrderBalance: make(map[int64]decimal.Decimal, len(wallet.AllTypes)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Init(ctx context.Context, uid, asset, brand string) ([]entity.Wallet, error) {
|
||||||
|
wallets := make([]entity.Wallet, 0, len(wallet.AllTypes))
|
||||||
|
for _, t := range wallet.AllTypes {
|
||||||
|
balance := decimal.Zero
|
||||||
|
|
||||||
|
wallets = append(wallets, entity.Wallet{
|
||||||
|
Brand: brand,
|
||||||
|
UID: uid,
|
||||||
|
Asset: asset,
|
||||||
|
Balance: balance,
|
||||||
|
Type: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.db.WithContext(ctx).Create(&wallets).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range wallets {
|
||||||
|
repo.localWalletBalance[v.Type] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) All(ctx context.Context) ([]entity.Wallet, error) {
|
||||||
|
var result []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.buildCommonWhereSQL(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Find(&result).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range result {
|
||||||
|
repo.localWalletBalance[v.Type] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Get(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.buildCommonWhereSQL(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type IN ?", kinds).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, notFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetWithLock(ctx context.Context, kinds []wallet.Types) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.buildCommonWhereSQL(repo.uid, repo.asset).
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("type IN ?", kinds).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, notFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) LocalBalance(kind wallet.Types) decimal.Decimal {
|
||||||
|
w, ok := repo.localWalletBalance[kind]
|
||||||
|
if !ok {
|
||||||
|
return decimal.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Balance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) LockByIDs(ctx context.Context, ids []int64) ([]entity.Wallet, error) {
|
||||||
|
var wallets []entity.Wallet
|
||||||
|
|
||||||
|
err := repo.db.WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("id, crypto, balance, type").
|
||||||
|
Where("id IN ?", ids).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Find(&wallets).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []entity.Wallet{}, notFoundError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
repo.localWalletBalance[w.Type] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) CheckReady(ctx context.Context) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := repo.buildCommonWhereSQL(repo.uid, repo.asset).WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Select("1").
|
||||||
|
Where("type = ?", wallet.TypeAvailable).
|
||||||
|
Limit(1).
|
||||||
|
Scan(&exists).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 新增某種餘額餘額
|
||||||
|
// 使用前 localWalletBalance 必須有資料,所以必須執行過 GetWithLock / All 才會有資料
|
||||||
|
func (repo *userWallet) Add(kind wallet.Types, orderID string, amount decimal.Decimal) error {
|
||||||
|
w, ok := repo.localWalletBalance[kind]
|
||||||
|
if !ok {
|
||||||
|
return repository.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Balance = w.Balance.Add(amount)
|
||||||
|
if w.Balance.LessThan(decimal.Zero) {
|
||||||
|
return repository.ErrBalanceInsufficient
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.transactions = append(repo.transactions, entity.WalletTransaction{
|
||||||
|
OrderID: orderID,
|
||||||
|
UID: repo.uid,
|
||||||
|
WalletType: kind,
|
||||||
|
Asset: repo.asset,
|
||||||
|
Amount: amount,
|
||||||
|
Balance: w.Balance,
|
||||||
|
})
|
||||||
|
|
||||||
|
repo.localWalletBalance[kind] = w
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Sub(kind wallet.Types, orderID string, amount decimal.Decimal) error {
|
||||||
|
return repo.Add(kind, orderID, decimal.Zero.Sub(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) AddTransaction(txID int64, orderID string, brand string, business wallet.BusinessName, kind wallet.Types, amount decimal.Decimal) {
|
||||||
|
balance := repo.LocalBalance(kind).Add(amount)
|
||||||
|
repo.transactions = append(repo.transactions, entity.WalletTransaction{
|
||||||
|
TransactionID: txID,
|
||||||
|
OrderID: orderID,
|
||||||
|
Brand: brand,
|
||||||
|
UID: repo.uid,
|
||||||
|
WalletType: kind,
|
||||||
|
BusinessType: business.ToInt8(),
|
||||||
|
Asset: repo.asset,
|
||||||
|
Amount: amount,
|
||||||
|
Balance: balance,
|
||||||
|
CreateAt: time.Now().UTC().UnixNano(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) Commit(ctx context.Context) error {
|
||||||
|
// 事務隔離等級設定
|
||||||
|
rc := &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelReadCommitted,
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
for _, w := range repo.localWalletBalance {
|
||||||
|
err := tx.WithContext(ctx).
|
||||||
|
Model(&entity.Wallet{}).
|
||||||
|
Where("id = ?", w.ID).
|
||||||
|
UpdateColumns(map[string]any{
|
||||||
|
"balance": w.Balance,
|
||||||
|
"update_time": time.Now().UTC().Unix(),
|
||||||
|
}).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update wallet id %d: %w", w.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // 所有更新成功才 return nil
|
||||||
|
}, rc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update uid: %s asset: %s error: %w", repo.uid, repo.asset, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) GetTransactions() []entity.WalletTransaction {
|
||||||
|
return repo.transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *userWallet) CommitOrder(ctx context.Context) error {
|
||||||
|
rc := &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelReadCommitted,
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
err := repo.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
for id, balance := range repo.localOrderBalance {
|
||||||
|
err := tx.WithContext(ctx).
|
||||||
|
Model(&entity.Transaction{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("balance", balance).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update order balance, id=%d, err=%w", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // 所有更新成功才 return nil
|
||||||
|
}, rc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("update uid: %s asset: %s error: %w", repo.uid, repo.asset, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func (repo *userWallet) buildCommonWhereSQL(uid, asset string) *gorm.DB {
|
||||||
|
return repo.db.Where("uid = ?", uid).
|
||||||
|
Where("asset = ?", asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notFoundError(err error) error {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return repository.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func MustCategoryRepository(param WalletRepositoryParam) repository.WalletReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) NewDB() *gorm.DB {
|
func (repo *WalletRepository) NewDB() *gorm.DB {
|
||||||
return repo.DB
|
return repo.DB.Begin()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) Transaction(fn func(db *gorm.DB) error) error {
|
func (repo *WalletRepository) Transaction(fn func(db *gorm.DB) error) error {
|
||||||
|
@ -45,26 +45,68 @@ func (repo *WalletRepository) Transaction(fn func(db *gorm.DB) error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) Session(uid, asset string) repository.UserWalletService {
|
func (repo *WalletRepository) Session(uid, asset string) repository.UserWalletService {
|
||||||
//TODO implement me
|
return NewUserWallet(repo.DB, uid, asset)
|
||||||
panic("implement me")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) SessionWithTx(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
func (repo *WalletRepository) SessionWithTx(db *gorm.DB, uid, asset string) repository.UserWalletService {
|
||||||
//TODO implement me
|
return NewUserWallet(db, uid, asset)
|
||||||
panic("implement me")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) InitWallets(ctx context.Context, param []repository.Wallet) error {
|
func (repo *WalletRepository) InitWallets(ctx context.Context, param []repository.Wallet) error {
|
||||||
//TODO implement me
|
wallets := make([]entity.Wallet, 0, len(param))
|
||||||
panic("implement me")
|
for _, t := range param {
|
||||||
|
wallets = append(wallets, entity.Wallet{
|
||||||
|
Brand: t.Brand,
|
||||||
|
UID: t.UID,
|
||||||
|
Asset: t.Asset,
|
||||||
|
Balance: t.Balance,
|
||||||
|
Type: t.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.DB.Create(&wallets).WithContext(ctx).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) QueryBalances(ctx context.Context, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
func (repo *WalletRepository) QueryBalances(ctx context.Context, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
||||||
//TODO implement me
|
var data []entity.Wallet
|
||||||
panic("implement me")
|
s := repo.DB.WithContext(ctx).
|
||||||
|
Select("id, asset, balance, type, update_at").
|
||||||
|
Where("uid = ?", req.UID)
|
||||||
|
|
||||||
|
if req.Asset != "" {
|
||||||
|
s = s.Where("asset = ?", req.Asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.Find(&data).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *WalletRepository) QueryBalancesByUIDs(ctx context.Context, uids []string, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
func (repo *WalletRepository) QueryBalancesByUIDs(ctx context.Context, uids []string, req repository.BalanceQuery) ([]entity.Wallet, error) {
|
||||||
//TODO implement me
|
var data []entity.Wallet
|
||||||
panic("implement me")
|
query := repo.DB.WithContext(ctx).
|
||||||
|
Select("uid, asset, balance, type, update_at").
|
||||||
|
Where("uid IN ?", uids)
|
||||||
|
|
||||||
|
if req.Asset != "" {
|
||||||
|
query = query.Where("asset = ?", req.Asset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Kinds) > 0 {
|
||||||
|
query = query.Where("type IN ?", req.Kinds)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Find(&data).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/internal/config"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/repository"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/lib/sql_client"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestWalletRepository() (repository.WalletRepository, *gorm.DB, func(), error) {
|
||||||
|
host, port, _, tearDown, err := startMySQLContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("failed to start MySQL container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := config.Config{
|
||||||
|
MySQL: struct {
|
||||||
|
UserName string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
MaxIdleConns int
|
||||||
|
MaxOpenConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
LogLevel string
|
||||||
|
}{
|
||||||
|
UserName: MySQLUser,
|
||||||
|
Password: MySQLPassword,
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Database: MySQLDatabase,
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
MaxOpenConns: 100,
|
||||||
|
ConnMaxLifetime: 300,
|
||||||
|
LogLevel: "info",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql_client.NewMySQLClient(conf)
|
||||||
|
if err != nil {
|
||||||
|
tearDown()
|
||||||
|
return nil, nil, nil, fmt.Errorf("failed to create db client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := MustCategoryRepository(WalletRepositoryParam{DB: db})
|
||||||
|
return repo, db, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_InitWallets(t *testing.T) {
|
||||||
|
repo, db, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 🔽 這裡加上建表 SQL
|
||||||
|
createTableSQL := `
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID(錢包唯一識別)',
|
||||||
|
brand VARCHAR(50) NOT NULL COMMENT '品牌/平台(多租戶識別)',
|
||||||
|
uid VARCHAR(64) NOT NULL COMMENT '使用者 UID',
|
||||||
|
asset VARCHAR(32) NOT NULL COMMENT '資產代碼(如 BTC、ETH、GEM_RED 等)',
|
||||||
|
balance DECIMAL(30, 18) UNSIGNED NOT NULL DEFAULT 0 COMMENT '資產餘額',
|
||||||
|
type TINYINT NOT NULL COMMENT '錢包類型',
|
||||||
|
create_at INTEGER NOT NULL DEFAULT 0 COMMENT '建立時間(Unix)',
|
||||||
|
update_at INTEGER NOT NULL DEFAULT 0 COMMENT '更新時間(Unix)',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type),
|
||||||
|
KEY idx_uid (uid),
|
||||||
|
KEY idx_brand (brand)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='錢包';
|
||||||
|
`
|
||||||
|
|
||||||
|
err = db.Exec(createTableSQL).Error
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
param []repository.Wallet
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "insert single wallet",
|
||||||
|
param: []repository.Wallet{
|
||||||
|
{
|
||||||
|
Brand: "test-brand",
|
||||||
|
UID: "user001",
|
||||||
|
Asset: "BTC",
|
||||||
|
Balance: decimal.NewFromFloat(10.5),
|
||||||
|
Type: wallet.TypeAvailable,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "insert multiple wallets",
|
||||||
|
param: []repository.Wallet{
|
||||||
|
{
|
||||||
|
Brand: "test-brand",
|
||||||
|
UID: "user002",
|
||||||
|
Asset: "ETH",
|
||||||
|
Balance: decimal.NewFromFloat(5),
|
||||||
|
Type: wallet.TypeAvailable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Brand: "test-brand",
|
||||||
|
UID: "user002",
|
||||||
|
Asset: "ETH",
|
||||||
|
Balance: decimal.NewFromFloat(1.2),
|
||||||
|
Type: wallet.TypeFreeze,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "insert duplicate primary key (should fail if unique constraint)",
|
||||||
|
param: []repository.Wallet{
|
||||||
|
{
|
||||||
|
Brand: "test-brand",
|
||||||
|
UID: "user001",
|
||||||
|
Asset: "BTC",
|
||||||
|
Balance: decimal.NewFromFloat(1),
|
||||||
|
Type: wallet.TypeAvailable, // 與第一筆測試資料相同
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true, // 預期會違反 UNIQUE constraint
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := repo.InitWallets(context.Background(), tt.param)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_QueryBalances(t *testing.T) {
|
||||||
|
repo, db, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
// 🔽 這裡加上建表 SQL
|
||||||
|
createTableSQL := `
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID(錢包唯一識別)',
|
||||||
|
brand VARCHAR(50) NOT NULL COMMENT '品牌/平台(多租戶識別)',
|
||||||
|
uid VARCHAR(64) NOT NULL COMMENT '使用者 UID',
|
||||||
|
asset VARCHAR(32) NOT NULL COMMENT '資產代碼(如 BTC、ETH、GEM_RED 等)',
|
||||||
|
balance DECIMAL(30, 18) UNSIGNED NOT NULL DEFAULT 0 COMMENT '資產餘額',
|
||||||
|
type TINYINT NOT NULL COMMENT '錢包類型',
|
||||||
|
create_at INTEGER NOT NULL DEFAULT 0 COMMENT '建立時間(Unix)',
|
||||||
|
update_at INTEGER NOT NULL DEFAULT 0 COMMENT '更新時間(Unix)',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type),
|
||||||
|
KEY idx_uid (uid),
|
||||||
|
KEY idx_brand (brand)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='錢包';
|
||||||
|
`
|
||||||
|
|
||||||
|
err = db.Exec(createTableSQL).Error
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 建立初始化錢包資料
|
||||||
|
wallets := []repository.Wallet{
|
||||||
|
{Brand: "brand1", UID: "user1", Asset: "BTC", Balance: decimal.NewFromFloat(1.5), Type: wallet.TypeAvailable},
|
||||||
|
{Brand: "brand1", UID: "user1", Asset: "ETH", Balance: decimal.NewFromFloat(2.5), Type: wallet.TypeFreeze},
|
||||||
|
{Brand: "brand1", UID: "user2", Asset: "BTC", Balance: decimal.NewFromFloat(3.0), Type: wallet.TypeAvailable},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.InitWallets(ctx, wallets)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
query repository.BalanceQuery
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Query all by UID",
|
||||||
|
query: repository.BalanceQuery{
|
||||||
|
UID: "user1",
|
||||||
|
},
|
||||||
|
expected: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Query by UID and Asset",
|
||||||
|
query: repository.BalanceQuery{
|
||||||
|
UID: "user1",
|
||||||
|
Asset: "BTC",
|
||||||
|
},
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Query by UID with non-existing asset",
|
||||||
|
query: repository.BalanceQuery{
|
||||||
|
UID: "user1",
|
||||||
|
Asset: "GEM_RED",
|
||||||
|
},
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := repo.QueryBalances(ctx, tt.query)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, got, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_QueryBalancesByUIDs(t *testing.T) {
|
||||||
|
repo, db, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 🔽 這裡加上建表 SQL
|
||||||
|
createTableSQL := `
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID(錢包唯一識別)',
|
||||||
|
brand VARCHAR(50) NOT NULL COMMENT '品牌/平台(多租戶識別)',
|
||||||
|
uid VARCHAR(64) NOT NULL COMMENT '使用者 UID',
|
||||||
|
asset VARCHAR(32) NOT NULL COMMENT '資產代碼(如 BTC、ETH、GEM_RED 等)',
|
||||||
|
balance DECIMAL(30, 18) UNSIGNED NOT NULL DEFAULT 0 COMMENT '資產餘額',
|
||||||
|
type TINYINT NOT NULL COMMENT '錢包類型',
|
||||||
|
create_at INTEGER NOT NULL DEFAULT 0 COMMENT '建立時間(Unix)',
|
||||||
|
update_at INTEGER NOT NULL DEFAULT 0 COMMENT '更新時間(Unix)',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type),
|
||||||
|
KEY idx_uid (uid),
|
||||||
|
KEY idx_brand (brand)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='錢包';
|
||||||
|
`
|
||||||
|
|
||||||
|
err = db.Exec(createTableSQL).Error
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 初始化錢包資料
|
||||||
|
initData := []repository.Wallet{
|
||||||
|
{Brand: "brand1", UID: "user1", Asset: "BTC", Balance: decimal.NewFromFloat(1.5), Type: wallet.TypeAvailable},
|
||||||
|
{Brand: "brand1", UID: "user2", Asset: "BTC", Balance: decimal.NewFromFloat(2.0), Type: wallet.TypeAvailable},
|
||||||
|
{Brand: "brand1", UID: "user2", Asset: "ETH", Balance: decimal.NewFromFloat(3.0), Type: wallet.TypeFreeze},
|
||||||
|
{Brand: "brand1", UID: "user3", Asset: "BTC", Balance: decimal.NewFromFloat(4.5), Type: wallet.TypeAvailable},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.InitWallets(ctx, initData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uids []string
|
||||||
|
query repository.BalanceQuery
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Query all users with BTC",
|
||||||
|
uids: []string{"user1", "user2", "user3"},
|
||||||
|
query: repository.BalanceQuery{Asset: "BTC"},
|
||||||
|
expected: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Query specific users with filter by type",
|
||||||
|
uids: []string{"user2"},
|
||||||
|
query: repository.BalanceQuery{Kinds: []wallet.Types{wallet.TypeAvailable}},
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Query with no matches",
|
||||||
|
uids: []string{"user2"},
|
||||||
|
query: repository.BalanceQuery{Asset: "DOGE"},
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Query all for user2",
|
||||||
|
uids: []string{"user2"},
|
||||||
|
query: repository.BalanceQuery{},
|
||||||
|
expected: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.QueryBalancesByUIDs(ctx, tt.uids, tt.query)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_NewDB(t *testing.T) {
|
||||||
|
repo, _, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tx := repo.NewDB()
|
||||||
|
assert.NotNil(t, tx)
|
||||||
|
assert.NoError(t, tx.Commit().Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_Transaction(t *testing.T) {
|
||||||
|
repo, _, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
t.Run("commit success", func(t *testing.T) {
|
||||||
|
err := repo.Transaction(func(tx *gorm.DB) error {
|
||||||
|
return nil // 模擬成功流程
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rollback due to fn error", func(t *testing.T) {
|
||||||
|
customErr := errors.New("rollback me")
|
||||||
|
err := repo.Transaction(func(tx *gorm.DB) error {
|
||||||
|
return customErr
|
||||||
|
})
|
||||||
|
assert.ErrorIs(t, err, customErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_Session(t *testing.T) {
|
||||||
|
repo, _, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
sess := repo.Session("userX", "BTC")
|
||||||
|
assert.NotNil(t, sess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalletRepository_SessionWithTx(t *testing.T) {
|
||||||
|
repo, _, tearDown, err := SetupTestWalletRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
tx := repo.NewDB()
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
sess := repo.SessionWithTx(tx, "userY", "ETH")
|
||||||
|
assert.NotNil(t, sess)
|
||||||
|
}
|
Loading…
Reference in New Issue