代码初始化

This commit is contained in:
2026-04-02 10:22:36 +08:00
commit 7394983236
35 changed files with 3014 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea/*

74
config.yml Normal file
View File

@@ -0,0 +1,74 @@
server:
address: ":3007"
name: "shop-user-trade"
workerId: 1
logStdout: true
errorStack: true
rate:
limit: 200
burst: 300
# Database
database:
default:
- type: "pgsql"
host: "116.204.74.41"
port: "15432"
user: "postgres"
pass: "Bjang09@686^*^"
name: "shop_user_trade"
role: "master"
debug: false
dryRun: false
charset: "utf8"
timezone: "Asia/Shanghai"
maxIdle: 5
maxOpen: 20
maxLifetime: "30s"
maxIdleConnTime: "30s"
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
- type: "pgsql"
host: "116.204.74.41"
port: "15432"
user: "postgres"
pass: "Bjang09@686^*^"
name: "shop_user_trade"
role: "slave"
debug: false
dryRun: false
charset: "utf8"
timezone: "Asia/Shanghai"
maxIdle: 5
maxOpen: 20
maxLifetime: "30s"
maxIdleConnTime: "30s"
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
redis:
default:
address: 116.204.74.41:6379
db: 0
idleTimeout: "60s"
maxConnLifetime: "90s"
waitTimeout: "60s"
dialTimeout: "30s"
readTimeout: "30s"
writeTimeout: "30s"
maxActive: 100
consul:
address: 116.204.74.41:8500
jaeger:
addr: 116.204.74.41:4318
cache:
localTTL: 60
redisTTL: 300

View File

@@ -0,0 +1,13 @@
package knapsack
// KnapsackActionType 背包操作类型
type KnapsackActionType string
const (
KnapsackActionCreate KnapsackActionType = "create" // 创建
KnapsackActionUse KnapsackActionType = "use" // 使用
KnapsackActionVerify KnapsackActionType = "verify" // 核销
KnapsackActionList KnapsackActionType = "list" // 上架
KnapsackActionUnlist KnapsackActionType = "unlist" // 下架
KnapsackActionExpire KnapsackActionType = "expire" // 过期
)

View File

@@ -0,0 +1,28 @@
package knapsack
// KnapsackAssetType 背包资产类型
type KnapsackAssetType string
const (
KnapsackAssetTypePhysical KnapsackAssetType = "physical" // 实物资产
KnapsackAssetTypeVirtual KnapsackAssetType = "virtual" // 虚拟资产
KnapsackAssetTypeService KnapsackAssetType = "service" // 服务资产
)
// ServiceAssetType 服务资产子类型
type ServiceAssetType string
const (
ServiceAssetTypeSchedule ServiceAssetType = "schedule" // 排期服务
ServiceAssetTypeBooking ServiceAssetType = "booking" // 预订服务
)
// VirtualAssetType 虚拟资产子类型
type VirtualAssetType string
const (
VirtualAssetTypeNFT VirtualAssetType = "nft" // NFT数字藏品
VirtualAssetTypeCoupon VirtualAssetType = "coupon" // 优惠券
VirtualAssetTypeVoucher VirtualAssetType = "voucher" // 兑换券
VirtualAssetTypeTicket VirtualAssetType = "ticket" // 门票
)

View File

@@ -0,0 +1,12 @@
package knapsack
// KnapsackStatus 背包项状态
type KnapsackStatus int
const (
KnapsackStatusDisabled KnapsackStatus = 0 // 禁用
KnapsackStatusActive KnapsackStatus = 1 // 启用(可用)
KnapsackStatusUsed KnapsackStatus = 2 // 已使用
KnapsackStatusListed KnapsackStatus = 3 // 已上架到市场
KnapsackStatusExpired KnapsackStatus = 4 // 已过期
)

View File

@@ -0,0 +1,13 @@
package market
// MarketActionType 市场操作类型
type MarketActionType string
const (
MarketActionList MarketActionType = "list" // 上架
MarketActionUnlist MarketActionType = "unlist" // 下架
MarketActionBuy MarketActionType = "buy" // 购买
MarketActionSold MarketActionType = "sold" // 售出
MarketActionPriceUpdate MarketActionType = "price_update" // 更新价格
MarketActionExpire MarketActionType = "expire" // 过期
)

View File

@@ -0,0 +1,11 @@
package market
// MarketStatus 市场物品状态
type MarketStatus int
const (
MarketStatusActive MarketStatus = 1 // 活跃(上架中)
MarketStatusSold MarketStatus = 2 // 已售出
MarketStatusInactive MarketStatus = 3 // 已下架
MarketStatusExpired MarketStatus = 4 // 已过期
)

View File

@@ -0,0 +1,11 @@
package public
// 数据库表名
const (
TableNameKnapsack = "knapsack" // 背包表
TableNameKnapsackLog = "knapsack_log" // 背包日志表
TableNameMarket = "market" // 市场表
TableNameMarketLog = "market_log" // 市场日志表
TableNameWallet = "wallet" // 钱包表
TableNameWalletLog = "wallet_log" // 钱包日志表
)

View File

@@ -0,0 +1,11 @@
package wallet
// WalletLogType 钱包日志操作类型
type WalletLogType string
const (
WalletLogTypeIncome WalletLogType = "income" // 收入
WalletLogTypeExpense WalletLogType = "expense" // 支出
WalletLogTypeFreeze WalletLogType = "freeze" // 冻结
WalletLogTypeUnfreeze WalletLogType = "unfreeze" // 解冻
)

View File

@@ -0,0 +1,10 @@
package wallet
// WalletStatus 钱包状态
type WalletStatus int
const (
WalletStatusDisabled WalletStatus = 0 // 禁用
WalletStatusEnabled WalletStatus = 1 // 启用
WalletStatusFrozen WalletStatus = -1 // 冻结
)

View File

@@ -0,0 +1,68 @@
package controller
import (
"context"
knapsackDto "shop-user-trade/model/dto/knapsack"
knapsackService "shop-user-trade/service/knapsack"
)
var Knapsack = new(knapsackController)
type knapsackController struct{}
// Create 创建背包项
func (c *knapsackController) Create(ctx context.Context, req *knapsackDto.CreateKnapsackReq) (res *knapsackDto.CreateKnapsackRes, err error) {
id, err := knapsackService.Knapsack.Create(ctx, req)
if err != nil {
return nil, err
}
return &knapsackDto.CreateKnapsackRes{ID: id}, nil
}
// Get 获取背包项详情
func (c *knapsackController) Get(ctx context.Context, req *knapsackDto.GetKnapsackReq) (res *knapsackDto.GetKnapsackRes, err error) {
return knapsackService.Knapsack.GetOne(ctx, req)
}
// List 获取背包列表
func (c *knapsackController) List(ctx context.Context, req *knapsackDto.ListKnapsackReq) (res *knapsackDto.ListKnapsackRes, err error) {
return knapsackService.Knapsack.List(ctx, req)
}
// Use 使用背包项
func (c *knapsackController) Use(ctx context.Context, req *knapsackDto.UseKnapsackReq) (res *knapsackDto.UseKnapsackRes, err error) {
if err = knapsackService.Knapsack.Use(ctx, req); err != nil {
return nil, err
}
return &knapsackDto.UseKnapsackRes{Success: true, Message: "使用成功"}, nil
}
// ListToMarket 上架背包项到市场
func (c *knapsackController) ListToMarket(ctx context.Context, req *knapsackDto.ListToMarketReq) (res *knapsackDto.ListToMarketRes, err error) {
if err = knapsackService.Knapsack.ListToMarket(ctx, req); err != nil {
return nil, err
}
return &knapsackDto.ListToMarketRes{Success: true, Message: "上架成功"}, nil
}
// Unlist 从市场下架背包项
func (c *knapsackController) Unlist(ctx context.Context, req *knapsackDto.UnlistKnapsackReq) (res *knapsackDto.UnlistKnapsackRes, err error) {
if err = knapsackService.Knapsack.UnlistKnapsack(ctx, req); err != nil {
return nil, err
}
return &knapsackDto.UnlistKnapsackRes{Success: true, Message: "下架成功"}, nil
}
// Verify 核销背包项
func (c *knapsackController) Verify(ctx context.Context, req *knapsackDto.VerifyKnapsackReq) (res *knapsackDto.VerifyKnapsackRes, err error) {
if err = knapsackService.Knapsack.Verify(ctx, req); err != nil {
return nil, err
}
return &knapsackDto.VerifyKnapsackRes{Success: true, Message: "核销成功"}, nil
}
// GenerateQRCode 生成核销二维码
func (c *knapsackController) GenerateQRCode(ctx context.Context, req *knapsackDto.GenerateQRCodeReq) (res *knapsackDto.GenerateQRCodeRes, err error) {
return knapsackService.Knapsack.GenerateQRCode(ctx, req)
}

View File

@@ -0,0 +1,56 @@
package controller
import (
"context"
marketDto "shop-user-trade/model/dto/market"
marketService "shop-user-trade/service/market"
)
var Market = new(marketController)
type marketController struct{}
// Create 创建市场物品
func (c *marketController) Create(ctx context.Context, req *marketDto.CreateMarketReq) (res *marketDto.CreateMarketRes, err error) {
id, err := marketService.Market.Create(ctx, req)
if err != nil {
return nil, err
}
return &marketDto.CreateMarketRes{ID: id}, nil
}
// Get 获取市场物品详情
func (c *marketController) Get(ctx context.Context, req *marketDto.GetMarketReq) (res *marketDto.GetMarketRes, err error) {
return marketService.Market.GetOne(ctx, req)
}
// List 获取市场列表
func (c *marketController) List(ctx context.Context, req *marketDto.ListMarketReq) (res *marketDto.ListMarketRes, err error) {
return marketService.Market.List(ctx, req)
}
// Unlist 下架市场物品
func (c *marketController) Unlist(ctx context.Context, req *marketDto.UnlistMarketReq) (res *marketDto.UnlistMarketRes, err error) {
if err = marketService.Market.Unlist(ctx, req); err != nil {
return nil, err
}
return &marketDto.UnlistMarketRes{Success: true, Message: "下架成功"}, nil
}
// UpdatePrice 更新价格
func (c *marketController) UpdatePrice(ctx context.Context, req *marketDto.UpdatePriceReq) (res *marketDto.UpdatePriceRes, err error) {
if err = marketService.Market.UpdatePrice(ctx, req); err != nil {
return nil, err
}
return &marketDto.UpdatePriceRes{Success: true, Message: "价格更新成功"}, nil
}
// Buy 购买市场物品
func (c *marketController) Buy(ctx context.Context, req *marketDto.BuyMarketReq) (res *marketDto.BuyMarketRes, err error) {
orderNo, err := marketService.Market.Buy(ctx, req)
if err != nil {
return nil, err
}
return &marketDto.BuyMarketRes{OrderNo: orderNo}, nil
}

View File

@@ -0,0 +1,32 @@
package controller
import (
"context"
walletDto "shop-user-trade/model/dto/wallet"
walletService "shop-user-trade/service/wallet"
)
var Wallet = new(walletController)
type walletController struct{}
// GetByUserId 根据用户ID获取钱包
func (c *walletController) GetByUserId(ctx context.Context, req *walletDto.GetWalletByUserIdReq) (res *walletDto.GetWalletByUserIdResp, err error) {
return walletService.Wallet.GetByUserId(ctx, req)
}
// Create 创建钱包
func (c *walletController) Create(ctx context.Context, req *walletDto.CreateWalletReq) (res *walletDto.CreateWalletResp, err error) {
return walletService.Wallet.Create(ctx, req)
}
// UpdateBalance 更新余额
func (c *walletController) UpdateBalance(ctx context.Context, req *walletDto.UpdateBalanceReq) (res *walletDto.UpdateBalanceResp, err error) {
return walletService.Wallet.UpdateBalance(ctx, req)
}
// GetWalletLogs 获取钱包日志
func (c *walletController) GetWalletLogs(ctx context.Context, req *walletDto.GetWalletLogsReq) (res *walletDto.GetWalletLogsResp, err error) {
return walletService.Wallet.GetWalletLogs(ctx, req)
}

View File

@@ -0,0 +1,137 @@
package dao
import (
"context"
"time"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
knapsackConsts "shop-user-trade/consts/knapsack"
"shop-user-trade/consts/public"
knapsackDto "shop-user-trade/model/dto/knapsack"
knapsackEntity "shop-user-trade/model/entity/knapsack"
)
var Knapsack = new(knapsackDao)
type knapsackDao struct{}
// Insert 插入背包项
func (d *knapsackDao) Insert(ctx context.Context, req *knapsackDto.CreateKnapsackReq) (id int64, err error) {
var entity *knapsackEntity.Knapsack
if err = gconv.Struct(req, &entity); err != nil {
return
}
entity.Status = knapsackConsts.KnapsackStatusActive
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).Data(entity).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新背包项
func (d *knapsackDao) Update(ctx context.Context, req *knapsackDto.UpdateKnapsackReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).
Data(req).
Where(knapsackEntity.KnapsackCol.Id, req.Id).
OmitEmpty().
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除背包项(软删除)
func (d *knapsackDao) Delete(ctx context.Context, req *knapsackDto.DeleteKnapsackReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).
Where(knapsackEntity.KnapsackCol.Id, req.Id).
Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个背包项
func (d *knapsackDao) GetOne(ctx context.Context, req *knapsackDto.GetKnapsackReq) (res *knapsackEntity.Knapsack, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).
Where(knapsackEntity.KnapsackCol.Id, req.Id).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// GetByID 根据ID获取背包项
func (d *knapsackDao) GetByID(ctx context.Context, id int64) (res *knapsackEntity.Knapsack, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).
Where(knapsackEntity.KnapsackCol.Id, id).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取背包数量
func (d *knapsackDao) Count(ctx context.Context, req *knapsackDto.ListKnapsackReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取背包列表
func (d *knapsackDao) List(ctx context.Context, req *knapsackDto.ListKnapsackReq) (res []knapsackEntity.Knapsack, total int, err error) {
model := d.buildListFilter(ctx, req).OrderDesc(knapsackEntity.KnapsackCol.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// ListExpired 获取过期背包项列表
func (d *knapsackDao) ListExpired(ctx context.Context) (res []knapsackEntity.Knapsack, err error) {
now := time.Now().Unix()
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).
WhereIn(knapsackEntity.KnapsackCol.Status, []knapsackConsts.KnapsackStatus{
knapsackConsts.KnapsackStatusActive,
knapsackConsts.KnapsackStatusListed,
}).
WhereLT(knapsackEntity.KnapsackCol.ExpireAt, now).
WhereNotNull(knapsackEntity.KnapsackCol.ExpireAt).
All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询过滤条件
func (d *knapsackDao) buildListFilter(ctx context.Context, req *knapsackDto.ListKnapsackReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, public.TableNameKnapsack).OmitEmpty()
if !g.IsEmpty(req.UserID) {
model = model.Where(knapsackEntity.KnapsackCol.UserID, req.UserID)
}
if req.Status != nil {
model = model.Where(knapsackEntity.KnapsackCol.Status, *req.Status)
}
if !g.IsEmpty(req.Type) {
model = model.Where(knapsackEntity.KnapsackCol.Type, req.Type)
}
if !g.IsEmpty(req.Keyword) {
model = model.WhereLike(knapsackEntity.KnapsackCol.AssetName, "%"+req.Keyword+"%")
}
return model
}

151
dao/market/market_dao.go Normal file
View File

@@ -0,0 +1,151 @@
package dao
import (
"context"
"time"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
marketConsts "shop-user-trade/consts/market"
"shop-user-trade/consts/public"
marketDto "shop-user-trade/model/dto/market"
marketEntity "shop-user-trade/model/entity/market"
)
var Market = new(marketDao)
type marketDao struct{}
// Insert 插入市场物品
func (d *marketDao) Insert(ctx context.Context, req *marketDto.CreateMarketReq) (id int64, err error) {
var entity *marketEntity.Market
if err = gconv.Struct(req, &entity); err != nil {
return
}
entity.Status = marketConsts.MarketStatusActive
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).Data(entity).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新市场物品
func (d *marketDao) Update(ctx context.Context, req *marketDto.UpdateMarketReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Data(req).
Where(marketEntity.MarketCol.Id, req.Id).
OmitEmpty().
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除市场物品(软删除)
func (d *marketDao) Delete(ctx context.Context, req *marketDto.DeleteMarketReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Where(marketEntity.MarketCol.Id, req.Id).
Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个市场物品
func (d *marketDao) GetOne(ctx context.Context, req *marketDto.GetMarketReq) (res *marketEntity.Market, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Where(marketEntity.MarketCol.Id, req.Id).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// GetByID 根据ID获取市场物品
func (d *marketDao) GetByID(ctx context.Context, id int64) (res *marketEntity.Market, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Where(marketEntity.MarketCol.Id, id).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// GetByKnapsackID 根据背包项ID获取市场物品
func (d *marketDao) GetByKnapsackID(ctx context.Context, knapsackID int64) (res *marketEntity.Market, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Where(marketEntity.MarketCol.KnapsackID, knapsackID).
WhereNotIn(marketEntity.MarketCol.Status, []marketConsts.MarketStatus{
marketConsts.MarketStatusSold,
marketConsts.MarketStatusInactive,
marketConsts.MarketStatusExpired,
}).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取市场物品数量
func (d *marketDao) Count(ctx context.Context, req *marketDto.ListMarketReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取市场列表
func (d *marketDao) List(ctx context.Context, req *marketDto.ListMarketReq) (res []marketEntity.Market, total int, err error) {
model := d.buildListFilter(ctx, req).OrderDesc(marketEntity.MarketCol.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// ListExpired 获取过期市场物品列表
func (d *marketDao) ListExpired(ctx context.Context) (res []marketEntity.Market, err error) {
now := time.Now().Unix()
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).
Where(marketEntity.MarketCol.Status, marketConsts.MarketStatusActive).
WhereLT(marketEntity.MarketCol.ListExpireAt, now).
WhereNotNull(marketEntity.MarketCol.ListExpireAt).
All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询过滤条件
func (d *marketDao) buildListFilter(ctx context.Context, req *marketDto.ListMarketReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, public.TableNameMarket).OmitEmpty()
if !g.IsEmpty(req.UserID) {
model = model.Where(marketEntity.MarketCol.UserID, req.UserID)
}
if req.Status != nil {
model = model.Where(marketEntity.MarketCol.Status, *req.Status)
}
if !g.IsEmpty(req.Type) {
model = model.Where(marketEntity.MarketCol.Type, req.Type)
}
if !g.IsEmpty(req.Keyword) {
model = model.WhereLike(marketEntity.MarketCol.AssetName, "%"+req.Keyword+"%")
}
return model
}

134
dao/wallet/wallet_dao.go Normal file
View File

@@ -0,0 +1,134 @@
package dao
import (
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
"shop-user-trade/consts/public"
walletConsts "shop-user-trade/consts/wallet"
walletDto "shop-user-trade/model/dto/wallet"
walletEntity "shop-user-trade/model/entity/wallet"
)
var Wallet = new(walletDao)
type walletDao struct{}
// GetByUserID 根据用户ID获取钱包
func (d *walletDao) GetByUserID(ctx context.Context, userID int64) (res *walletEntity.Wallet, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameWallet).
Where(walletEntity.WalletCol.UserID, userID).
WhereNot(walletEntity.WalletCol.Status, int(walletConsts.WalletStatusFrozen)).
One()
if err != nil {
return
}
if r.IsEmpty() {
return nil, nil
}
err = r.Struct(&res)
return
}
// GetByID 根据ID获取钱包
func (d *walletDao) GetByID(ctx context.Context, walletID int64) (res *walletEntity.Wallet, err error) {
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameWallet).
Where(walletEntity.WalletCol.Id, walletID).
One()
if err != nil {
return
}
if r.IsEmpty() {
return nil, nil
}
err = r.Struct(&res)
return
}
// Create 创建钱包
func (d *walletDao) Create(ctx context.Context, req *walletDto.CreateWalletReq) (id int64, err error) {
entity := &walletEntity.Wallet{
UserID: req.UserId,
Balance: 0,
Currency: req.Currency,
Status: walletConsts.WalletStatusEnabled,
Version: 1,
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameWallet).Data(entity).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// UpdateBalance 更新余额(乐观锁)
func (d *walletDao) UpdateBalance(ctx context.Context, walletID int64, amount int64, opType string, version int64) (bool, error) {
var updateData gdb.Map
switch opType {
case "income":
updateData = gdb.Map{
"balance": gdb.Raw("balance + " + gconv.String(amount)),
"version": gdb.Raw("version + 1"),
}
case "expense":
updateData = gdb.Map{
"balance": gdb.Raw("balance - " + gconv.String(amount)),
"version": gdb.Raw("version + 1"),
}
case "freeze":
updateData = gdb.Map{
"status": int(walletConsts.WalletStatusFrozen),
}
case "unfreeze":
updateData = gdb.Map{
"status": int(walletConsts.WalletStatusEnabled),
}
default:
return false, errors.New("unsupported operation type")
}
result, err := gfdb.DB(ctx).Model(ctx, public.TableNameWallet).
Data(updateData).
Where(walletEntity.WalletCol.Id, walletID).
Where(walletEntity.WalletCol.Version, version).
Update()
if err != nil {
return false, err
}
rows, err := result.RowsAffected()
if err != nil {
return false, err
}
return rows > 0, nil
}
// CreateLog 创建钱包日志
func (d *walletDao) CreateLog(ctx context.Context, req *walletDto.CreateWalletLogReq) (id int64, err error) {
var entity *walletEntity.WalletLog
if err = gconv.Struct(req, &entity); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameWalletLog).Data(entity).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// ListLogs 获取钱包日志列表
func (d *walletDao) ListLogs(ctx context.Context, userID int64, page, pageSize int) (res []walletEntity.WalletLog, total int, err error) {
r, total, err := gfdb.DB(ctx).Model(ctx, public.TableNameWalletLog).
Where(walletEntity.WalletLogCol.UserID, userID).
OrderDesc(walletEntity.WalletLogCol.CreatedAt).
Page(page, pageSize).
AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}

89
go.mod Normal file
View File

@@ -0,0 +1,89 @@
module shop-user-trade
go 1.25.8
require (
gitea.com/red-future/common v0.0.7
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
github.com/gogf/gf/v2 v2.10.0
)
replace gitea.com/red-future/common v0.0.7 => ../common
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 // indirect
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/consul/api v1.33.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.18.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/tiger1103/gfast-token v1.0.10 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

450
go.sum Normal file
View File

@@ -0,0 +1,450 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0 h1:39+jbTenm7KBj4hO2C8ANAxVHpX/7OuRDs1VcGC9ylA=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0/go.mod h1:B0s0fVzn0W220E8UTpSGzrrGKsop5KcB90twBeLCiz0=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 h1:egobo4YfQX3C4NtrEFunBqMX3jsddagklgut9u91+BM=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1/go.mod h1:YQ+u5Cs5N2ETCeQaLbv29z/UWYuxw27mJpUkOLj0kJ8=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
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/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/consul/api v1.33.5 h1:Nn6q87zudRU1rLBTJEgaWxz9STCNadilLCD7B8OA5aI=
github.com/hashicorp/consul/api v1.33.5/go.mod h1:pa6fJOSHKLOzNHpUVeqLDtxA5+J1D7NNzLasuk8eRXA=
github.com/hashicorp/consul/sdk v0.17.3 h1:oZMMxzQGSsiT+ToOH50y3Qcs0nc9Ud+7L5lRx+EmMU0=
github.com/hashicorp/consul/sdk v0.17.3/go.mod h1:jnOmYjiNfVRpBaujQ1DFFVs0N6g3S1y6wygSjLTzYfc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

164
init.sql Normal file
View File

@@ -0,0 +1,164 @@
-- shop_user_trade 数据库初始化脚本
-- 背包表
CREATE TABLE IF NOT EXISTS knapsack (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
user_id BIGINT NOT NULL,
asset_id BIGINT NOT NULL,
asset_name VARCHAR(255) NOT NULL DEFAULT '',
sku_id BIGINT NOT NULL DEFAULT 0,
sku_name VARCHAR(255) NOT NULL DEFAULT '',
spec_values JSONB,
image_url VARCHAR(512) NOT NULL DEFAULT '',
type VARCHAR(32) NOT NULL,
stock_detail_id BIGINT NOT NULL DEFAULT 0,
batch_id BIGINT NOT NULL DEFAULT 0,
batch_no VARCHAR(64) NOT NULL DEFAULT '',
stock_mode SMALLINT NOT NULL DEFAULT 1,
status SMALLINT NOT NULL DEFAULT 1,
used_at BIGINT,
expire_at BIGINT,
physical_asset_config JSONB,
service_asset_config JSONB,
virtual_asset_config JSONB
);
CREATE INDEX IF NOT EXISTS idx_knapsack_user_id ON knapsack(user_id);
CREATE INDEX IF NOT EXISTS idx_knapsack_asset_id ON knapsack(asset_id);
CREATE INDEX IF NOT EXISTS idx_knapsack_status ON knapsack(status);
CREATE INDEX IF NOT EXISTS idx_knapsack_tenant_id ON knapsack(tenant_id);
-- 背包日志表
CREATE TABLE IF NOT EXISTS knapsack_log (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
knapsack_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
action VARCHAR(32) NOT NULL,
operator_id BIGINT NOT NULL DEFAULT 0,
operator_name VARCHAR(64) NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_knapsack_log_knapsack_id ON knapsack_log(knapsack_id);
-- 市场表
CREATE TABLE IF NOT EXISTS market (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
knapsack_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
asset_id BIGINT NOT NULL,
asset_name VARCHAR(255) NOT NULL DEFAULT '',
sku_id BIGINT NOT NULL DEFAULT 0,
sku_name VARCHAR(255) NOT NULL DEFAULT '',
image_url VARCHAR(512) NOT NULL DEFAULT '',
type VARCHAR(32) NOT NULL,
stock_detail_id BIGINT NOT NULL DEFAULT 0,
batch_id BIGINT NOT NULL DEFAULT 0,
batch_no VARCHAR(64) NOT NULL DEFAULT '',
stock_mode SMALLINT NOT NULL DEFAULT 1,
price BIGINT NOT NULL DEFAULT 0,
original_price BIGINT NOT NULL DEFAULT 0,
description TEXT NOT NULL DEFAULT '',
status SMALLINT NOT NULL DEFAULT 1,
list_expire_at BIGINT
);
CREATE INDEX IF NOT EXISTS idx_market_user_id ON market(user_id);
CREATE INDEX IF NOT EXISTS idx_market_knapsack_id ON market(knapsack_id);
CREATE INDEX IF NOT EXISTS idx_market_status ON market(status);
CREATE INDEX IF NOT EXISTS idx_market_tenant_id ON market(tenant_id);
-- 市场日志表
CREATE TABLE IF NOT EXISTS market_log (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
market_id BIGINT NOT NULL,
knapsack_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
action VARCHAR(32) NOT NULL,
operator_id BIGINT NOT NULL DEFAULT 0,
operator_name VARCHAR(64) NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_market_log_market_id ON market_log(market_id);
-- 钱包表
CREATE TABLE IF NOT EXISTS wallet (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
user_id BIGINT NOT NULL UNIQUE,
balance BIGINT NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
status SMALLINT NOT NULL DEFAULT 1,
version BIGINT NOT NULL DEFAULT 1
);
CREATE INDEX IF NOT EXISTS idx_wallet_user_id ON wallet(user_id);
CREATE INDEX IF NOT EXISTS idx_wallet_tenant_id ON wallet(tenant_id);
-- 钱包日志表
CREATE TABLE IF NOT EXISTS wallet_log (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
creator VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updater VARCHAR(64) NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
user_id BIGINT NOT NULL,
wallet_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL DEFAULT '',
transaction_no VARCHAR(64) NOT NULL DEFAULT '',
type VARCHAR(16) NOT NULL,
amount BIGINT NOT NULL DEFAULT 0,
balance_before BIGINT NOT NULL DEFAULT 0,
balance_after BIGINT NOT NULL DEFAULT 0,
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
description TEXT NOT NULL DEFAULT '',
extra_data JSONB
);
CREATE INDEX IF NOT EXISTS idx_wallet_log_user_id ON wallet_log(user_id);
CREATE INDEX IF NOT EXISTS idx_wallet_log_wallet_id ON wallet_log(wallet_id);
CREATE INDEX IF NOT EXISTS idx_wallet_log_order_no ON wallet_log(order_no);

26
main.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"context"
knapsackController "shop-user-trade/controller/knapsack"
marketController "shop-user-trade/controller/market"
walletController "shop-user-trade/controller/wallet"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"
_ "gitea.com/red-future/common/swagger"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
)
func main() {
ctx := context.Background()
defer jaeger.ShutDown(ctx)
http.RouteRegister([]interface{}{
knapsackController.Knapsack,
marketController.Market,
walletController.Wallet,
})
select {}
}

View File

@@ -0,0 +1,80 @@
package config
import "shop-user-trade/consts/knapsack"
// KnapsackAssetConfigInterface 背包资产配置接口
type KnapsackAssetConfigInterface interface {
GetType() knapsack.KnapsackAssetType
}
// PhysicalKnapsackConfig 实物资产背包配置
type PhysicalKnapsackConfig struct {
Shipping PhysicalShippingConfig `orm:"shipping" json:"shipping"`
}
// PhysicalShippingConfig 实物配送配置
type PhysicalShippingConfig struct {
DeliveryMethod string `json:"deliveryMethod"` // 配送方式
}
func (PhysicalKnapsackConfig) GetType() knapsack.KnapsackAssetType {
return knapsack.KnapsackAssetTypePhysical
}
// ServiceKnapsackConfig 服务资产背包配置
type ServiceKnapsackConfig struct {
ServiceAssetType knapsack.ServiceAssetType `json:"serviceType"`
ServiceAssetArrivalConfig ServiceAssetArrivalConfig `json:"serviceArrivalConfig"`
}
type ServiceAssetArrivalConfig struct {
Schedule ScheduleConfig `json:"schedule,omitempty"`
Booking BookingConfig `json:"booking,omitempty"`
}
type ScheduleConfig struct {
TimeSlots []TimeSlot `json:"timeSlots"`
Exceptions []ScheduleException `json:"exceptions,omitempty"`
}
type BookingConfig struct {
MinAdvance int `json:"minAdvance,omitempty"`
MinDuration int `json:"minDuration,omitempty"`
CancelWindow int `json:"cancelWindow,omitempty"`
}
type TimeSlot struct {
DayOfWeek string `json:"dayOfWeek"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
Capacity int `json:"capacity,omitempty"`
}
type ScheduleException struct {
Date string `json:"date"`
Status int `json:"status"`
Reason string `json:"reason,omitempty"`
DayOfWeek string `json:"dayOfWeek"`
}
func (ServiceKnapsackConfig) GetType() knapsack.KnapsackAssetType {
return knapsack.KnapsackAssetTypeService
}
// VirtualKnapsackConfig 虚拟资产背包配置
type VirtualKnapsackConfig struct {
VirtualAssetType knapsack.VirtualAssetType `json:"virtualType"`
CollectionPrice int64 `json:"collectionPrice,omitempty"`
ApiConfig *VirtualApiConfig `json:"apiConfig,omitempty"`
}
type VirtualApiConfig struct {
Method string `json:"method"`
RequestURL string `json:"requestUrl"`
Headers map[string]string `json:"headers,omitempty"`
AuthType string `json:"authType,omitempty"`
}
func (VirtualKnapsackConfig) GetType() knapsack.KnapsackAssetType {
return knapsack.KnapsackAssetTypeVirtual
}

View File

@@ -0,0 +1,35 @@
package config
// MarketConfigInterface 市场配置接口
type MarketConfigInterface interface {
GetMarketType() string
}
// PhysicalMarketConfig 实物市场配置
type PhysicalMarketConfig struct {
DeliveryMethod string `json:"deliveryMethod"`
DeliveryFee int64 `json:"deliveryFee"`
}
func (PhysicalMarketConfig) GetMarketType() string {
return "physical"
}
// ServiceMarketConfig 服务市场配置
type ServiceMarketConfig struct {
ServiceLocation string `json:"serviceLocation"`
ServiceDuration int `json:"serviceDuration"`
}
func (ServiceMarketConfig) GetMarketType() string {
return "service"
}
// VirtualMarketConfig 虚拟市场配置
type VirtualMarketConfig struct {
TransferMethod string `json:"transferMethod"`
}
func (VirtualMarketConfig) GetMarketType() string {
return "virtual"
}

View File

@@ -0,0 +1,180 @@
package dto
import (
knapsackConsts "shop-user-trade/consts/knapsack"
"shop-user-trade/model/config"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateKnapsackReq 创建背包项请求
type CreateKnapsackReq struct {
g.Meta `path:"/createKnapsack" method:"post" tags:"背包管理" summary:"创建背包项" dc:"创建新的背包项"`
UserID int64 `json:"userId" v:"required" dc:"用户ID"`
AssetID int64 `json:"assetId" v:"required" dc:"资产ID"`
AssetName string `json:"assetName" v:"required" dc:"资产名称"`
SkuID int64 `json:"skuId,omitempty" dc:"SKU ID"`
SkuName string `json:"skuName,omitempty" dc:"SKU名称"`
SpecValues map[string]interface{} `json:"specValues,omitempty" dc:"规格值"`
ImageURL string `json:"imageUrl" dc:"资产图片URL"`
Type knapsackConsts.KnapsackAssetType `json:"type" v:"required" dc:"资产类型"`
StockDetailID int64 `json:"stockDetailId,omitempty" dc:"库存明细ID明细模式"`
BatchID int64 `json:"batchId,omitempty" dc:"批次ID批次模式"`
BatchNo string `json:"batchNo,omitempty" dc:"批次号(批次模式)"`
StockMode int `json:"stockMode" v:"required" dc:"库存管理模式1-明细模式2-批次模式"`
ExpireAt *int64 `json:"expireAt,omitempty" dc:"过期时间(时间戳)"`
PhysicalAssetConfig *config.PhysicalKnapsackConfig `json:"physicalAssetConfig,omitempty" dc:"实物资产配置"`
ServiceAssetConfig *config.ServiceKnapsackConfig `json:"serviceAssetConfig,omitempty" dc:"服务资产配置"`
VirtualAssetConfig *config.VirtualKnapsackConfig `json:"virtualAssetConfig,omitempty" dc:"虚拟资产配置"`
}
// GetKnapsackReq 获取背包项请求
type GetKnapsackReq struct {
g.Meta `path:"/getKnapsack" method:"get" tags:"背包管理" summary:"获取背包项" dc:"获取背包项详情"`
Id int64 `json:"id" v:"required" dc:"背包项ID"`
}
// UpdateKnapsackReq 更新背包项请求(内部使用)
type UpdateKnapsackReq struct {
Id int64 `json:"id" v:"required" dc:"背包项ID"`
Status *knapsackConsts.KnapsackStatus `json:"status,omitempty" dc:"状态"`
UsedAt *int64 `json:"usedAt,omitempty" dc:"使用时间"`
Updater string `json:"updater,omitempty" dc:"更新者"`
}
// DeleteKnapsackReq 删除背包项请求
type DeleteKnapsackReq struct {
g.Meta `path:"/deleteKnapsack" method:"delete" tags:"背包管理" summary:"删除背包项" dc:"删除背包项"`
Id int64 `json:"id" v:"required" dc:"背包项ID"`
}
// ListKnapsackReq 获取背包列表请求
type ListKnapsackReq struct {
g.Meta `path:"/listKnapsack" method:"get" tags:"背包管理" summary:"获取背包列表" dc:"分页查询背包列表,支持多条件筛选"`
*beans.Page
UserID int64 `json:"userId,omitempty" dc:"用户ID"`
Status *int `json:"status,omitempty" dc:"状态"`
Type string `json:"type,omitempty" dc:"资产类型"`
Keyword string `json:"keyword,omitempty" dc:"关键词搜索"`
}
// UseKnapsackReq 使用背包项请求
type UseKnapsackReq struct {
g.Meta `path:"/useKnapsack" method:"post" tags:"背包管理" summary:"使用背包项" dc:"手动使用背包中的物品"`
ID int64 `json:"id" v:"required" dc:"背包项ID"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
Reason string `json:"reason,omitempty" dc:"使用原因"`
}
// ListToMarketReq 上架背包项请求
type ListToMarketReq struct {
g.Meta `path:"/listToMarket" method:"post" tags:"背包管理" summary:"上架到市场" dc:"将背包项上架到市场"`
ID int64 `json:"id" v:"required" dc:"背包项ID"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
}
// UnlistKnapsackReq 下架背包项请求
type UnlistKnapsackReq struct {
g.Meta `path:"/unlistKnapsack" method:"post" tags:"背包管理" summary:"从市场下架" dc:"将背包项从市场下架"`
ID int64 `json:"id" v:"required" dc:"背包项ID"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
}
// VerifyKnapsackReq 核销背包项请求
type VerifyKnapsackReq struct {
g.Meta `path:"/verifyKnapsack" method:"post" tags:"背包管理" summary:"核销背包项" dc:"核销背包中的物品"`
ID int64 `json:"id" v:"required" dc:"背包项ID"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
VerifyLocation string `json:"verifyLocation,omitempty" dc:"核销位置"`
VerifyDevice string `json:"verifyDevice,omitempty" dc:"核销设备"`
}
// GenerateQRCodeReq 生成二维码请求
type GenerateQRCodeReq struct {
g.Meta `path:"/generateQRCode" method:"post" tags:"背包管理" summary:"生成核销二维码" dc:"生成用于核销的二维码"`
ID int64 `json:"id" v:"required" dc:"背包项ID"`
ExpireDuration int64 `json:"expireDuration" v:"required" dc:"过期时长(秒)"`
}
// ========== 响应 ==========
// CreateKnapsackRes 创建背包项响应
type CreateKnapsackRes struct {
ID int64 `json:"id" dc:"背包项ID"`
}
// GetKnapsackRes 获取背包项响应
type GetKnapsackRes struct {
*KnapsackItem
}
// ListKnapsackRes 获取背包列表响应
type ListKnapsackRes struct {
List []*KnapsackItem `json:"list" dc:"背包列表"`
Total int `json:"total" dc:"总数"`
}
// UseKnapsackRes 使用背包项响应
type UseKnapsackRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// ListToMarketRes 上架背包项响应
type ListToMarketRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// UnlistKnapsackRes 下架背包项响应
type UnlistKnapsackRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// VerifyKnapsackRes 核销背包项响应
type VerifyKnapsackRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// GenerateQRCodeRes 生成二维码响应
type GenerateQRCodeRes struct {
QRCode string `json:"qrCode,omitempty"`
VerifyCode string `json:"verifyCode,omitempty"`
}
// KnapsackItem 背包项视图
type KnapsackItem struct {
ID int64 `json:"id" dc:"背包项ID"`
UserID int64 `json:"userId" dc:"用户ID"`
AssetID int64 `json:"assetId" dc:"资产ID"`
AssetName string `json:"assetName" dc:"资产名称"`
SkuID int64 `json:"skuId,omitempty" dc:"SKU ID"`
SkuName string `json:"skuName,omitempty" dc:"SKU名称"`
ImageURL string `json:"imageUrl" dc:"资产图片URL"`
Type knapsackConsts.KnapsackAssetType `json:"type" dc:"资产类型"`
Status knapsackConsts.KnapsackStatus `json:"status" dc:"状态"`
ExpireAt *int64 `json:"expireAt,omitempty" dc:"过期时间"`
// 库存相关字段
StockDetailID int64 `json:"stockDetailId,omitempty" dc:"库存明细ID"`
BatchID int64 `json:"batchId,omitempty" dc:"批次ID"`
BatchNo string `json:"batchNo,omitempty" dc:"批次号"`
StockMode int `json:"stockMode" dc:"库存管理模式"`
CreatedAt string `json:"createdAt" dc:"创建时间"`
UpdatedAt string `json:"updatedAt" dc:"更新时间"`
}

View File

@@ -0,0 +1,158 @@
package dto
import (
marketConsts "shop-user-trade/consts/market"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateMarketReq 创建市场物品请求
type CreateMarketReq struct {
g.Meta `path:"/createMarket" method:"post" tags:"市场管理" summary:"创建市场物品" dc:"将背包项上架到市场"`
KnapsackID int64 `json:"knapsackId" v:"required" dc:"背包项ID"`
UserID int64 `json:"userId" v:"required" dc:"卖家用户ID"`
AssetID int64 `json:"assetId" v:"required" dc:"资产ID"`
AssetName string `json:"assetName" v:"required" dc:"资产名称"`
SkuID int64 `json:"skuId,omitempty" dc:"SKU ID"`
SkuName string `json:"skuName,omitempty" dc:"SKU名称"`
ImageURL string `json:"imageUrl" dc:"资产图片URL"`
Type string `json:"type" v:"required" dc:"资产类型"`
Price int64 `json:"price" v:"required" dc:"售价(分)"`
OriginalPrice int64 `json:"originalPrice,omitempty" dc:"原价(分)"`
Description string `json:"description,omitempty" dc:"描述信息"`
ListExpireAt *int64 `json:"listExpireAt,omitempty" dc:"上架过期时间(时间戳)"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
// 库存相关字段
StockDetailID int64 `json:"stockDetailId,omitempty" dc:"库存明细ID明细模式"`
BatchID int64 `json:"batchId,omitempty" dc:"批次ID批次模式"`
BatchNo string `json:"batchNo,omitempty" dc:"批次号(批次模式)"`
StockMode int `json:"stockMode" v:"required" dc:"库存管理模式1-明细模式2-批次模式"`
}
// GetMarketReq 获取市场物品请求
type GetMarketReq struct {
g.Meta `path:"/getMarket" method:"get" tags:"市场管理" summary:"获取市场物品" dc:"获取市场物品详情"`
Id int64 `json:"id" v:"required" dc:"市场物品ID"`
}
// UpdateMarketReq 更新市场物品请求(内部使用)
type UpdateMarketReq struct {
Id int64 `json:"id" v:"required" dc:"市场物品ID"`
Price *int64 `json:"price,omitempty" dc:"售价(分)"`
OriginalPrice *int64 `json:"originalPrice,omitempty" dc:"原价(分)"`
Description string `json:"description,omitempty" dc:"描述信息"`
Status *marketConsts.MarketStatus `json:"status,omitempty" dc:"状态"`
ListExpireAt *int64 `json:"listExpireAt,omitempty" dc:"上架过期时间"`
Updater string `json:"updater,omitempty" dc:"更新者"`
}
// DeleteMarketReq 删除市场物品请求
type DeleteMarketReq struct {
g.Meta `path:"/deleteMarket" method:"delete" tags:"市场管理" summary:"删除市场物品" dc:"删除市场物品"`
Id int64 `json:"id" v:"required" dc:"市场物品ID"`
}
// ListMarketReq 获取市场列表请求
type ListMarketReq struct {
g.Meta `path:"/listMarkets" method:"get" tags:"市场管理" summary:"获取市场列表" dc:"分页查询市场列表,支持多条件筛选"`
*beans.Page
UserID int64 `json:"userId,omitempty" dc:"用户ID"`
Status *int `json:"status,omitempty" dc:"状态"`
Type string `json:"type,omitempty" dc:"资产类型"`
Keyword string `json:"keyword,omitempty" dc:"关键词搜索"`
}
// UnlistMarketReq 下架市场物品请求
type UnlistMarketReq struct {
g.Meta `path:"/unlistMarket" method:"post" tags:"市场管理" summary:"下架市场物品" dc:"将市场物品下架"`
ID int64 `json:"id" v:"required" dc:"市场物品ID"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
Reason string `json:"reason,omitempty" dc:"下架原因"`
}
// UpdatePriceReq 更新价格请求
type UpdatePriceReq struct {
g.Meta `path:"/updatePrice" method:"post" tags:"市场管理" summary:"更新价格" dc:"更新市场物品价格"`
ID int64 `json:"id" v:"required" dc:"市场物品ID"`
Price int64 `json:"price" v:"required" dc:"新价格(分)"`
OperatorID int64 `json:"operatorId" v:"required" dc:"操作者ID"`
OperatorName string `json:"operatorName" v:"required" dc:"操作者名称"`
}
// BuyMarketReq 购买市场物品请求
type BuyMarketReq struct {
g.Meta `path:"/buyMarket" method:"post" tags:"市场管理" summary:"购买市场物品" dc:"购买市场中的物品"`
MarketID int64 `json:"marketId" v:"required" dc:"市场物品ID"`
BuyerID int64 `json:"buyerId" v:"required" dc:"买家ID"`
BuyerName string `json:"buyerName" v:"required" dc:"买家名称"`
}
// ========== 响应 ==========
// CreateMarketRes 创建市场物品响应
type CreateMarketRes struct {
ID int64 `json:"id" dc:"市场物品ID"`
}
// GetMarketRes 获取市场物品响应
type GetMarketRes struct {
*MarketItem
}
// ListMarketRes 获取市场列表响应
type ListMarketRes struct {
List []*MarketItem `json:"list" dc:"市场列表"`
Total int `json:"total" dc:"总数"`
}
// UnlistMarketRes 下架市场物品响应
type UnlistMarketRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// UpdatePriceRes 更新价格响应
type UpdatePriceRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// BuyMarketRes 购买市场物品响应
type BuyMarketRes struct {
OrderNo string `json:"orderNo"`
}
// MarketItem 市场物品视图
type MarketItem struct {
ID int64 `json:"id" dc:"市场物品ID"`
KnapsackID int64 `json:"knapsackId" dc:"背包项ID"`
UserID int64 `json:"userId" dc:"卖家用户ID"`
AssetID int64 `json:"assetId" dc:"资产ID"`
AssetName string `json:"assetName" dc:"资产名称"`
SkuID int64 `json:"skuId,omitempty" dc:"SKU ID"`
SkuName string `json:"skuName,omitempty" dc:"SKU名称"`
ImageURL string `json:"imageUrl" dc:"资产图片URL"`
Type string `json:"type" dc:"资产类型"`
Price int64 `json:"price" dc:"售价(分)"`
OriginalPrice int64 `json:"originalPrice" dc:"原价(分)"`
Description string `json:"description" dc:"描述信息"`
Status marketConsts.MarketStatus `json:"status" dc:"状态"`
ListExpireAt *int64 `json:"listExpireAt,omitempty" dc:"上架过期时间"`
// 库存相关字段
StockDetailID int64 `json:"stockDetailId,omitempty" dc:"库存明细ID"`
BatchID int64 `json:"batchId,omitempty" dc:"批次ID"`
BatchNo string `json:"batchNo,omitempty" dc:"批次号"`
StockMode int `json:"stockMode" dc:"库存管理模式"`
CreatedAt string `json:"createdAt" dc:"创建时间"`
UpdatedAt string `json:"updatedAt" dc:"更新时间"`
}

View File

@@ -0,0 +1,112 @@
package dto
import (
"github.com/gogf/gf/v2/frame/g"
)
// GetWalletByUserIdReq 根据用户ID获取钱包请求
type GetWalletByUserIdReq struct {
g.Meta `path:"/getWallet" method:"get" tags:"钱包管理" summary:"获取钱包" dc:"根据用户ID获取钱包信息"`
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
}
// GetWalletByUserIdResp 根据用户ID获取钱包响应
type GetWalletByUserIdResp struct {
Data WalletInfo `json:"data"`
}
// WalletInfo 钱包信息
type WalletInfo struct {
ID int64 `json:"id" dc:"钱包ID"`
UserID int64 `json:"userId" dc:"用户ID"`
Balance int64 `json:"balance" dc:"余额(分)"`
Currency string `json:"currency" dc:"货币类型"`
Status int `json:"status" dc:"状态"`
}
// CreateWalletReq 创建钱包请求
type CreateWalletReq struct {
g.Meta `path:"/createWallet" method:"post" tags:"钱包管理" summary:"创建钱包" dc:"为用户创建钱包"`
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
Currency string `json:"currency" v:"required" dc:"货币类型"`
}
// CreateWalletResp 创建钱包响应
type CreateWalletResp struct {
Data CreateWalletData `json:"data"`
}
// CreateWalletData 创建钱包数据
type CreateWalletData struct {
WalletID int64 `json:"walletId" dc:"钱包ID"`
}
// UpdateBalanceReq 更新余额请求
type UpdateBalanceReq struct {
g.Meta `path:"/updateBalance" method:"post" tags:"钱包管理" summary:"更新余额" dc:"对钱包余额进行收入/支出/冻结/解冻操作"`
WalletID int64 `json:"walletId" v:"required" dc:"钱包ID"`
Amount int64 `json:"amount" v:"required|min:1" dc:"金额(分)"`
Type string `json:"type" v:"required|in:income,expense,freeze,unfreeze" dc:"操作类型"`
OrderNo string `json:"orderNo" dc:"业务订单号"`
TransactionNo string `json:"transactionNo" dc:"钱包交易流水号"`
Description string `json:"description" dc:"描述"`
ExtraData map[string]interface{} `json:"extraData" dc:"额外数据"`
}
// UpdateBalanceResp 更新余额响应
type UpdateBalanceResp struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// GetWalletLogsReq 获取钱包日志请求
type GetWalletLogsReq struct {
g.Meta `path:"/getWalletLogs" method:"get" tags:"钱包管理" summary:"获取钱包日志" dc:"分页获取钱包操作日志"`
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
Page int `json:"page" v:"required|min:1" dc:"页码"`
PageSize int `json:"pageSize" v:"required|min:1|max:100" dc:"每页大小"`
}
// GetWalletLogsResp 获取钱包日志响应
type GetWalletLogsResp struct {
Data WalletLogData `json:"data"`
}
// WalletLogData 钱包日志数据
type WalletLogData struct {
Logs []WalletLogInfo `json:"logs"`
Total int `json:"total"`
}
// WalletLogInfo 钱包日志信息
type WalletLogInfo struct {
ID int64 `json:"id"`
OrderNo string `json:"orderNo"`
TransactionNo string `json:"transactionNo"`
Type string `json:"type"`
Amount int64 `json:"amount"`
BalanceBefore int64 `json:"balanceBefore"`
BalanceAfter int64 `json:"balanceAfter"`
Currency string `json:"currency"`
Description string `json:"description"`
CreatedAt string `json:"createdAt"`
}
// CreateWalletLogReq 创建钱包日志请求(内部使用)
type CreateWalletLogReq struct {
UserID int64 `json:"userId"`
WalletID int64 `json:"walletId"`
OrderNo string `json:"orderNo"`
TransactionNo string `json:"transactionNo"`
Type string `json:"type"`
Amount int64 `json:"amount"`
BalanceBefore int64 `json:"balanceBefore"`
BalanceAfter int64 `json:"balanceAfter"`
Currency string `json:"currency"`
Description string `json:"description"`
ExtraData map[string]interface{} `json:"extraData"`
}

View File

@@ -0,0 +1,82 @@
package entity
import (
knapsackConsts "shop-user-trade/consts/knapsack"
"shop-user-trade/model/config"
"gitea.com/red-future/common/beans"
)
type knapsackCol struct {
beans.SQLBaseCol
UserID string
AssetID string
AssetName string
SkuID string
SkuName string
SpecValues string
ImageURL string
Type string
StockDetailID string
BatchID string
BatchNo string
StockMode string
Status string
UsedAt string
ExpireAt string
PhysicalAssetConfig string
ServiceAssetConfig string
VirtualAssetConfig string
}
var KnapsackCol = knapsackCol{
SQLBaseCol: beans.DefSQLBaseCol,
UserID: "user_id",
AssetID: "asset_id",
AssetName: "asset_name",
SkuID: "sku_id",
SkuName: "sku_name",
SpecValues: "spec_values",
ImageURL: "image_url",
Type: "type",
StockDetailID: "stock_detail_id",
BatchID: "batch_id",
BatchNo: "batch_no",
StockMode: "stock_mode",
Status: "status",
UsedAt: "used_at",
ExpireAt: "expire_at",
PhysicalAssetConfig: "physical_asset_config",
ServiceAssetConfig: "service_asset_config",
VirtualAssetConfig: "virtual_asset_config",
}
// Knapsack 背包实体
type Knapsack struct {
beans.SQLBaseDO `orm:",inherit"`
UserID int64 `orm:"user_id" json:"userId" description:"用户ID"`
AssetID int64 `orm:"asset_id" json:"assetId,string" description:"资产ID"`
AssetName string `orm:"asset_name" json:"assetName" description:"资产名称"`
SkuID int64 `orm:"sku_id" json:"skuId,string" description:"SKU ID"`
SkuName string `orm:"sku_name" json:"skuName" description:"SKU名称"`
SpecValues map[string]interface{} `orm:"spec_values" json:"specValues" description:"规格值(JSONB)"`
ImageURL string `orm:"image_url" json:"imageUrl" description:"资产图片URL"`
Type knapsackConsts.KnapsackAssetType `orm:"type" json:"type" description:"资产类型physical/virtual/service"`
// 库存相关字段
StockDetailID int64 `orm:"stock_detail_id" json:"stockDetailId,string" description:"库存明细ID明细模式"`
BatchID int64 `orm:"batch_id" json:"batchId,string" description:"批次ID批次模式"`
BatchNo string `orm:"batch_no" json:"batchNo" description:"批次号(批次模式)"`
StockMode int `orm:"stock_mode" json:"stockMode" description:"库存管理模式1-明细模式2-批次模式"`
// 状态字段
Status knapsackConsts.KnapsackStatus `orm:"status" json:"status" description:"状态0禁用/1启用/2已使用/3已上架/4已过期"`
UsedAt *int64 `orm:"used_at" json:"usedAt,omitempty" description:"使用时间(时间戳)"`
ExpireAt *int64 `orm:"expire_at" json:"expireAt,omitempty" description:"过期时间(时间戳)"`
// 类型专用配置JSONB
PhysicalAssetConfig *config.PhysicalKnapsackConfig `orm:"physical_asset_config" json:"physicalAssetConfig,omitempty" description:"实物资产配置(JSONB)"`
ServiceAssetConfig *config.ServiceKnapsackConfig `orm:"service_asset_config" json:"serviceAssetConfig,omitempty" description:"服务资产配置(JSONB)"`
VirtualAssetConfig *config.VirtualKnapsackConfig `orm:"virtual_asset_config" json:"virtualAssetConfig,omitempty" description:"虚拟资产配置(JSONB)"`
}

View File

@@ -0,0 +1,19 @@
package entity
import (
knapsackConsts "shop-user-trade/consts/knapsack"
"gitea.com/red-future/common/beans"
)
// KnapsackLog 背包操作日志实体
type KnapsackLog struct {
beans.SQLBaseDO `orm:",inherit"`
KnapsackID int64 `orm:"knapsack_id" json:"knapsackId,string" description:"背包项ID"`
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
Action knapsackConsts.KnapsackActionType `orm:"action" json:"action" description:"操作类型"`
OperatorID int64 `orm:"operator_id" json:"operatorId,string" description:"操作者ID"`
OperatorName string `orm:"operator_name" json:"operatorName" description:"操作者名称"`
Description string `orm:"description" json:"description" description:"操作描述"`
}

View File

@@ -0,0 +1,80 @@
package entity
import (
marketConsts "shop-user-trade/consts/market"
"gitea.com/red-future/common/beans"
)
type marketCol struct {
beans.SQLBaseCol
KnapsackID string
UserID string
AssetID string
AssetName string
SkuID string
SkuName string
ImageURL string
Type string
StockDetailID string
BatchID string
BatchNo string
StockMode string
Price string
OriginalPrice string
Description string
Status string
ListExpireAt string
}
var MarketCol = marketCol{
SQLBaseCol: beans.DefSQLBaseCol,
KnapsackID: "knapsack_id",
UserID: "user_id",
AssetID: "asset_id",
AssetName: "asset_name",
SkuID: "sku_id",
SkuName: "sku_name",
ImageURL: "image_url",
Type: "type",
StockDetailID: "stock_detail_id",
BatchID: "batch_id",
BatchNo: "batch_no",
StockMode: "stock_mode",
Price: "price",
OriginalPrice: "original_price",
Description: "description",
Status: "status",
ListExpireAt: "list_expire_at",
}
// Market 市场实体
type Market struct {
beans.SQLBaseDO `orm:",inherit"`
KnapsackID int64 `orm:"knapsack_id" json:"knapsackId,string" description:"背包项ID"`
UserID int64 `orm:"user_id" json:"userId,string" description:"卖家用户ID"`
AssetID int64 `orm:"asset_id" json:"assetId,string" description:"资产ID"`
AssetName string `orm:"asset_name" json:"assetName" description:"资产名称"`
SkuID int64 `orm:"sku_id" json:"skuId,string" description:"SKU ID"`
SkuName string `orm:"sku_name" json:"skuName" description:"SKU名称"`
ImageURL string `orm:"image_url" json:"imageUrl" description:"资产图片URL"`
Type string `orm:"type" json:"type" description:"资产类型physical/virtual/service"`
// 库存相关字段
StockDetailID int64 `orm:"stock_detail_id" json:"stockDetailId,string" description:"库存明细ID明细模式"`
BatchID int64 `orm:"batch_id" json:"batchId,string" description:"批次ID批次模式"`
BatchNo string `orm:"batch_no" json:"batchNo" description:"批次号(批次模式)"`
StockMode int `orm:"stock_mode" json:"stockMode" description:"库存管理模式1-明细模式2-批次模式"`
// 交易相关字段
Price int64 `orm:"price" json:"price" description:"售价(分)"`
OriginalPrice int64 `orm:"original_price" json:"originalPrice" description:"原价(分)"`
Description string `orm:"description" json:"description" description:"描述信息"`
// 状态字段
Status marketConsts.MarketStatus `orm:"status" json:"status" description:"状态1活跃/2已售/3下架/4过期"`
// 上架过期时间
ListExpireAt *int64 `orm:"list_expire_at" json:"listExpireAt,omitempty" description:"上架过期时间(时间戳)"`
}

View File

@@ -0,0 +1,20 @@
package entity
import (
marketConsts "shop-user-trade/consts/market"
"gitea.com/red-future/common/beans"
)
// MarketLog 市场操作日志实体
type MarketLog struct {
beans.SQLBaseDO `orm:",inherit"`
MarketID int64 `orm:"market_id" json:"marketId,string" description:"市场物品ID"`
KnapsackID int64 `orm:"knapsack_id" json:"knapsackId,string" description:"背包项ID"`
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
Action marketConsts.MarketActionType `orm:"action" json:"action" description:"操作类型"`
OperatorID int64 `orm:"operator_id" json:"operatorId,string" description:"操作者ID"`
OperatorName string `orm:"operator_name" json:"operatorName" description:"操作者名称"`
Description string `orm:"description" json:"description" description:"操作描述"`
}

View File

@@ -0,0 +1,36 @@
package entity
import (
walletConsts "shop-user-trade/consts/wallet"
"gitea.com/red-future/common/beans"
)
type walletCol struct {
beans.SQLBaseCol
UserID string
Balance string
Currency string
Status string
Version string
}
var WalletCol = walletCol{
SQLBaseCol: beans.DefSQLBaseCol,
UserID: "user_id",
Balance: "balance",
Currency: "currency",
Status: "status",
Version: "version",
}
// Wallet 钱包实体
type Wallet struct {
beans.SQLBaseDO `orm:",inherit"`
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
Balance int64 `orm:"balance" json:"balance" description:"余额(分)"`
Currency string `orm:"currency" json:"currency" description:"货币类型CNY-人民币"`
Status walletConsts.WalletStatus `orm:"status" json:"status" description:"状态1启用/0禁用/-1冻结"`
Version int64 `orm:"version" json:"version" description:"乐观锁版本号"`
}

View File

@@ -0,0 +1,24 @@
package entity
import (
walletConsts "shop-user-trade/consts/wallet"
"gitea.com/red-future/common/beans"
)
// WalletLog 钱包操作日志实体
type WalletLog struct {
beans.SQLBaseDO `orm:",inherit"`
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
WalletID int64 `orm:"wallet_id" json:"walletId,string" description:"钱包ID"`
OrderNo string `orm:"order_no" json:"orderNo" description:"业务订单号"`
TransactionNo string `orm:"transaction_no" json:"transactionNo" description:"钱包交易流水号"`
Type walletConsts.WalletLogType `orm:"type" json:"type" description:"操作类型"`
Amount int64 `orm:"amount" json:"amount" description:"金额(分)"`
BalanceBefore int64 `orm:"balance_before" json:"balanceBefore" description:"操作前余额"`
BalanceAfter int64 `orm:"balance_after" json:"balanceAfter" description:"操作后余额"`
Currency string `orm:"currency" json:"currency" description:"货币类型"`
Description string `orm:"description" json:"description" description:"描述"`
ExtraData map[string]interface{} `orm:"extra_data" json:"extraData" description:"额外数据(JSONB)"`
}

View File

@@ -0,0 +1,33 @@
package entity
import "gitea.com/red-future/common/beans"
type walletLogCol struct {
beans.SQLBaseCol
UserID string
WalletID string
OrderNo string
TransactionNo string
Type string
Amount string
BalanceBefore string
BalanceAfter string
Currency string
Description string
ExtraData string
}
var WalletLogCol = walletLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
UserID: "user_id",
WalletID: "wallet_id",
OrderNo: "order_no",
TransactionNo: "transaction_no",
Type: "type",
Amount: "amount",
BalanceBefore: "balance_before",
BalanceAfter: "balance_after",
Currency: "currency",
Description: "description",
ExtraData: "extra_data",
}

View File

@@ -0,0 +1,262 @@
package service
import (
"context"
"crypto/md5"
"errors"
"fmt"
"time"
"github.com/gogf/gf/v2/util/gconv"
knapsackConsts "shop-user-trade/consts/knapsack"
knapsackDao "shop-user-trade/dao/knapsack"
knapsackDto "shop-user-trade/model/dto/knapsack"
knapsackEntity "shop-user-trade/model/entity/knapsack"
)
type knapsack struct{}
// Knapsack 背包服务
var Knapsack = new(knapsack)
// Create 创建背包项
func (s *knapsack) Create(ctx context.Context, req *knapsackDto.CreateKnapsackReq) (int64, error) {
id, err := knapsackDao.Knapsack.Insert(ctx, req)
if err != nil {
return 0, fmt.Errorf("创建背包项失败: %w", err)
}
return id, nil
}
// GetOne 获取单个背包项
func (s *knapsack) GetOne(ctx context.Context, req *knapsackDto.GetKnapsackReq) (*knapsackDto.GetKnapsackRes, error) {
item, err := knapsackDao.Knapsack.GetOne(ctx, req)
if err != nil {
return nil, err
}
if item == nil {
return nil, errors.New("背包项不存在")
}
return &knapsackDto.GetKnapsackRes{
KnapsackItem: s.entityToItem(item),
}, nil
}
// List 获取背包列表(支持分页和搜索)
func (s *knapsack) List(ctx context.Context, req *knapsackDto.ListKnapsackReq) (*knapsackDto.ListKnapsackRes, error) {
list, total, err := knapsackDao.Knapsack.List(ctx, req)
if err != nil {
return nil, err
}
res := &knapsackDto.ListKnapsackRes{Total: total}
for _, item := range list {
itemCopy := item
res.List = append(res.List, s.entityToItem(&itemCopy))
}
return res, nil
}
// Use 使用背包项
func (s *knapsack) Use(ctx context.Context, req *knapsackDto.UseKnapsackReq) error {
item, err := knapsackDao.Knapsack.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("背包项不存在")
}
if err = s.canBeUsed(item); err != nil {
return err
}
now := time.Now().Unix()
usedStatus := knapsackConsts.KnapsackStatusUsed
updateReq := &knapsackDto.UpdateKnapsackReq{
Id: item.Id,
Status: &usedStatus,
UsedAt: &now,
Updater: req.OperatorName,
}
if _, err = knapsackDao.Knapsack.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新背包项失败: %w", err)
}
return nil
}
// ListToMarket 上架背包项到市场
func (s *knapsack) ListToMarket(ctx context.Context, req *knapsackDto.ListToMarketReq) error {
item, err := knapsackDao.Knapsack.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("背包项不存在")
}
if err = s.canBeListed(item); err != nil {
return err
}
listedStatus := knapsackConsts.KnapsackStatusListed
updateReq := &knapsackDto.UpdateKnapsackReq{
Id: item.Id,
Status: &listedStatus,
Updater: req.OperatorName,
}
if _, err = knapsackDao.Knapsack.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新背包项失败: %w", err)
}
return nil
}
// UnlistKnapsack 从市场下架背包项
func (s *knapsack) UnlistKnapsack(ctx context.Context, req *knapsackDto.UnlistKnapsackReq) error {
item, err := knapsackDao.Knapsack.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("背包项不存在")
}
if item.Status != knapsackConsts.KnapsackStatusListed {
return errors.New("只有已上架的物品才能下架")
}
activeStatus := knapsackConsts.KnapsackStatusActive
updateReq := &knapsackDto.UpdateKnapsackReq{
Id: item.Id,
Status: &activeStatus,
Updater: req.OperatorName,
}
if _, err = knapsackDao.Knapsack.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新背包项失败: %w", err)
}
return nil
}
// Verify 核销背包项
func (s *knapsack) Verify(ctx context.Context, req *knapsackDto.VerifyKnapsackReq) error {
item, err := knapsackDao.Knapsack.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("背包项不存在")
}
if err = s.canBeVerified(item); err != nil {
return err
}
now := time.Now().Unix()
usedStatus := knapsackConsts.KnapsackStatusUsed
updateReq := &knapsackDto.UpdateKnapsackReq{
Id: item.Id,
Status: &usedStatus,
UsedAt: &now,
Updater: req.OperatorName,
}
if _, err = knapsackDao.Knapsack.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新背包项失败: %w", err)
}
return nil
}
// GenerateQRCode 生成核销二维码
func (s *knapsack) GenerateQRCode(ctx context.Context, req *knapsackDto.GenerateQRCodeReq) (*knapsackDto.GenerateQRCodeRes, error) {
item, err := knapsackDao.Knapsack.GetByID(ctx, req.ID)
if err != nil {
return nil, err
}
if item == nil {
return nil, errors.New("背包项不存在")
}
if err = s.canBeUsed(item); err != nil {
return nil, err
}
now := time.Now().Unix()
expireTime := now + req.ExpireDuration
verifyCode := s.generateVerifyCode(gconv.String(item.Id), now)
qrContent := fmt.Sprintf("%d:%d:%d:%s", item.Id, now, expireTime, verifyCode)
qrCode := fmt.Sprintf("QR_%x", md5.Sum([]byte(qrContent)))
return &knapsackDto.GenerateQRCodeRes{
QRCode: qrCode,
VerifyCode: verifyCode,
}, nil
}
// ExpireExpiredItems 将过期的背包项标记为过期状态(定时任务调用)
func (s *knapsack) ExpireExpiredItems(ctx context.Context) (int64, error) {
expiredList, err := knapsackDao.Knapsack.ListExpired(ctx)
if err != nil {
return 0, fmt.Errorf("查询过期物品失败: %w", err)
}
if len(expiredList) == 0 {
return 0, nil
}
count := int64(len(expiredList))
expiredStatus := knapsackConsts.KnapsackStatusExpired
for _, item := range expiredList {
updateReq := &knapsackDto.UpdateKnapsackReq{
Id: item.Id,
Status: &expiredStatus,
}
if _, err = knapsackDao.Knapsack.Update(ctx, updateReq); err != nil {
return count, fmt.Errorf("更新过期物品状态失败: %w", err)
}
}
return count, nil
}
// canBeUsed 检查背包项是否可以使用
func (s *knapsack) canBeUsed(item *knapsackEntity.Knapsack) error {
if item.Status != knapsackConsts.KnapsackStatusActive {
return errors.New("物品状态不可用")
}
if item.ExpireAt != nil && *item.ExpireAt < time.Now().Unix() {
return errors.New("物品已过期")
}
return nil
}
// canBeListed 检查背包项是否可以上架
func (s *knapsack) canBeListed(item *knapsackEntity.Knapsack) error {
if item.Status != knapsackConsts.KnapsackStatusActive {
return errors.New("只有启用状态的物品才能上架")
}
if item.ExpireAt != nil && *item.ExpireAt < time.Now().Unix() {
return errors.New("已过期的物品不能上架")
}
return nil
}
// canBeVerified 检查背包项是否可以核销
func (s *knapsack) canBeVerified(item *knapsackEntity.Knapsack) error {
if item.Status != knapsackConsts.KnapsackStatusActive {
return errors.New("物品状态不可核销")
}
if item.ExpireAt != nil && *item.ExpireAt < time.Now().Unix() {
return errors.New("物品已过期")
}
return nil
}
// generateVerifyCode 生成验证码
func (s *knapsack) generateVerifyCode(knapsackID string, timestamp int64) string {
data := fmt.Sprintf("%s_%d_verify", knapsackID, timestamp)
hash := md5.Sum([]byte(data))
return fmt.Sprintf("V%x", hash)[:8]
}
// entityToItem 实体转换为Item
func (s *knapsack) entityToItem(e *knapsackEntity.Knapsack) *knapsackDto.KnapsackItem {
item := &knapsackDto.KnapsackItem{}
if err := gconv.Struct(e, item); err != nil {
return item
}
item.ID = e.Id
item.Status = e.Status
item.Type = e.Type
if e.CreatedAt != nil {
item.CreatedAt = e.CreatedAt.String()
}
if e.UpdatedAt != nil {
item.UpdatedAt = e.UpdatedAt.String()
}
return item
}

View File

@@ -0,0 +1,249 @@
package service
import (
"context"
"errors"
"fmt"
"time"
"gitea.com/red-future/common/consul"
"gitea.com/red-future/common/http"
"github.com/gogf/gf/v2/util/gconv"
marketConsts "shop-user-trade/consts/market"
marketDao "shop-user-trade/dao/market"
marketDto "shop-user-trade/model/dto/market"
marketEntity "shop-user-trade/model/entity/market"
)
type market struct{}
// Market 市场服务
var Market = new(market)
// Create 创建市场物品
func (s *market) Create(ctx context.Context, req *marketDto.CreateMarketReq) (int64, error) {
// 检查该背包项是否已经在市场中
existing, _ := marketDao.Market.GetByKnapsackID(ctx, req.KnapsackID)
if existing != nil && existing.Id > 0 {
return 0, errors.New("该物品已在市场中")
}
// 设置上架过期时间默认7天
if req.ListExpireAt == nil {
defaultExpire := time.Now().Add(7 * 24 * time.Hour).Unix()
req.ListExpireAt = &defaultExpire
}
id, err := marketDao.Market.Insert(ctx, req)
if err != nil {
return 0, fmt.Errorf("创建市场物品失败: %w", err)
}
return id, nil
}
// GetOne 获取单个市场物品
func (s *market) GetOne(ctx context.Context, req *marketDto.GetMarketReq) (*marketDto.GetMarketRes, error) {
item, err := marketDao.Market.GetOne(ctx, req)
if err != nil {
return nil, err
}
if item == nil {
return nil, errors.New("市场物品不存在")
}
return &marketDto.GetMarketRes{
MarketItem: s.entityToItem(item),
}, nil
}
// List 获取市场列表(支持分页和搜索)
func (s *market) List(ctx context.Context, req *marketDto.ListMarketReq) (*marketDto.ListMarketRes, error) {
list, total, err := marketDao.Market.List(ctx, req)
if err != nil {
return nil, err
}
res := &marketDto.ListMarketRes{Total: total}
for _, item := range list {
itemCopy := item
res.List = append(res.List, s.entityToItem(&itemCopy))
}
return res, nil
}
// Unlist 下架市场物品
func (s *market) Unlist(ctx context.Context, req *marketDto.UnlistMarketReq) error {
item, err := marketDao.Market.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("市场物品不存在")
}
if item.Status != marketConsts.MarketStatusActive {
return errors.New("只有活跃状态的物品才能下架")
}
inactiveStatus := marketConsts.MarketStatusInactive
updateReq := &marketDto.UpdateMarketReq{
Id: item.Id,
Status: &inactiveStatus,
Updater: req.OperatorName,
}
if _, err = marketDao.Market.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新市场物品失败: %w", err)
}
return nil
}
// UpdatePrice 更新价格
func (s *market) UpdatePrice(ctx context.Context, req *marketDto.UpdatePriceReq) error {
item, err := marketDao.Market.GetByID(ctx, req.ID)
if err != nil {
return err
}
if item == nil {
return errors.New("市场物品不存在")
}
if item.Status != marketConsts.MarketStatusActive {
return errors.New("只有活跃状态的物品才能更新价格")
}
updateReq := &marketDto.UpdateMarketReq{
Id: item.Id,
Price: &req.Price,
Updater: req.OperatorName,
}
if _, err = marketDao.Market.Update(ctx, updateReq); err != nil {
return fmt.Errorf("更新价格失败: %w", err)
}
return nil
}
// Buy 购买市场物品 - 调用order模块创建订单
func (s *market) Buy(ctx context.Context, req *marketDto.BuyMarketReq) (string, error) {
item, err := marketDao.Market.GetByID(ctx, req.MarketID)
if err != nil {
return "", err
}
if item == nil {
return "", errors.New("市场物品不存在")
}
if err = s.canBeBought(item); err != nil {
return "", err
}
// 检查买家不能是卖家
if item.UserID == req.BuyerID {
return "", errors.New("不能购买自己上架的物品")
}
// 调用order模块创建订单
orderAddr, err := consul.GetInstanceAddr(ctx, "order")
if err != nil {
return "", fmt.Errorf("获取order服务地址失败: %w", err)
}
orderReq := map[string]interface{}{
"user_id": req.BuyerID,
"order_type": "market",
"subject": item.AssetName,
"description": item.Description,
"order_items": []map[string]interface{}{
{
"asset_id": item.AssetID,
"asset_name": item.AssetName,
"asset_type": item.Type,
"image_url": item.ImageURL,
"stocks": []map[string]interface{}{
{
"stock_id": item.StockDetailID,
"batch_id": item.BatchID,
"batch_no": item.BatchNo,
"quantity": 1,
"price": item.Price,
"stock_mode": item.StockMode,
"stock_attrs": map[string]interface{}{
"market_id": item.Id,
"seller_id": item.UserID,
},
},
},
},
},
"shipping_info": map[string]interface{}{},
}
orderRes := &struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
OrderNo string `json:"order_no"`
TotalAmount int64 `json:"total_amount"`
PayAmount int64 `json:"pay_amount"`
ExpiredAt string `json:"expired_at"`
} `json:"data"`
}{}
err = http.Post(ctx, fmt.Sprintf("http://%s/order/create", orderAddr), nil, orderRes, orderReq) // #nosec G107
if err != nil {
return "", fmt.Errorf("创建订单失败: %w", err)
}
// 更新市场物品状态为已售出
soldStatus := marketConsts.MarketStatusSold
updateReq := &marketDto.UpdateMarketReq{
Id: item.Id,
Status: &soldStatus,
}
if _, err = marketDao.Market.Update(ctx, updateReq); err != nil {
return "", fmt.Errorf("更新市场物品失败: %w", err)
}
return orderRes.Data.OrderNo, nil
}
// ExpireExpiredItems 将过期的市场物品标记为过期状态(定时任务调用)
func (s *market) ExpireExpiredItems(ctx context.Context) (int64, error) {
expiredList, err := marketDao.Market.ListExpired(ctx)
if err != nil {
return 0, fmt.Errorf("查询过期物品失败: %w", err)
}
if len(expiredList) == 0 {
return 0, nil
}
count := int64(len(expiredList))
expiredStatus := marketConsts.MarketStatusExpired
for _, item := range expiredList {
updateReq := &marketDto.UpdateMarketReq{
Id: item.Id,
Status: &expiredStatus,
}
if _, err = marketDao.Market.Update(ctx, updateReq); err != nil {
return count, fmt.Errorf("更新过期物品状态失败: %w", err)
}
}
return count, nil
}
// canBeBought 检查市场物品是否可以购买
func (s *market) canBeBought(item *marketEntity.Market) error {
if item.Status != marketConsts.MarketStatusActive {
return errors.New("物品状态不可购买")
}
if item.ListExpireAt != nil && *item.ListExpireAt < time.Now().Unix() {
return errors.New("物品已过期")
}
return nil
}
// entityToItem 实体转换为Item
func (s *market) entityToItem(e *marketEntity.Market) *marketDto.MarketItem {
item := &marketDto.MarketItem{}
if err := gconv.Struct(e, item); err != nil {
return item
}
item.ID = e.Id
item.Status = e.Status
if e.CreatedAt != nil {
item.CreatedAt = e.CreatedAt.String()
}
if e.UpdatedAt != nil {
item.UpdatedAt = e.UpdatedAt.String()
}
return item
}

View File

@@ -0,0 +1,153 @@
package service
import (
"context"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
walletDao "shop-user-trade/dao/wallet"
walletDto "shop-user-trade/model/dto/wallet"
walletEntity "shop-user-trade/model/entity/wallet"
)
type wallet struct{}
// Wallet 钱包服务
var Wallet = new(wallet)
// GetByUserId 根据用户ID获取钱包
func (s *wallet) GetByUserId(ctx context.Context, req *walletDto.GetWalletByUserIdReq) (*walletDto.GetWalletByUserIdResp, error) {
w, err := walletDao.Wallet.GetByUserID(ctx, req.UserId)
if err != nil {
g.Log().Errorf(ctx, "获取用户钱包失败, userId: %d, error: %v", req.UserId, err)
return nil, err
}
if w == nil {
return nil, errors.New("钱包不存在")
}
return &walletDto.GetWalletByUserIdResp{
Data: walletDto.WalletInfo{
ID: w.Id,
UserID: w.UserID,
Balance: w.Balance,
Currency: w.Currency,
Status: int(w.Status),
},
}, nil
}
// Create 创建钱包
func (s *wallet) Create(ctx context.Context, req *walletDto.CreateWalletReq) (*walletDto.CreateWalletResp, error) {
// 检查钱包是否已存在
existing, err := walletDao.Wallet.GetByUserID(ctx, req.UserId)
if err != nil {
g.Log().Errorf(ctx, "检查用户钱包失败, userId: %d, error: %v", req.UserId, err)
return nil, err
}
if existing != nil {
return nil, errors.New("钱包已存在")
}
id, err := walletDao.Wallet.Create(ctx, req)
if err != nil {
g.Log().Errorf(ctx, "创建钱包失败, userId: %d, error: %v", req.UserId, err)
return nil, err
}
return &walletDto.CreateWalletResp{
Data: walletDto.CreateWalletData{WalletID: id},
}, nil
}
// UpdateBalance 更新余额
func (s *wallet) UpdateBalance(ctx context.Context, req *walletDto.UpdateBalanceReq) (*walletDto.UpdateBalanceResp, error) {
w, err := walletDao.Wallet.GetByID(ctx, req.WalletID)
if err != nil {
g.Log().Errorf(ctx, "获取钱包失败, walletId: %d, error: %v", req.WalletID, err)
return nil, err
}
if w == nil {
return nil, errors.New("钱包不存在")
}
// 检查余额是否充足
if req.Type == "expense" && w.Balance < req.Amount {
return nil, errors.New("余额不足")
}
// 记录操作前余额
balanceBefore := w.Balance
var balanceAfter int64
switch req.Type {
case "income":
balanceAfter = balanceBefore + req.Amount
case "expense":
balanceAfter = balanceBefore - req.Amount
default:
balanceAfter = balanceBefore
}
// 更新余额(乐观锁)
success, err := walletDao.Wallet.UpdateBalance(ctx, req.WalletID, req.Amount, req.Type, w.Version)
if err != nil {
g.Log().Errorf(ctx, "更新余额失败, walletId: %d, error: %v", req.WalletID, err)
return nil, err
}
if !success {
return nil, errors.New("余额更新失败,请重试")
}
// 记录流水日志
logReq := &walletDto.CreateWalletLogReq{
UserID: w.UserID,
WalletID: w.Id,
OrderNo: req.OrderNo,
TransactionNo: req.TransactionNo,
Type: req.Type,
Amount: req.Amount,
BalanceBefore: balanceBefore,
BalanceAfter: balanceAfter,
Currency: w.Currency,
Description: req.Description,
ExtraData: req.ExtraData,
}
if _, err = walletDao.Wallet.CreateLog(ctx, logReq); err != nil {
g.Log().Warningf(ctx, "记录钱包日志失败, walletId: %d, error: %v", req.WalletID, err)
}
return &walletDto.UpdateBalanceResp{
Success: true,
Message: "更新成功",
}, nil
}
// GetWalletLogs 获取钱包日志
func (s *wallet) GetWalletLogs(ctx context.Context, req *walletDto.GetWalletLogsReq) (*walletDto.GetWalletLogsResp, error) {
logs, total, err := walletDao.Wallet.ListLogs(ctx, req.UserId, req.Page, req.PageSize)
if err != nil {
return nil, fmt.Errorf("获取钱包日志失败: %w", err)
}
var logInfos []walletDto.WalletLogInfo
for _, log := range logs {
logInfos = append(logInfos, s.logEntityToInfo(&log))
}
return &walletDto.GetWalletLogsResp{
Data: walletDto.WalletLogData{
Logs: logInfos,
Total: total,
},
}, nil
}
// logEntityToInfo 日志实体转换为Info
func (s *wallet) logEntityToInfo(e *walletEntity.WalletLog) walletDto.WalletLogInfo {
info := walletDto.WalletLogInfo{}
_ = gconv.Struct(e, &info)
info.ID = e.Id
info.Type = string(e.Type)
if e.CreatedAt != nil {
info.CreatedAt = e.CreatedAt.String()
}
return info
}